Python에서 Pickle을 사용한 Deserialization의 보안 위험
Wenhao Wang
Dev Intern · Leapcell

Python에서 Pickle을 사용한 Deserialization의 보안 위험
소개
안녕하세요 여러분! Python 프로그래밍 영역에는 잠재적인 보안 위험인 deserialization 공격이 존재합니다. Deserialization 공격에 대해 자세히 알아보기 전에 serialization과 deserialization이 무엇인지 이해하는 것이 중요합니다.
개념적으로 serialization은 데이터 구조 또는 객체를 바이트 스트림으로 변환하는 프로세스입니다. 이러한 변환을 통해 데이터를 파일에 편리하게 저장하거나 네트워크를 통해 전송할 수 있습니다. 반면에 deserialization은 바이트 스트림을 원래의 데이터 구조 또는 객체로 다시 변환하는 역방향 프로세스입니다.
Python에서 Pickle 모듈은 serialization 및 deserialization을 구현하는 데 일반적으로 사용되는 도구 중 하나입니다. Pickle 모듈은 복잡한 Python 객체를 직렬화하고 저장할 수 있는 편리한 인터페이스를 제공하며, 나중에 필요할 때 쉽게 직렬 해제하고 복원할 수 있습니다. 그러나 이러한 편리함은 잠재적인 보안 위험도 초래합니다.
Deserialization 공격 개요
Deserialization 프로세스가 항상 안전하고 신뢰할 수 있는 것은 아닙니다. 신뢰할 수 없는 데이터 소스에서 deserialization 작업을 수행할 때 deserialization 공격을 받을 가능성이 있습니다. 공격자는 악성 코드를 직렬화된 데이터에 포함시킬 수 있습니다. 이러한 데이터가 직렬 해제되면 포함된 악성 코드가 실행됩니다. 이러한 공격은 데이터 유출, 시스템 충돌과 같은 심각한 결과를 초래할 수 있으며, 공격자가 시스템의 원격 제어 권한을 획득하도록 할 수도 있습니다.
Python Pickle 모듈 개요
Pickle의 기본 기능
Pickle 모듈은 Python 표준 라이브러리의 일부이며 추가 설치 없이 사용할 수 있습니다. Pickle 모듈의 주요 기능은 Python 객체의 serialization 및 deserialization을 구현하는 것입니다. 간단한 기본 데이터 유형이든 복잡한 데이터 구조(예: 목록, 딕셔너리, 클래스 인스턴스 등)이든 Pickle은 이를 저장 또는 전송을 위해 바이트 스트림으로 변환하고 필요할 때 원래 객체 형태로 복원할 수 있습니다.
Pickle의 작동 원리
Pickle의 작동 원리는 비교적 직관적입니다. Serialization 단계에서 Pickle은 특정 규칙에 따라 Python 객체를 바이트 스트림으로 변환합니다. 이러한 바이트 스트림에는 객체의 유형 정보와 데이터 내용이 포함됩니다. Deserialization 단계에서 Pickle은 바이트 스트림을 읽고 그 안의 정보에 따라 해당 Python 객체로 복원합니다.
Pickle의 Serialization 및 Deserialization
- Serialization:
Pickle은
pickle.dump
와pickle.dumps
라는 두 가지 주요 serialization 기능을 제공합니다.pickle.dump
함수는 직렬화된 객체를 지정된 파일에 직접 쓰고,pickle.dumps
함수는 직렬화된 데이터를 포함하는 바이트 스트림을 반환합니다.
import pickle # 객체 생성 data = {'name': 'Leapcell', 'age': 29, 'city': 'New York'} # 객체를 직렬화하여 파일에 쓰기 with open('data.pickle', 'wb') as file: pickle.dump(data, file) # 또는 바이트 스트림 반환 data_bytes = pickle.dumps(data)
- Deserialization:
Deserialization에는
pickle.load
와pickle.loads
라는 두 가지 일반적으로 사용되는 함수가 있습니다.pickle.load
함수는 지정된 파일에서 바이트 스트림을 읽고 직렬 해제하고,pickle.loads
함수는 바이트 스트림을 직접 직렬 해제합니다.
import pickle # 파일에서 객체 직렬 해제 with open('data.pickle', 'rb') as file: data = pickle.load(file) # 또는 바이트 스트림을 직접 직렬 해제 data = pickle.loads(data_bytes)
Deserialization 공격 원리
공격 메커니즘
Deserialization 공격의 핵심은 공격자가 악성 코드를 직렬화된 데이터에 삽입할 수 있다는 것입니다. 대상 시스템이 악성 코드가 포함된 직렬화된 데이터를 직렬 해제하면 악성 코드가 실행되어 공격자의 목표를 달성합니다. 즉, deserialization 중에 데이터 소스를 엄격하게 검증하고 스크리닝하지 않으면 공격자가 시스템에서 임의의 코드를 실행할 수 있도록 문을 열어주는 것과 같습니다.
공격자가 할 수 있는 일
공격자는 deserialization 취약점을 사용하여 임의의 시스템 명령 실행, 시스템의 중요한 데이터 수정 또는 중요한 정보 도용 등과 같은 다양한 악성 작업을 수행할 수 있습니다. 이러한 작업은 시스템의 보안과 안정성에 심각한 손상을 초래할 수 있습니다.
예제 코드
Deserialization 공격 프로세스를 더 명확하게 보여주기 위해 구체적인 예를 살펴보겠습니다.
import pickle import os # 악성 코드 생성 class Malicious: def __reduce__(self): return (os.system, ('echo Hacked!',)) # 악성 객체 직렬화 malicious_data = pickle.dumps(Malicious()) # Deserialization 중 악성 코드 실행 pickle.loads(malicious_data)
이 예에서:
- 악성 코드 생성:
Malicious
라는 클래스를 정의하고__reduce__
메서드에서 실행될 명령os.system('echo Hacked!')
를 지정합니다.__reduce__
메서드는 객체를 재구성하기 위해 Pickle이 deserialization 프로세스 중에 호출하는 특수 메서드입니다. - 악성 객체 직렬화:
Malicious
클래스의 인스턴스를 직렬화하여 악성 코드가 포함된 바이트 스트림malicious_data
를 얻기 위해pickle.dumps
함수를 사용합니다. - 악성 객체 직렬 해제:
malicious_data
를 직렬 해제하기 위해pickle.loads
함수를 사용하면__reduce__
메서드가 호출되어 지정된 명령이 실행되고 "Hacked!"가 출력됩니다.
Pickle Deserialization 공격을 방지하는 방법
안전한 Deserialization 원칙
Deserialization 공격을 방지하는 주요 원칙은 신뢰할 수 없는 소스에서 deserialization 작업을 수행하지 않는 것입니다. 데이터 소스를 완전히 신뢰할 수 있는 경우에만 deserialization 작업을 수행할 수 있습니다.
실제 방어 방법
- 안전한 Deserialization 코드 예제:
경우에 따라 Pickle을 사용한 deserialization이 필요한 경우
find_class
메서드를 오버로드하여 deserialization할 수 있는 객체 유형을 제한하여 deserialization 범위를 제한할 수 있습니다.
import pickle import types import io # 직렬 해제 가능한 유형을 제한하는 사용자 지정 Unpickler class RestrictedUnpickler(pickle.Unpickler): def find_class(self, module, name): if module == "builtins" and name in {"str", "list", "dict", "set", "int", "float", "bool"}: return getattr(__import__(module), name) raise pickle.UnpicklingError(f"global '{module}.{name}' is forbidden") def restricted_loads(s): return RestrictedUnpickler(io.BytesIO(s)).load()
위의 코드에서 pickle.Unpickler
에서 상속되고 find_class
메서드를 재정의하는 RestrictedUnpickler
클래스를 사용자 정의했습니다. 이러한 방식으로 일부 안전한 기본 제공 유형만 직렬 해제할 수 있으므로 deserialization 작업의 보안이 향상됩니다.
2. 다른 안전한 Serialization 모듈 사용(예: JSON):
보다 안전한 옵션은 serialization 및 deserialization 작업에 Pickle 대신 JSON 모듈을 사용하는 것입니다. JSON은 기본 데이터 유형(예: 문자열, 숫자, 부울, 배열 및 객체)만 지원하며 임의의 코드를 실행하지 않으므로 보안 측면에서 특정 이점이 있습니다.
import json # 객체 직렬화 data = {'name': 'Leapcell', 'age': 29, 'city': 'New York'} data_json = json.dumps(data) # 객체 직렬 해제 data = json.loads(data_json)
결론
이 기사에서는 Python의 serialization 및 deserialization 개념과 이 프로세스에서 Pickle 모듈의 응용 프로그램을 포괄적으로 소개합니다. 동시에 deserialization 공격의 원리를 자세히 설명하고 공격자가 특정 코드 예제를 통해 사용할 수 있는 방법을 보여줍니다. 마지막으로 deserialization 유형을 제한하고 보다 안전한 serialization 모듈을 사용하는 것을 포함하여 Pickle deserialization 공격을 방지하기 위한 원칙과 특정 방법을 논의했습니다. 이 기사의 소개를 통해 모든 사람이 deserialization 공격에 대한 더 깊은 이해를 갖고 실제 프로그래밍에서 효과적인 예방 조치를 취하여 시스템 보안을 보장할 수 있기를 바랍니다. 이 기사의 내용에 대한 질문이나 제안 사항이 있으면 댓글 섹션에서 자유롭게 토론하십시오.
Leapcell: 최고의 서버리스 웹 호스팅
마지막으로 Python 서비스를 배포하는 데 가장 적합한 플랫폼인 **Leapcell**을 추천합니다.
🚀 좋아하는 언어로 빌드
JavaScript, Python, Go 또는 Rust로 쉽게 개발하십시오.
🌍 무제한 프로젝트를 무료로 배포
사용한 만큼만 지불하십시오. 요청도 없고 요금도 없습니다.
⚡ 사용량에 따라 지불, 숨겨진 비용 없음
유휴 요금 없이 원활한 확장성만 제공합니다.
🔹 Twitter에서 팔로우하세요: @LeapcellHQ