-
Notifications
You must be signed in to change notification settings - Fork 0
Open
Labels
Description
📌 [아이템 85] 자바 직렬화의 대안을 찾으라
✨ 핵심 내용
- 쓰지 마라
- 피해야 한다.
- JSON 이나 Protocol Buffers, 다른 거 써라.
- 꼭 써야하면 필터링을 사용하되 이 역시 모든 공격을 다 막아줄 수는 없다.
- 클래스가 직렬화를 지원하게 만들지 말고, 꼭 지원해야 한다면 정말 신경써서 작성해야한다.
💡 새롭게 알게 된 점
📚 정리
- 직렬화는 프로그래머가 어렵지 않게 분산 객체를 만들 수 있다는 구호는 매력적이었으나, 보이지 않는 생성자, API와 구현 사이의 모호해진 경계, 잠재적인 정확성 문제, 성능, 보안, 유지보수성 등 그 대가가 컸다.
- 지지자들은 장점이 위험성을 압도한다고 생각했지만, 지금까지 경험한 바로는 그 반대다
- 샌프란시스코 교통국이 랜섬웨어 공격을 받아 요금 징수 시스템이 마비되는 취약점도 있었다!
- 직렬화의 근본적인 문제는 공격 범위가 너무 넓고 지속적으로 더 넓어져 방어하기 어렵다.
- 자바 표준 라이브러리나 아파치 커먼즈 같은 서드파티 라이브러리는 물론 애플리케이션 자신의 클래스도 공격 범위에 포함된다.
- 모든 모범사례를 따르고 공격에 대비해도 여전히 취약할 수 있다.
자바의 역직렬화는 명백하고 현존하는 위험이다. 이 기술은 지금도 애플리케이션에서 직접 혹은, 자바 하부 시스템(RMI(Remote Method Invocation), JMX(Java Managemnet Extension), JMS(Java Messaging System) 등)을 통해 간접적으로 쓰이고 있기 때문이다. 신뢰할 수 없는 스트림을 역직렬화하면 원격 코드 실행(Remote Code Execution, REC), 서비스 거부(Denial-Of-Service, DoS) 등의 공격으로 이어질 수 있다. 잘못한 게 아무것도 없는 애플리케이션이라도 이런 공격에 취약해 질 수 있다.
-- CERT 조정센터 기술관리자 Robert Seacord
- 자바 라이브러리와 널리 쓰이는 서드파티 라이브러리에서 직렬화 가능 타입들을 연구해 역직렬화 과정에서 호출돼 잠재적으로 위험한 동작을 수행하는 매서드들을 찾아봤다. 이런 메서드를 gadget 이라 부르며 여러 gadget 을 모아 gadget-chain을 만들 수도 있다. 가끔 공격자가 기반 하드웨어의 네이티브 코드를 마음대로 실행할 수도 있는 강력한 가젯 체인도 발견된다. 그러므로 아주 신중하게 제작한 바이트 스트림만 역직렬화 해야한다.
- 가젯까지 갈 것도 없이 역직렬화에 시간이 오래 걸리는 짧은 스트림을 역 직렬화 하는 것으로도 DoS 공격에 쉽게 노출될 수 있다. 이런 스트림을 역직렬화 폭탄(deserialization bomb) 이라고 한다.
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); // 간결하게 하기 위해 이 메서드의 코드는 생략
}
// 아무튼 엄청난 계산을 유발하는 코드 452 페이지 참조- 신뢰할 수 없는 바이트 스트림을 역직렬화하는 일 자체가 스스로를 공격에 노출하는 행위다. 직렬화 위험을 회피하는 가장 좋은 방법은 아무것도 역직렬화하지 않는 것이다. 여러분이 작성하는 새로운 시스템에서 자바 직렬화를 써야 할 이유는 전혀 없다.
- 자바 직렬화와 구문하여 크로스-플랫폼 구조화된 데이터 표현(cross-platform structured-data representaion)이라고 하는 이 표현들의 선두주자는 JSON과 Protocol Buffers 이다. 이들은 언어 중립적이라고는 하지만 JSON은 Javascript 용으로, Protobuf는 C++용으로 만들어 져 있다.
- 둘의 가장 큰 차이는 JSON은 텍스트 기반이라 사람이 읽을 수 있고, protobuf는 이진표현이라 효율이 훨씬 높다.
- JSON은 데이터를 표현하는 데만 쓰이지만, protobuf는 문서를 위한 스키마(type)을 제공하고 올바로 쓰도록 강요한다. (String only / with type)
- 효율은 protobuf가 훨씬 좋지만, 텍스트 기반 표현에는 JSON이 아주 효과적이다. protobuf는 이진 표현뿐 아니라 사람이 읽을 수 있는 텍스트 표현(pbtxt)도 지원한다.
- 레거시 시스템 때문에 자바 역직렬화를 완전히 배제할 수 없을 때는 신뢰할 수 없는 데이터는 절대 역직렬화 하지 않아야 한다.
- 직렬화를 피할 수 없고 역직렬화한 데이터가 안전한지 완전히 확신할 수 없다면 객체 역직렬화 필터링(java.io.ObjectInputFilter)을 사용할 수 있다. 클래스 단위로 특정 클래스를 받아 들이거나 거부할 수 있다.
- '기본 수용' 모드에서는 블랙리스트에 기록된 잠재적으로 위험한 클래스들을 거부한다.
- '기본 거부' 모드에서는 화이트리스트에 기록된 안전하다고 알려진 클래스들만 수용한다.
- 블랙리스트 방식보다는 화이트리스트 방식을 추천한다.
- 여러분의 applicaton을 위한 화이트 리스트를 자동으로 생성해주는 SWAT(Serial Whitelist Applicationi Trainer)이라는도구도 있다.
- 필터링은 메모리를 과하게 사용하거나 객체 그래프가 너무 깊어지는 사태로부터도 보호해준다. 하지만 앞서 나온 직렬화 폭탄은 걸러내지 못한다.
- 직렬화는 안타깝게도 자바 생태계 곳곳에서 사용되고 있다. 이런 시스템을 관리해야 한다면, 시간과 노력을 들여서라도 크로스-플랫폼 구조화된 데이터 표현으로 마이그레이션하는 것을 심각하게 고민해보자.
📢 댓글로 각자의 학습 내용을 공유해주세요!