Skip to content

[Item90] 직렬화된 인스턴스 대신 직렬화 프록시 사용을 검토하라 #148

Description

@imsando

## 목표

  • 직렬화 프록시 패턴을 활용하여 직렬화의 보안 문제를 방지하는 방법을 알아보자.

## 핵심 요약

직렬화(Serialization)란?

  • 객체의 상태를 바이트 스트림으로 변환하여 저장하거나 네트워크를 통해 전송할 수 있도록 하는 과정이다.
  • Serializable 인터페이스를 구현하여 객체를 직렬화할 수 있다.

직렬화의 문제점

  • 1. 생성자 이외의 방식으로 인스턴스를 생성할 수 있음
    • 직렬화된 데이터를 역직렬화하면 생성자를 거치지 않고 객체가 만들어진다.
    • 이로 인해 불변식(invariant)이 깨질 위험이 있다.
  • 2. 보안 문제 발생 가능
    • 공격자가 조작된 직렬화 데이터를 이용하여 내부 필드를 탈취할 수 있다.
    • 가짜 바이트 스트림 공격을 통해 시스템을 위협할 수도 있다.


1. 직렬화 프록시 패턴(Serialization Proxy Pattern)

  • 직렬화 시, 기존 클래스 대신 프록시 객체를 사용하여 직렬화하는 기법
  • 프록시 객체를 통해 역직렬화 후, 정상적인 생성자를 거쳐 인스턴스를 복원

1️⃣ 직렬화 프록시 설계 방법

  • 바깥 클래스의 논리적 상태를 저장할 private static 중첩 클래스를 생성 한다.
  • 이 중첩 클래스가 바깥 클래스의 직렬화 프록시 역할을 한다.
  • 프록시는 Serializable을 구현하며, 바깥 클래스의 데이터를 복사하는 생성자를 가진다.
private static class SerializationProxy implements Serializable {  
    private final Date start;  
    private final Date end;  

    SerializationProxy(Period p) {  
        this.start = p.start;  
        this.end = p.end;  
    }  

    private static final long serialVersionUID = 234098243823485285L;  
}  

2️⃣ writeReplace 메서드 추가

  • 직렬화 시 프록시 객체를 대신 반환하도록 한다.
  • 이로 인해 바깥 클래스의 직렬화된 인스턴스를 직접 생성할 수 없게 된다.
private Object writeReplace() {  
    return new SerializationProxy(this);  
}  

3️⃣ readObject 메서드를 방어적으로 추가

  • 바깥 클래스에서 역직렬화 공격을 차단하기 위해 사용한다.
  • 프록시가 없으면 역직렬화를 막는다.
private void readObject(ObjectInputStream stream) throws InvalidObjectException {  
    throw new InvalidObjectException("프록시가 필요합니다.");  
}  

4️⃣ readResolve 메서드 추가

  • 역직렬화 시, 프록시 객체를 원래 객체로 변환한다.
private Object readResolve() {  
    return new Period(start, end); // 생성자를 사용하여 복원  
}  

2. 직렬화 프록시 패턴의 장단점

장점

  • 불변성 보장
  • 가짜 바이트 스트림 공격 방어
  • 내부 필드 탈취 공격 방어
  • 프록시 객체만 저장하므로, 불필요한 필드 노출을 방지
  • 클래스 변경에도 유연

단점 또는 한계

  • 확장 가능한 클래스에는 적용할 수 없음
  • 순환 참조가 있는 객체에는 적용할 수 없음
  • 성능 저하 가능성


Topics

writeReplace vs readResolve

1️⃣ 전반적인 비교

메서드 명 목적 실행 시점 사용 예시
writeReplace 직렬화 시 프록시 객체가 반환됨 객체가 직렬화되기 전 직렬화 프록시 패턴 사용 시
readResolve 역직렬화 시 기존 인스턴스로 대체됨 역직렬화 후 객체 생성 전 싱글턴 패턴에서 사용

2️⃣ writeReplace vs readResolve 예제 비교

(1) writeReplace 사용 예제
private Object writeReplace() {  
    return new SerializationProxy(this);  
}  
  • 직렬화 과정에서 원본 객체 대신 SerializationProxy 객체가 저장된다.
(2) readResolve 사용 예제
private Object readResolve() {  
    return INSTANCE;  
}  
  • 역직렬화 시 새로운 객체가 아닌 기존의 INSTANCE를 반환한다.
  • 하지만 임시 객체가 생성된 후 GC에 의해 제거되는 단점이 있다.


핵심 정리

  • 제3자가 확장할 수 없는 클래스라면 직렬화 프록시 패턴을 적극적으로 사용하자.
  • 불변성을 유지하면서도 보안 위협을 방지하는 가장 안전한 직렬화 방법이다.

Metadata

Metadata

Assignees

Labels

No labels
No labels

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions