Item 85. Java 직렬화의 대안을 찾으라

직렬화, 역직렬화

  • 직렬화 : Java가 객체를 byte stream으로 인코딩
  • 역직렬화 : byte stream을 객체로 encoding

역직렬화와 보안간의 관련성

  • 직렬화의 근본적인 문제는 공격범위가 넓다는 점이다. ObjectInputStream의 readObject method를 호출하면 객체 그래프가 역직렬화되기 때문이다.
    클래스패스 안의 Serilizable interface를 구현한 거의 모든 타입의 객체를 만들어낼 수 있기 떄문에, 바이트 스트림을 역직렬화하는 과정에서 이 method는 그 타입들 안의 모든 코드를 수행할 수 있다.

  • -> 그 타입들의 코드 전체가 공격범위에 들어간다.

  • gadget method : 객체 역직렬화 과정에서 호출되어 잠재적으로 위험한 동작을 수행하는 method

따라서 위험요소가 없는 byte stream만 역직렬화해야 한다.

그렇다하더라도 역직렬화에 시간이 오래걸리는 짧은 stream을 역직렬화 하는 것만으로도 서비스 거부 공격(denial-of-service,DoS)을 할 수 있다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
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);
}

위 코드는 201개의 HashSet 객체를 만드는데, 각각은 3개 이하의 객체 참조를 가진다. HashSet을 역직렬화하려면 각각 원소의 해시코드를 계산해야 하는데,
HashSet에 포함된 객체 참조 타입이 역시 HashSet임으로 depth가 100단계까지 만들어진다. 즉 2^100 이상으로 HashCode method를 호출해야 한다.

역직렬화의 보안성 이슈를 피하는 방법

  • 저자는 역직렬화 자체를 하지 않을 것을 권고하고 있다. 직렬화 대신에 객체를 byte sequence로 바꿔주는 mechanism (cross-platform structured-data representation)을 사용하자
  • 만약 직렬화를 피할수 없는 상황이고, 역직렬화한 데이터가 안전한지 완전히 확신할 수 없다면, 객체 역직렬화 필터링 (java.io.ObjectInputFilter)을 사용하자
    이 기능은 data stream이 역직렬화되기 전에 필터를 설치하는 기능이다. 클래스 단위로 특정 클래스를 받아들어거나 거부할 수 있는데, 저자는 블랙 리스트 방식보다는 안전하다고 알려진 클래스만 허용하는 화이트 리스트 방식을 사용할 것을 권고하고 있다.

Cross-platform structured-data representation

  • 객체를 byte sequence 로 바꿔주는 메카니즘
  • 객체 그래프는 직렬화 / 역직렬화하지 않고, 간단한 K-V형태의 객체를 사용한다.
  • 대표적인 예시로 JSON과 protocol buffer가 있다.
  • JSON은 텍스트 기반이라 사람이 읽을 수 있고, Protocol buffer는 이진 표현이라 효율이 높다.

Comments