12장. 직렬화 - GOAL

객체 직렬화란 자바가 객체를 바이트 스트림으로 인코딩하고(직렬화)
그 바이트 스트림으로부터 다시 객체를 재구성하는(역직렬화) 메커니즘이다.
직렬화된 객체는 다른 VM에 전송하거나 디스크에 저장한 후 나중에 역직렬화할 수 있다.
직렬화가 품고 있는 위험과 그 위험을 최소화하는 방법에 대해 알아보자.

아이템85. 자바 직렬화의 대안을 찾으라

직렬화는 위험하다. 직렬화의 위험성을 회피하는 가장 좋은 방법은 아무것도 역직렬화하지 않는 것이다.

직렬화란 무엇일까

넓은 의미로 직렬화는 어떤 데이터를 다른 데이터의 형태로 변환하는 것을 말한다.
이팩티브 자바에서 말하는 직렬화(Serializable)란 바이트 스트림으로의 직렬화로 객체의 상태를 바이트 스트림으로 변환하는 것을 의미한다.
반대로 바이트 스트림에서 객체의 상태로 변환하는 건 역직렬화(Deserializable)라고 부른다.

직렬화를 사용하는 이유

일단 자바에서 표현된 객체를 목적지에 보낸다 할 때, 목적지에서 객체를 알 수 있는 방법이 없다. 그래서 모두 다 알 수 있는 형태로 변환을 해줘야 한다.
여기서 변환을 도와주는 방법이 직렬화이며 직렬화를 통해서 바이트 스트림으로 변환해줄 수 있다.

직렬화가 위험한 이유

직렬화의 근본적인 문제는 공격 범위가 너무 넓고 지속적으로 더 넓어져 방어하기 어렵다는 점이다.
직렬화를 하고 나서 역직렬화를 할 때 문제가 된다.


  • 객체를 읽는 readObject 메서드는 클래스 패스에 존재하는 거의 모든 타입의 객체를 만들어낼 수 있다.
    • 반환 타입이 Object이다.
  • 바이트 스트림을 역직렬화하는 과정에서 해당 타입 안의 모든 코드를 수행할 수 있다.
    • 객체를 아예 불러올 수 있으므로 모든 코드를 수행할 수 있게된다.
  • 그렇기에 타입 전체가 전부 공격 범위에 들어가게 된다.
  • 용량도 다른 포맷에 비해서 몇 배 이상의 크기를 가진다.
  • 또한 역직렬화 폭탄을 맞을 수도 있다.

바이트 스트림으로 직렬화하는데는 시간이 별로 걸리지 않지만 역직렬화를 잘못하면 안으로 들어가서 hashCode 메서드를 계속 호출해야하기때문에 시간이 엄청 오래걸린다.

역직렬화 폭탄

이 스트림의 역직렬화는 영원히 계속된다.
HashSet을 역직렬화하려면 hashCode 메서드를 2의 100승번 넘게 호출해야 한다.
영원히 역직렬화가 계속된다는 것도 문제지만, 무언가 잘못되었다는 신호조차 주지 않는다는 것도 큰 문제다.


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); // 직렬화 수행
}

그럼 어떻게 대처해야 할까

직렬화 위험을 회피하는 가장 좋은 방법은 아무것도 역직렬화하지 않는 것이다.
여러분이 작성하는 새로운 시스템에서 자바 직렬화를 써야 할 이유는 전혀 없다. (JSON, 프로토콜 버퍼 등 좋은 다른 매커니즘이 있다.)
직렬화를 피할 수 없고 역직렬화한 데이터가 안전한지 완전히 확신할 수 없다면 java 9에 나온 ObjectInputFilter를 사용하는 것도 방법이다. 이는 데이터 스트림이 역직렬화되기 전에 필터를 적용해서 특정 클래스를 받아들이거나 거부할 수 있다.

결론

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