item85. 자바 직렬화의 대안을 찾으라
아이템 85 자바 직렬화의 대안을 찾으라
⚓️서론
이번에 처음 직렬화 부분을 들어가는 아이템 85를 맡았다.
이 책에서는 직렬화에 대해서 기본적으로 습득한 느낌으로 들어가고 있다.
직렬화를 조금 정리하고 들어가 보겠습니다.
➡️직렬화란 무엇인가?
- 자바 시스템 내부에서 사용되는 객체 또는 데이터를 외부의 시스템에서 사용할 수 있도록 바이트스트림으로 상호 변환하는 기술
- 역직렬화 : 바이트로 변환된 데이터를 다시 객체로 변환하는 기술
위의 두 개를 합쳐서 직렬화라고 합니다.
➡️ 배경
- 1977년 자바에 처음으로 직렬화가 도입되었다.
- 직렬화는 연구용 언어인 모듈라-3(Modula-3)에서만 시도되었다. 대중적 언어에 적용되는 건 위험하다고 판단되었다.
- 장점으로는 프로그래머가 어렵지 않게 분산 객체를 만들 수 있다는 점이였지만, 단점이 너무 많았다. 단점으로는
보이지 않는 생성자
,API와 구현 사이의 모호해진 경계
,잠재적인 정확성 문제
,성능
,보안
,유지보수성
너무 많은 단점을 지닌다.- 대표적인 보안문제 : 2016년 11월 샌프란시스코 시영 교통국 랜섬웨어 공격
➡️ 직렬화의 문제
- 직렬화의 근본적인 문제는 공격 범위가 너무 넓고 지속적으로 더 넓어져 방어하기 어렵다는 점이다.
✅ObjectInputStream
- 자바에서 객체를 역직렬화(deserialize)하기 위한 클래스이다. 객체를 직렬화(serialize)하면 해당 객체를 바이트 스트림으로 변환하여 파일에 저장하거나 네트워크를 통해 전송할 수 있다.
readObject()
메소드를 통해서 객체를 역직렬화한다.- 바이트 스트림을 역직렬화하는 과정에서 이 메소드는 그 타입들 안의 모든 코드를 수행할 수 있다. 이 말의 의미는 그 타입들의 코드 전체가 공격 범위에 들어간다는 뜻이다.
✅ 공격 범위
- 자바의 표준 라이브러리나 아파치 커먼즈 컬렉션 같은 서드파티 라이브러리는 물론 애플리케이션 자신의 클래스들도 공격 범위에 포함된다.
- 관련한 모든 모범 사례를 따르고 모든 직렬화 가능 클래스들을 공격에 대비하여도 애플리케이션은 여전히 취약할 수 있다.
✅ 가젯(gadget)
- 공격자와 보안 전문가들은 자바 라이브러리와 널리 쓰이는 서브파티 라이브러리에서 직렬화 기능 타입들을 연구하여 역직렬화 과정에서 호출되어 잠재적으로 위험한 동작을 수행하는 메소드들을 찾아보았다. 이런 메서드를
가젯(gadget)
이라고 한다. - 간략히 말해서, 역직렬화 과정에서 호출되어 잠재적으로 위험한 동작을 수행하는 메서드
✅ 역직렬화 폭탄(deserialization bomb)
역직렬화에 시간이 오래 걸리는 짧은 스트림을 역직렬화하는 것만으로도 서비스 거부 공격에 쉽게 노출될 수 있다. 이런 스트림을
역직렬화 폭탄
이라고한다.역직렬화 폭탄 예시 - HashSet과 문자열만 사용해서 만든 예시
import java.io.*;
public class Util {
public static byte[] serialize(Object o) {
ByteArrayOutputStream ba = new ByteArrayOutputStream();
try {
new ObjectOutputStream(ba).writeObject(o);
} catch (IOException e) {
throw new IllegalArgumentException(e);
}
return ba.toByteArray();
}
public static Object deserialize(byte[] bytes) {
try {
return new ObjectInputStream(
new ByteArrayInputStream(bytes)).readObject();
} catch (IOException | ClassNotFoundException e) {
throw new IllegalArgumentException(e);
}
}
}
import static main.Chapter26.Util.*;
import java.util.HashSet;
import java.util.Set;
// 코드 85-1 역직렬화 폭탄 - 이 스트림의 역직렬화는 영원히 계속된다. (451-452쪽)
public class DeserializationBomb {
public static void main(String[] args) throws Exception {
System.out.println(bomb().length); // 5,744
deserialize(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); // 이 메서드는 effectivejava.chapter12.Util 클래스에 정의되어 있다.
}
}
위 코드의 객체 그래프는 201개의 HashSet 인스턴스로 구성되며, 그 각각은 3개 이하의 객체 참조를 갖는다.
스트림의 전체 크기는 5,744바이트지만, 역직렬화는 정말 오랫동안 진행될 것이다.
- 문제는 HashSet 인스턴스를 역직렬화하려면 그 원소들의 해시코드를 계산해야 한다는데 있다. 아래는 위의 코드를 디버깅한 그림이다.
serialize
메서드가 수행되기 전의 인스턴스의 참조 형태를 보면 아래와 같은 형태이다.- 이 HashSet을 역직렬화하려면 hashCode 메서드를 2^100번 넘게 호출해야한다.
- 더욱 더 무서운 점은 무언가 잘못되었다는 신호조차 주지 않는다.
➡️해결책
- 애초에 신뢰할 수 없는 바이트 스트림을 역직렬화하는 일 자체가 스스로를 공격에 노출하는 행위다.
직렬화 위험을 회피하는 가장 좋은 방법은 아무것도 역직렬화하지 않는 것이다.
글쓴이는 위의 표현을 비유를 통해서 알려준다. 표현이 좋아서 남겨놓음
“승리하는 유일한 길은 전쟁하지 않는 것이다.”
✅크로스-플랫폼 구조화된 데이터 표현(Cross-platform structured-data representation
)
- JSON & 프로토콜 버퍼(Protocol Buffers 혹은 Protobuf)
JSON | Protocol Buffers | |
---|---|---|
설계 목적 | 브라우저와 서버의 통신용으로 설계 | 구글이 서버 사이에 데이터를 교환하고 저장하기 위해 설계 |
언어 | 자바스크립트용 | C++용 |
차이점 | 텍스트 기반→ 사람이 읽을 수 있음 | 이진 표현 → 효율이 훨씬 높다 , 텍스트 표현도 지원 |
오직 데이터를 표현하는 데만 사용 | 문서를 위한 스키마(타입)를 제공하고 올바르게 쓰도록 강요함 |
✅직렬화를 피할 수 없고 역직렬화한 데이터가 안전한지 완전히 확실할 수 없을때
- 객체 역직렬화 필터링(java.io.ObjectInputFilter)를 사용하자. → 자바 9에 추가가되었음. 이전 버전에서도 사용 가능
- 객체 역직렬화 필터링은 데이터 스트림이 역직렬화되기 전에 필터를 설치하는 기능
클래스 단위로, 특정 클래스를 받아들이거나 거부할 수 있음
- 기본 수용모드
블랙리스트 | 기록된 잠재적으로 위험한 클래스들을 거부 |
---|---|
화이트리스트 | 기록된 안전하고 알려진 클래들만 수용 |
보통 화이트리스트 방식을 추천한다.
- 화이트리스트를 자동 생성해주는 도구
- 스왓(SWAT,Serial Whitelist Application Trainer)
- 필터링 기능은 메모리를 과하게 사용하거나 객체 그래프가 너무 깊어지는 사태로부터 보호해준다.
- 하지만 직렬화 폭탄은 걸러내지 못함.
- 스왓(SWAT,Serial Whitelist Application Trainer)
➡️핵심정리
- 직렬화는 위험하니 피하자.
- 시스템을 처음부터 설계한다면 JSON, Protocol Buffers 같은 대안을 사용하자.
- 신뢰할 수 없는 데이터는 역직렬화하지 말자.
- 꼭 해야한다면, 객체 역직렬화 필터링을 사용하자. → 한계 : 모든 공격을 막아 줄 수는 없다.
참조
[자바 직렬화, 그것이 알고싶다. 훑어보기편 | 우아한형제들 기술블로그](https://techblog.woowahan.com/2550/) |