[이펙티브자바] item85. 자바 직렬화의 대안을 찾아라

wally·2022년 11월 14일
0

1997년 처음 직렬화가 도입되었으나 추후 어마어마하게 문제가 많다는 것이 밝혀졌다.
결론 부터 말하자면

  • 직렬화는 위험하니 피해야 한다
  • 시트템을 밑바닥부터 설계한다면 JSON 이나 프로토콜 버퍼 같은 대안을 사용하라
  • 신뢰할 수 없는 데이터는 역직렬화하지 말라
  • 꼭 해야 한다면 객체 역직렬화 필터링을 사용하되, 이마저도 모든 공격을 막아줄 수 없음을 기억하라
  • 클래스가 직렬화를 지원하도록 만들지 말고, 꼭 그렇게 만들어야 한다면 정말 신경 써서 작성해야 한다.

1. 자바 직렬화 역직렬화

직렬화 역직렬화란

자바 시스템 내부에서 사용되는 객체 또는 데이터를 외부의 자바 시스템에서도 사용할 수 있도록 바이트(byte) 형태로 데이터 변환하는 기술

각자 PC의 OS마다 서로 다른 가상 메모리 주소 공간을 갖기 때문에, Reference Type의 데이터들은 인스턴스를 전달 할 수 없다.

따라서, 이런 문제를 해결하기 위해선 주소값이 아닌 Byte 형태로 직렬화된 객체 데이터를 전달해야 한다.

직렬화된 데이터들은 모두 Primitive Type(기본형)이 되고, 이는 파일 저장이나 네트워크 전송 시 파싱이 가능한 유의미한 데이터가 된다. 따라서, 전송 및 저장이 가능한 데이터로 만들어주는 것이 바로 '직렬화(Serialization)'이라고 말할 수 있다.

직렬화 조건

자바에서는 간단히 java.io.Serializable 인터페이스 구현으로 직렬화/역직렬화가 가능하다.

역직렬화는 직렬화된 데이터를 받는쪽에서 다시 객체 데이터로 변환하기 위한 작업을 말한다.

직렬화 대상 : 인터페이스 상속 받은 객체, Primitive 타입의 데이터

Primitive 타입이 아닌 Reference 타입처럼 주소값을 지닌 객체들은 바이트로 변환하기 위해 Serializable 인터페이스를 구현해야 한다.

우아한 형제들 - 자바 직렬화 실무 적용

2. 보안적 문제

직렬화는 프로그래머가 어렵지 않게 분산 객체를 만들 수 있다는 구호는 매력적이지만, 보이지 않는 생성자, API 와 구현 상의 모호해진 경계, 잠재적인 정확성 문제, 성능, 보안, 유지보수성 등 대가가 크다

직렬화의 문제

  1. 공격범위가 너무 넓고 지속적으로 더 넓어져 방어하기 어렵다

직렬화를 하고 나서 역직렬화를 할 때 문제가 됩니다.

  • 객체를 읽는 readObject 메서드는 클래스 패스에 존재하는 거의 모든 타입의 객체를 만들어낼 수 있습니다.
    • 반환 타입이 Object입니다
  • 바이트 스트림을 역직렬화하는 과정에서 해당 타입 안의 모든 코드를 수행할 수 있습니다.
    • 객체를 아예 불러올 수 있으므로 모든 코드를 수행할 수 있습니다.
  • 그렇기에 타입 전체가 전부 공격 범위에 들어가게 됩니다.
  1. 자바의 표준 라이브러리나 아파치 커먼즈 컬렉션 같은 서드파티 라이브러리는 물론 애플리케이션 자신의 클래스들도 공격 범위에 포함된다.

  2. 역직렬화 과정에서 호출되어 잠재적으로 위험한 동작을 수행하는 메서드들을 가젯(gadget) 이라고 부르는데, 가젝 체인으로 보안공격이 가능하다.

  3. 역직렬화에 시간이 오래 걸리는 짧은 스트림을 역직렬화하는 것만으로도 서비스 거부 공격에 쉽게 노출될 수 있다. 이런 스트림을 역직렬화 폭탄(descerialization bomb) 라고 부른다.

static byte[] bomb() {
    Set<Object> root = new HashSet<>();
    Set<Object> s1 = root;
    Set<Object> s2 = new HashSet<>();
    for (int i = 0; i < 100; i++) {
        Set<Object> t1 = new HashSet<>();
        Set<Object> t2 = new HashSet<>();
        t1.add("foo");
        s1.add(t1);
        s1.add(t2);
        s2.add(t1);
        s2.add(t2);
        s1 = t1;
        s2 = t2;
    }
    return serialize(root); // 직렬화 수행
}

serialize 메서드가 수행되기 전의 인스턴스의 참조 형태를 보면 아래와 같은 형태다. 이 깊이가 100단계까지 만들어진다. 즉, 이를 역직렬화하려면 hashCode 메서드를 2^100 번 넘게 호출해야 한다.

3. 회피 방법

  • 아무것도 직렬화 하지 않는 것
  • 크로스0플랫폼 구조화된 데이터 표현(cross-platform structured-data representation)을 활용
    • json : key-value 형식의 텍스트 형태
    • Protocol Buffers : 이진 형태로 스키마(타입)도 제공
  • 신뢰할 수 없는 데이터는 절대 역직렬화하지 않는다
  • 객체 역직렬화 필터링(java.io.ObjectInputFilter) 를 활용
    • 역직렬화 이전에 필터를 설치하는 기능
    • '기본 수용' 모드 : 블랙리스트 클래스들 거부
    • '기본 거부' 모드 : 화이트리스트 클래스들만 수요(권장)
profile
클린코드 지향

0개의 댓글