직렬화의 근본적인 문제는 공격 범위가 너무 넓고 지속적으로 더 넓어져 방어하기 어렵다는 점이다. ObjectInputStream의 readObject 메소드를 호출하면서 객체 그래프가 역직렬화되기 때문이다. readObject 메소드는 Serializable 인터페이스를 구현했다면 클래스패스 안의 거의 모든 타입의 객체를 만들어낼 수 있는, 사실상 마법 같은 생성자다. 바이트 스트림을 역직렬화하는 과정에서 이 메소드는 그 타입들 안의 모든 코드를 수행할 수 있다. 이 말인즉슨, 그 타입들의 코드 전체가 공격 범위에 들어간다는 뜻이다.
자바의 표준 라이브러리나 아파치 커먼즈 컬렉션 같은 서드파티 라이브러리는 물론 애플리케이션 자신의 클래스들도 공격 범위에 포함된다. 관련된 모든 모범 사례를 따르고 모든 직렬화 가능 클래스들을 공격에 대비하도록 작성한다 해도, 여러분의 애플리케이션은 여전히 취약할 수 있다. 자바 라이브러리와 널리 쓰이는 서드파티 라이브러리에서 직렬화 가능 타입들을 연구하여 역직렬화 과정에서 호출되어 잠재적으로 위험한 동작을 수행하는 메소드들을 가젯(gadget)이라 부르고, 여러 가젯을 함께 사용하여 가젯 체인을 구성해 치명적인 공격이 만들어질 수도 있다. 그래서 아주 신중하게 제작한 바이트 스트림만 역직렬화해야 한다.
가젯까지 갈 것도 없이, 역직렬화에 시간이 오래 걸리는 짧은 스트림을 역직렬화하는 것만으로도 서비스 거부 공격에 쉽게 노출될 수 있다. 이런 스트림을 역직렬화 폭탄(deserialization bomb)이라고 한다.
직렬화 위험을 회피하는 가장 좋은 방법은 아무것도 역직렬화하지 않는 것이다. 마찬가지로 여러분이 작성하는 새로운 시스템에서 자바 직렬화를 써야 할 이유는 전혀 없다. 자바 직렬화의 여러 위험을 회피하면서 다양한 플랫폼 지원, 우수한 성능, 풍부한 지원 도구, 활발한 커뮤니티와 전문가 집단 등 수많은 이점까지 제공하는 객체와 바이트 시퀀스를 변환해주는 다른 매커니즘이 많이 있다. 이러한 매커니즘을 크로스-플랫폼 구조화된 데이터 표현(cross-platform structured-data representation)이라 한다.
크로스-플랫폼 구조화된 데이터 표현들의 공통점은 자바 직렬화보다 훨씬 간단하고, 임의 객체 그래프를 자동으로 직렬화/역직렬화하지 않는다. 대신 속성-값 쌍의 집합으로 구성된 간단하고 구조화된 데이터를 사용하며, 기본 타입 몇 개와 배열 타입만 지원할 뿐이다. 이런 간단한 추상화만으로도 아주 강력한 분산 시스템을 구축하기에 충분하고, 자바 직렬화가 가져온 심각한 문제들을 회피할 수 있다.
크로스-플랫폼 구조화된 데이터 표현의 선두주자는 JSON과 프로토콜 버퍼(Protocol Buffers, protobuf)다. JSON은 텍스트 기반이라 사람이 읽을 수 있고, 프로토콜 버퍼는 이진 표현이라 효율이 훨씬 높다. JSON은 오직 데이터를 표현하는 데만 쓰이지만, 프로토콜 버퍼는 문서를 위한 스키마(타입)를 제공하고 올바로 쓰도록 강요한다.
직렬화를 피할 수 없고 역직렬화한 데이터가 안전한지 완전히 확신할 수 없다면 객체 역직렬화 필터링(java.io.ObjectInputFilter)을 사용하자. 객체 역직렬화 필터링은 데이터 스트림이 역직렬화되기 전에 필터를 설치하는 기능이다. 클래스 단위로, 특정 클래스를 받아들이거나 거부할 수 있다.
'기본 수용' 모드에서는 블랙리스트에 기록된 잠재적으로 위험한 클래스들을 거부하고, '기본 거부' 모드에서는 화이트리스트에 기록된 안전하다고 알려진 클래스들만 수용한다. 블랙리스트 방식은 이미 알려진 위험으로부터만 보호할 수 있기 때문에, 블랙리스트 방식보다는 화이트리스트 방식을 추천한다. 여러분의 애플리케이션을 위한 화이트를 자동으로 생성해주는 스왓(SWAT, Serial Whitelist Application Trainer)이라는 도구도 있으니 참고하자.
직렬화는 위험하니 피해야 한다. 시스템을 밑바닥부터 설계한다면 JSON이나 프로토콜 버퍼 같은 대안을 사용하자. 신뢰할 수 없는 데이터는 역직렬화하지 말자. 꼭 해야 한다면 객체 역직렬화 필터링을 사용하되, 이마저도 모든 공격을 막아줄 수는 없음을 기억하자. 클래스가 직렬화를 지원하도록 만들지 말고, 꼭 그렇게 만들어야 한다면 정말 신경써서 작성해야 한다.