본문 바로가기
시큐어코딩/JAVA

[시큐어코딩]Java에서 신뢰되지 않은 역직렬화 취약점, 왜 위험하고 어떻게 막을까?

by ICT리더 리치 2025. 4. 17.

객체만 전송했을 뿐인데, 서버가 해킹된다면 믿기시나요? 역직렬화 취약점은 그런 가능성을 현실로 만듭니다.

안녕하세요, Java 백엔드 개발과 보안 시큐어코딩을 적용하시는데 어려움이 많으시죠. 오늘은 실무에서도 자주 등장하지만 놓치기 쉬운 역직렬화(Deserialization) 취약점에 대해 설명드립니다. 이 취약점은 단순히 데이터를 객체로 변환하는 과정에서 발생하지만, 악의적인 객체를 역직렬화할 경우 원격 코드 실행(RCE)까지 이어질 수 있는 심각한 보안 위협입니다.

이번 포스팅에서는 직렬화와 역직렬화의 구조 차이를 먼저 이해하고, 실제 취약한 코드 패턴과 공격 흐름, 그리고 Java 환경에서의 안전한 대응 패턴까지 함께 살펴보겠습니다.

1. 직렬화 vs 역직렬화, 구조와 차이점

직렬화(Serialization)는 자바 객체를 바이트 스트림으로 변환하여 저장하거나 전송하는 과정이고, 역직렬화(Deserialization)는 이를 다시 객체로 복원하는 작업입니다. 아래 표를 통해 개념을 정리해보겠습니다.

구분 직렬화 (Serialization) 역직렬화 (Deserialization)
방향 객체 → 바이트 바이트 → 객체
사용 목적 데이터 저장, 네트워크 전송 객체 복원 및 실행
보안 위험 낮음 (파일 유실 등) 높음 (임의 코드 실행 가능)

Java에서 ObjectInputStream을 이용해 역직렬화를 수행할 경우, 외부에서 조작된 바이트 스트림이 들어오면 임의 코드 실행(RCE)이 발생할 수 있습니다.

이제 구조를 이해했으니, 어떤 코드가 취약하고, 어떻게 악용되는지 살펴보겠습니다.

웃는 표정으로 자바 강의를 진행하는 20대 한국 여성 전문강사
밝고 자신감 있는 모습으로 강의를 진행하는 20대 자바 강사

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)

Q 모든 역직렬화가 위험한가요?

아닙니다. 내부 통제된 시스템 간에 사용하는 역직렬화는 상대적으로 안전합니다. 하지만 외부 입력을 처리할 경우에는 항상 위협이 존재합니다.

Q JSON 역직렬화도 위험한가요?

Jackson이나 Gson 같은 라이브러리도 enableDefaultTyping 등의 설정에 따라 위험할 수 있습니다. 타입 정보 자동 포함 기능은 비활성화하는 것이 안전합니다.

Q ysoserial은 뭔가요?

ysoserial은 다양한 Java 라이브러리에서 발생할 수 있는 역직렬화 취약점을 자동화해주는 오픈소스 툴입니다. 침투 테스트에 자주 사용됩니다.

5. 마무리 요약

Java에서 역직렬화 취약점은 간단한 코드 한 줄에서 시작되지만, 공격자에게는 서버를 장악할 수 있는 통로가 될 수 있습니다. 이번 포스팅을 통해 직렬화 구조를 이해하고, 신뢰할 수 없는 입력에 대한 방어 전략을 꼭 숙지해보시기 바랍니다.

반응형