애플리케이션 보안 가이드: Java 기반 시스템의 직렬화 취약점 대응 전략

애플리케이션 보안에서 직렬화(Serialization) 취약점은 가장 치명적인 공격 경로 중 하나입니다. 공격자가 악의적으로 조작된 객체를 역직렬화(Deserialization) 과정에 주입하면, 시스템은 공격자가 의도한 코드를 실행하게 되어 원격 코드 실행(RCE)에 이를 수 있습니다.

이 가이드는 Java 기반 시스템에서 발생 가능한 직렬화 취약점을 이해하고, 가장 강력하고 실질적인 방어 전략을 제시합니다.


🛡️ 1. 직렬화 취약점 이해하기 (The Threat Model)

취약점의 원리:
Java 직렬화는 객체의 상태(데이터)를 파일이나 네트워크를 통해 전송 가능한 바이트 스트림으로 변환하는 과정입니다. 이 과정에서 신뢰할 수 없는 출처의 데이터를 받아 역직렬화하는 순간, 공격자는 이 과정을 악용하여 임의의 코드를 실행시킬 수 있습니다.

핵심 위험 요소:

  • 신뢰 경계 침범: 외부 입력(HTTP 요청 파라미터, 파일 업로드 등)을 곧바로 객체로 변환(Deserialization)하는 모든 지점.
  • 사용 라이브러리: java.io.ObjectInputStream 사용 시 가장 취약합니다.

🧱 2. 다층적 방어 전략 (Layered Defense Strategy)

직렬화 취약점은 단일한 패치로 해결되지 않으며, 개발 라이프사이클 전반에 걸친 다층적 방어가 필수적입니다.

🟢 1단계: 설계 단계 (Prevention by Design)

가장 먼저 취약한 코드를 작성하지 않는 것이 최선의 방어입니다.

  • 직렬화 회피: 가능하다면, 객체 자체를 직렬화하지 않고, JSON, XML과 같은 데이터 교환 포맷을 사용하세요. 이 포맷들은 데이터 구조만 전송할 뿐, 객체의 로직(메서드 호출)을 포함하지 않아 RCE 위험이 없습니다.
  • 필요 최소한의 직렬화: 만약 직렬화가 필수적이라면, 데이터 전송에 필요한 필드만 포함하는 DTO(Data Transfer Object)를 만들고, 이 DTO만 직렬화하도록 범위를 제한해야 합니다.

🟡 2단계: 코드 구현 단계 (Runtime Mitigation)

직렬화가 불가피할 때 적용해야 하는 기술적 조치입니다.

  • 신뢰할 수 있는 출처만 사용: 직렬화된 객체는 반드시 애플리케이션 내부에서 생성되어 신뢰도가 검증된 경로를 통해서만 사용해야 합니다. 외부 사용자 입력은 절대 원본 객체로 역직렬화해서는 안 됩니다.
  • 객체 제한 (Whitelisting): 역직렬화 시 허용할 클래스 목록(Whitelist)을 명시적으로 정의하고, 이 목록에 없는 클래스는 로딩을 거부해야 합니다.

🔴 3단계: 인프라 및 라이브러리 관리 (Hardening)

사용하는 환경 자체를 강화하는 방법입니다.

  • Java 버전 관리: 최신 버전의 Java 런타임 환경(JRE)을 사용하고, 알려진 취약점이 패치된 버전을 유지합니다.
  • 보안 라이브러리 사용: Jackson이나 Gson과 같이 강력한 보안 기능을 내장한 최신 라이브러리를 사용하여 직렬화 과정을 감싸야 합니다.

🛠️ 3. 실질적인 구현 방어 가이드 (Actionable Steps)

다음은 가장 효과적이라고 입증된 세 가지 구체적인 방어 기법입니다.

1. 직렬화 금지 (The Golden Rule)

가장 강력한 해결책입니다.
데이터 교환 시 직렬화 대신 JSON 라이브러리 (Jackson, Gson)를 사용하고, ObjectMapper 등의 라이브러리에서 enableDefaultTyping()과 같은 자동 타입 감지 기능은 절대 활성화하지 마십시오.

2. 클래스 화이트리스팅 (Whitelisting)

Java의 표준 기능으로는 완벽한 방어가 어렵기 때문에, 서드파티 라이브러리나 커스텀 로직을 통해 허용할 클래스를 제한해야 합니다.

💡 구현 시 고려 사항:
역직렬화가 발생할 때, readObject()와 같은 메소드 오버라이딩을 통해 실행될 클래스 이름(Class Name)을 검증하는 로직을 삽입합니다.

// Pseudocode: 커스텀 ObjectInputStream 사용 예시
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 (isValidClass(className)) {
            return super.resolveClass(desc);
        } else {
            throw new InvalidClassException("Forbidden class detected: " + className);
        }
    }
}

3. 시리얼라이제이션 필터 (Java 9+ 권장)

Java 9 이상 버전부터는 Java Serialization Filter API가 도입되어 직렬화의 위험을 시스템 레벨에서 제어할 수 있습니다.

이 필터를 사용하여 특정 패키지나 클래스의 역직렬화를 사전에 차단할 수 있습니다. 이는 애플리케이션 코드 레벨의 수정 없이 환경 설정만으로 방어할 수 있다는 큰 장점이 있습니다.


📝 요약 체크리스트 (Security Checklist)

| 항목 | 적용 여부 | 비고 |
| :— | :— | :— |
| 데이터 교환 시 | ✅ | 직렬화 대신 JSON/XML 사용을 기본 원칙으로 한다. |
| 직렬화 사용 시 | ⚠️ | 반드시 DTO만 직렬화하고, 핵심 로직을 포함하는 객체는 제외한다. |
| 역직렬화 경로 | ✅ | 외부 사용자 입력(HTTP Body 등)을 원본 객체로 바로 사용하지 않는다. |
| 클래스 검증 | ✅ | ObjectInputStream을 오버라이딩하여 허용 클래스 목록을 강제한다. |
| 환경 업데이트 | ✅ | Java 버전 및 모든 라이브러리를 최신 보안 패치 버전으로 유지한다. |

댓글 남기기