
아이템85. 자바 직렬화의 대안을 찾으라
https://www.yna.co.kr/view/AKR20161129029400091
샌프란시스코 경전철 '랜섬웨어' 공격으로 결제시스템 마비 | 연합뉴스
(샌프란시스코=연합뉴스) 김현재 특파원 = 미국 캘리포니아주 샌프란시스코의 경전철 운영시스템이 해커의 공격을 받아 지난 주말 결제시스템이 마비됐...
www.yna.co.kr
2016년 샌프란시스코 교통국이 렌섬웨어에 감염되어 열차 발권, 배차 시스템이 정지되었다.
이 문제의 원인은 바로 직렬화.
직렬화의 가장 근본적인 문제는 역직렬화에 있다. 역직렬화의 readObject 메서드는 그 객체가 어느 객체인지간에 Serializable의 구현체라면 다시 객체를 만들어낼 수 있다. 모종의 사유로 역직렬화 코드가 실행될 때 해커가 악성코드를 심어놨다면, 그 코드도 같이 실행되는 것이다.
문제는 자바의 기본 라이브러리에서도 역직렬화가 간접적으로 사용될 수 있다. 신뢰할 수 없는 스트림을 역직렬화하면 악성코드 및 보안문제에 취약해진다.
굳이 악성코드를 심지 않아도 위험하다
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);
}
return serialize(root); //serialize 코드는 생략함
}
위 코드에서의 HashSet 객체는 반복문에서 생성되어 s1, s2에 더해지는 t1 100개 t2 100개씩해서 200개, root와 s2 2개해서 202개의 HashSet 객체가 생성된다. (책에는 201개라 되어있는데 뭐가 맞는지는 잘 모르겠다.) 202개의 HashSet 인스턴스는 3개 이하의 객체참조를 가지게 된다.
위 코드를 직렬화한 다음 어딘가에서 불러와 역직렬화를한다고하면
맨처음 root HashSet을 역직렬화하려면 그 원소의 해시코드를 계산해야하는데, 루트 HashSet은 두 개의 HashSet을 인스턴스로 가지고 있고, 그 두 개의 HashSet은 새로운 루트 HashSet이 되어 또 2개의 HashSet을 가지고... 이렇게 반복문을 100번 실행했으므로 HashSet을 역직렬화하기 위해서는 HashCode (해시코드를 구하는 알고리즘)을 2^100번 실행해야할 것이다. 영원히
문제는 역직렬화가 잘못되어도 아무 알림도 오지 않고, 스택 오버플로우 에러도 일어날 것이다. 만약 악의를 품고 이 코드를 심어놨다면 그 코드를 실행한 서버는 다운될 것이고. 이렇게 역직렬화 시간이 오래걸리는 메서드를 여러 번 수행하도록 만드는 것을 역직렬화 폭탄이라고 한다.
그렇다면 이러한 역직렬화의 취약점을 보완할 수 있는 방법이 있을까? 신뢰할 수 없는 데이터를 역직렬화하는 것은 클라이언트의 의도에 100% 따르게된다.
가장 좋고 안전한 역직렬화 방법은 역직렬화를 하지 않는 것이다. 자바에서 직렬화를 사용하지 말아라. 역직렬화도 사용하지 말아라. 직렬화와 역직렬화가 주로 수행하는 객체 ↔ 바이트 시퀀스 간 컨버터 역할을 다른 방법으로도 가능하다. <이펙티브 자바>에서는 '크로-플랫폼 구조화된 데이터 표현 (cross-platform structured-data representation)'이라 칭한다. 사실 이것 역시 직렬화 시스템으로 불려도 무방하다.
이러한 표현들은 직렬화/역직렬화를 수행하지 않는다. 대신 key-value 형식으로 간단하게 구조화된 데이터 객체를 사용한다. 타입도 기본형 타입과 배열만을 지원한다. 즉, 직렬화/역직렬화에 비해 훨씬 간단하며 직렬화의 문제들을 회피할 수 있다.
key-value 형식으로 구조화되며 기본형 타입과 배열만을 지원하는 데이터 객체 중 가장 익숙하며 가장 유명한 것이 바로 JSON 이다. JSON은 JavaScript Object Notation (자바스크립트 객체 표기법) 으로 자바스크립트용도로 설계되었다. 자연어로 구성되어 사람이 읽기 매우 편하다.
또 하나의 예시는 프로토콜 버퍼다. C++ 용도로 만들어졌으며 이진표현을 제공하기 때문에 효율성이 높다. 프로토콜 버퍼는 문서를 위한 스키마(타입)을 제공하고 이를 준수하도록 한다.
그래도 만약 직렬화가 꼭 필요하다면 신뢰할 수 없는 데이터를 절대 역직렬화를 해서는 안된다는 것을 명심해야한다.
자바에서도 이를 인지하고 있어서 JDK9부터는 역직렬화 필터링을 제공한다. 클래스 단위로 특정 클래스를 받아들이거나 거부할 수 있으며, 화이트 리스트에 등재된 클래스만을 받도록 설정할 수도 있다. 다만 역직렬화 폭탄은 피할 수 없으므로 주의해야한다.
'언어공부 > Java | Kotlin' 카테고리의 다른 글
| [CS] 자바의 정석 독서 #16 - String 클래스 파헤치기 (0) | 2025.11.18 |
|---|---|
| [CS] 자바의 정석 독서 #14 - 예외처리 (0) | 2025.11.11 |
| [CS] 스레드란 무엇일까? (0) | 2025.10.15 |
| [CS] 이펙티브자바 독서 #2 - 아이템18 (0) | 2025.10.14 |
| [CS] 이펙티브자바 독서 #1 - 아이템15 ~ 17 (0) | 2025.10.11 |