직렬화의 위험성
직렬화는 공격 범위가 너무 넓고, 지속적으로 넓어져 방어하기 어렵다. ObjectInputStream의 readObject 메서드를 호출하면서 객체 그래프가 역직렬화되기 때문이다. readObject 메서드는 (Serializable 인터페이스를 구현했다면) 클래스패스 안의 거의 모든 타입의 객체를 만들어낼 수 있다. 바이트 스트림을 역직렬화하는 과정에서 이 메서드는 그 타입들 안의 모든 코드를 수행할 수 있다. 이는 즉, 그 타입들의 코드 전체가 공격 범위에 들어간다는 뜻이다.
자바의 역직렬화는 명백하고 현존하는 위험이다. 신뢰할 수 없는 스트림을 역직렬화하면 원격 코드 실행, 서비스 거부 등의 공격으로 이어질 수 있다.
역직렬화 폭탄
역직렬화에 시간이 오래 걸리는 짧은 스트림을 역직렬화하는 것만으로도 서비스 거부 공격에 쉽게 노출될 수 있다. 이런 스트림을 역직렬화 폭탄(deserialization bomb)라고 한다.
Test.java
public class Test {
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"); // t1을 t2과 다르게 만든다.
s1.add(t1); s1.add(t2);
s2.add(t1); s2.add(t2);
s1 = t1; s2 = t2;
}
return serialize(root);
}
}
위 Test 예제의 문제는 HastSet 인스턴스를 역직렬화하려면 그 원소들이 해시코드를 계산해야한다는 점이다. 루트 HashSet에 담긴 두 원소는 다른 HashSet 2개씩 원소를 갖는 HashSet이다. 반복문에 의해 이 구조가 깊이 100단계까지 만들어진다. 따라서 이 HashSet을 역직렬화하려면 hashCode 메서드를 2의 100제곱번 넘게 호출해야한다. 역직렬화가 영원히 계속된다는 것도 문제지만, 무언가 잘못되었다는 신호 조차 주지 않는다는게 큰 문제다.
역직렬화를 하지 말자
직렬화 위험을 회피하는 가장 좋은 방법은 역직렬화를 하지 않는 것이다. 우리가 작성하는 새로운 시스템에서 자바 직렬화를 써야 할 이유는 전혀 없다. 객체와 바이트 시퀀스를 변환해주는 다른 메커니즘이 많이 있다.
크로스-플랫폼 구조화된 데이터 표현(cross-platform structured-data representation)
자바 직렬화보다 훨씬 간단하다. 임의 객체 그래프를 자동으로 직렬화/역직렬화 하지 않는다. 대신 속성-값 쌍의 집합으로 구성된 간단하고 구조화된 데이터 객체를 사용한다. 그리고 기본 타입 몇개와 배열 타입만 지원할 뿐이다. 이렇게 간단한 추상화만으로도 아주 강력한 분산 시스템을 구축하기 에 충분하고, 자바 직렬화가 가져온 심각한 문제들을 회피할 수 있다.
JSON, 프로토콜 버퍼가 있다. JSON은 텍스트 기반이라 사람이 읽을 수 있고, 프로토콜 버퍼는 이진 표현이라 효율이 훨씬 높다.
객체 역직렬화 필터링(java.io.ObjectInputFilter)
신뢰할 수 없는 데이터는 절대 역직렬화하지 않는 것이다. 신뢰할 수 없는 데이터의 역직렬화는 본질적으로 위험하므로 절대로 피해야한다. 직렬화를 피할 수 없고 역직렬화한 데이터가 안전한지 완전히 확신할 수 없다면 객체 역직렬화 필터링(java.io.ObjectInputFilter)을 사용하자. 객체 역직렬화 필터링은 데이터 스트림이 역직렬화되기 전에 필터를 설치하는 기능이다. 클래스 단위로, 특정 클래스를 받아들이거나 거부할 수 있다.
블랙 리스트 방식
블랙리스트에 기록된 잠재적으로 위험한 클래스들을 거부한다.
화이트리스트 방식
화이트리스트에 기록된 안전하다고 알려진 클래스들만 수용한다.
스왓(SWAT)이라는 도구로, 애플리케이션을 위한 화이트리스트를 자동으로 생성할 수 있다.
블랙 리스트 방식보다 화이트 리스트 방식을 더 추천한다. 블랙리스트 방식은 이미 알려진 위험으로부터만 보호할 수 있기 때문이다.
'Book > Effective Java' 카테고리의 다른 글
[교재 EffectiveJava] 아이템 87. 커스텀 직렬화 형태를 고려해보라 (0) | 2022.08.20 |
---|---|
[교재 EffectiveJava] 아이템 86. Serializable을 구현할지는 신중히 결정하라 (0) | 2022.08.20 |
[교재 EffectiveJava] 아이템 84. 프로그램의 동작을 스레드 스케줄러에 기대지 말라 (0) | 2022.08.19 |
[교재 EffectiveJava] 아이템 83. 지연 초기화는 신중히 사용하라 (0) | 2022.08.19 |
[교재 EffectiveJava] 아이템 82. 스레드 안정성 수준을 문서화하라 (0) | 2022.08.18 |