객체만 전송했을 뿐인데, 서버가 해킹된다면 믿기시나요? 역직렬화 취약점은 그런 가능성을 현실로 만듭니다.
안녕하세요, Java 백엔드 개발과 보안 시큐어코딩을 적용하시는데 어려움이 많으시죠. 오늘은 실무에서도 자주 등장하지만 놓치기 쉬운 역직렬화(Deserialization) 취약점에 대해 설명드립니다. 이 취약점은 단순히 데이터를 객체로 변환하는 과정에서 발생하지만, 악의적인 객체를 역직렬화할 경우 원격 코드 실행(RCE)까지 이어질 수 있는 심각한 보안 위협입니다.
이번 포스팅에서는 직렬화와 역직렬화의 구조 차이를 먼저 이해하고, 실제 취약한 코드 패턴과 공격 흐름, 그리고 Java 환경에서의 안전한 대응 패턴까지 함께 살펴보겠습니다.
📌 바로가기 목차
1. 직렬화 vs 역직렬화, 구조와 차이점
직렬화(Serialization)는 자바 객체를 바이트 스트림으로 변환하여 저장하거나 전송하는 과정이고, 역직렬화(Deserialization)는 이를 다시 객체로 복원하는 작업입니다. 아래 표를 통해 개념을 정리해보겠습니다.
구분 | 직렬화 (Serialization) | 역직렬화 (Deserialization) |
---|---|---|
방향 | 객체 → 바이트 | 바이트 → 객체 |
사용 목적 | 데이터 저장, 네트워크 전송 | 객체 복원 및 실행 |
보안 위험 | 낮음 (파일 유실 등) | 높음 (임의 코드 실행 가능) |
Java에서 ObjectInputStream
을 이용해 역직렬화를 수행할 경우, 외부에서 조작된 바이트 스트림이 들어오면 임의 코드 실행(RCE)이 발생할 수 있습니다.
이제 구조를 이해했으니, 어떤 코드가 취약하고, 어떻게 악용되는지 살펴보겠습니다.
2. 역직렬화 취약점 예제와 공격 시나리오
Java에서는 객체를 바이트 스트림으로 변환하고 다시 복원하는 직렬화/역직렬화 기능을 제공합니다. 그러나 신뢰할 수 없는 입력을 ObjectInputStream
으로 그대로 역직렬화할 경우, 내부적으로 악성 코드가 실행될 수 있는 심각한 취약점이 발생합니다.
다음은 공격자가 생성한 악성 직렬화 객체를 서버에서 역직렬화하면서 코드가 실행되는 실제 흐름을 보여주는 예제입니다.
import java.io.*;
class EvilPayload implements Serializable {
private static final long serialVersionUID = 1L;
private String cmd;
public EvilPayload(String cmd) {
this.cmd = cmd;
}
private void readObject(ObjectInputStream in) throws IOException, ClassNotFoundException {
in.defaultReadObject();
Runtime.getRuntime().exec(cmd); // 실행 시점에 명령 실행
}
}
public class DeserializationVulnerableExample {
public static void serializePayload(String filepath, String command) {
try (ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream(filepath))) {
EvilPayload payload = new EvilPayload(command);
oos.writeObject(payload);
} catch (IOException e) {
e.printStackTrace();
}
}
public static void deserializePayload(String filepath) {
try (ObjectInputStream ois = new ObjectInputStream(new FileInputStream(filepath))) {
Object obj = ois.readObject(); // 취약점 발생 지점
} catch (IOException | ClassNotFoundException e) {
e.printStackTrace();
}
}
public static void main(String[] args) {
String file = "evil_payload.ser";
serializePayload(file, "calc.exe"); // Windows 예시
deserializePayload(file);
}
}
이 코드는 단순하지만 실제로 많이 발생하는 취약 패턴입니다. Spring Boot 애플리케이션에서 사용자가 전달한 객체 데이터를 무심코 역직렬화할 경우, 시스템 명령 실행, 파일 접근, 외부 접속 등의 행위로 이어질 수 있습니다.
3. 안전한 역직렬화 처리 방식과 방어 전략
Spring Boot 또는 Java 환경에서 역직렬화를 안전하게 처리하기 위해서는 다음과 같은 원칙을 적용해야 합니다.
- 직렬화 기반 통신을 지양하고, JSON, XML 등 구조적 포맷으로 대체
- ObjectInputStream을 직접 사용하지 않거나, 반드시 클래스 필터링을 적용
- 사용자 입력을 역직렬화하기 전, 출처 및 무결성 검증 필수
- Jackson 사용 시
enableDefaultTyping
비활성화
public class SafeObjectInputStream extends ObjectInputStream {
public SafeObjectInputStream(InputStream in) throws IOException {
super(in);
}
@Override
protected Class<?> resolveClass(ObjectStreamClass desc) throws IOException, ClassNotFoundException {
String className = desc.getName();
if (!className.startsWith("com.myapp.safe.")) {
throw new InvalidClassException("Unauthorized class: " + className);
}
return super.resolveClass(desc);
}
}
핵심은 외부 입력에 대해 항상 검증 및 제한을 적용하는 것입니다. JSON 직렬화 방식으로 전환하거나, 안전한 커스텀 역직렬화 클래스를 사용하는 것이 가장 좋은 방어 전략입니다.
4. 자주 묻는 질문 (FAQ)
아닙니다. 내부 통제된 시스템 간에 사용하는 역직렬화는 상대적으로 안전합니다. 하지만 외부 입력을 처리할 경우에는 항상 위협이 존재합니다.
Jackson이나 Gson 같은 라이브러리도 enableDefaultTyping
등의 설정에 따라 위험할 수 있습니다. 타입 정보 자동 포함 기능은 비활성화하는 것이 안전합니다.
ysoserial은 다양한 Java 라이브러리에서 발생할 수 있는 역직렬화 취약점을 자동화해주는 오픈소스 툴입니다. 침투 테스트에 자주 사용됩니다.
5. 마무리 요약
Java에서 역직렬화 취약점은 간단한 코드 한 줄에서 시작되지만, 공격자에게는 서버를 장악할 수 있는 통로가 될 수 있습니다. 이번 포스팅을 통해 직렬화 구조를 이해하고, 신뢰할 수 없는 입력에 대한 방어 전략을 꼭 숙지해보시기 바랍니다.
'시큐어코딩 > JAVA' 카테고리의 다른 글
[시큐어코딩]Java에서 SQL Injection 취약점 JPA,Mybatis,동적쿼리 완전정복 (1) | 2025.06.13 |
---|---|
[시큐어코딩]Java에서 XML 외부 개체(XXE) 취약점 안전하게 막는 방법 (1) | 2025.04.16 |
[시큐어코딩]Java에서 부적절한 인증서 유효성 검증 – 보안 연결의 허점을 막는 시큐어코딩 방법 (0) | 2025.04.16 |
[시큐어코딩]Java에서 전자서명 검증 실패가 초래하는 실무 위협과 대응 전략 완벽 가이드 (0) | 2025.04.10 |
[시큐어코딩]Java에서 부적절한 세션 종료 보안 이슈와 안전한 처리 방법4 (0) | 2025.04.10 |