반응형
이 포스팅에서 작성하는 내용은 EFFECTIVE JAVA(이펙티브자바) 에서 발췌하였습니다.
아이템 85. 자바 직렬화의 대안을 찾으라
직렬화
- 객체 직렬화 : 자바가 객체를 바이트 스트림으로 인코딩(직렬화)하고, 그 바이트 스트림으로부터 다시 객체를 재구성(역직렬화)하는 매커니즘이다.
- 직렬화된 객체는 다른 VM에 전송하거나 디스크에 저장한 후 필요할 때 역직렬화하여 사용할 수 있다.
자바의 직렬화
- 자바의 직렬화는 위험하다. (보이지 않는 생성자, API와 구현 사이의 모호해진 경계, 잠재적인 정확성 문제, 성능, 보안, 유지보수성 등)
- 직렬화의 근본적인 문제는 공격 범위가 너무 넓고 지속적으로 더욱 넓어져 방어하기 어렵다는 것이다.
- ObjectInputStream의 readObject 메서드를 호출하면서 객체 그래프가 역직렬화 되기 때문이다.
- readObject 메서드: Serializable 인터페이스를 구현했다면 클래스패스 안의 거의 모든 타입의 객체를 만들어 낼 수 있는 생성자
- 바이트 스트림을 역직렬화하는 과정에서 이 메서드는 그 타입들 안의 모든 코드를 수행할 수 있다. 즉, 그 타입들의 코드 전체가 공격 범위에 들어간다는 뜻
가젯(gadget)
- 가젯 : 자바 라이브러리와 널리 쓰이는 서드파티 라이브러리에서 역직렬화 과정에서 호출되어 잠재적으로 위험한 동작을 수행하는 메서드
- 여러 가젯을 함께 사용하여 가젯 체인을 구성할 수도 있는데, 공격자가 기반 하드웨어의 네이티브 코드를 마음대로 실행할 수 있는 강력한 가젯 체인도 존재한다.
- 이러한 이유로 아주 신중하게 제작한 바이트 스트림만 역직렬화해야 한다.
ex 1) 역직렬화 폭탄의 예시
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);
}
- 역직렬화 폭탄 : 역직렬화에 시간이 오래 걸리는 짧은 스트림
- 이러한 역직렬화 폭탄을 역직렬화하는 것만으로도 서비스 거부 공격에 쉽게 노출될 수 있다.
- 이 객체 그래프는201개의 HashSet 인스턴스로 구성되며, 각각 3개 이하의 객체 참조를 갖는다.
- 스트림의 전체 크기는 5744바이트지만, 역직렬화는 태양이 식을 때까지도 끝나지 않는다.
- 이유는 HashSet 인스턴스를 역직렬화하려면 그 원소들의 해시코드를 계산해야 하는 것이다.
- 루트 HashSet에 담긴 두 원소는 다른 HashSet 2개씩을 원소로 갖는 HashSet이며, 반복문에 의해 구조가 깊이 100까지 만들어진다.
- 따라서 HashSet을 역직렬화하려면 hashCode 메서드를 2^100 번 넘게 호출해야 한다.
- 직렬화 위험을 회피하는 가장 좋은 방법은 아무것도 역직렬화하지 않는 것이다.
- 마찬가지로, 작성하는 새로운 시스템에서 자바 직렬화를 써야할 이유는 거의 없다.
크로스-플랫폼 구조화된 데이터 표현
- 자바 직렬화보다 훨씬 간단하다.
- 임의 객체 그래프를 자동으로 직렬화/역직렬화하지 않고, 속성-값 쌍의 집합으로 구성된 간단하고 구조화된 데이터 객체를 사용한다.
- 기본 타입 몇 개와 배열 타입만 지원한다.
- 이러한 간단한 추상화만으로 강력한 분산 시스템을 구축하기에 충분하고, 자바 직렬화의 문제를 회피할 수 있다.
- 대표적인 예로는 JSON과 프로토콜 버퍼이다.
- JSON
- 브라우저와 서버의 통신용으로 설계됐으며, 자바스크립트용으로 만들어졌다.
- 텍스트 기반이라 사람이 읽을 수 있다.
- 오직 데이터를 표현하는 데만 쓰인다.
- 프로토콜 버퍼
- 구글이 서버 사이에 데이터를 교환하고 저장하기 위해 설계됐으며, C++용으로 만들어졌다.
- 이진 표현이라 효율이 훨씬 높다
- 문서를 위한 스키마(타입)를 제공하고 올바로 쓰도록 강요한다.
- 이진 표현 뿐만 아니라 사람이 읽을 수 있는 텍스트 표현도 지원한다.
정리
- 직렬화는 위험하니 피하자
- 직렬화 대신 JSON, 프로토콜 버퍼와 같은 대안을 사용하자
- 역직렬화 자체를 하지 않는 것이 좋고, 필요하다면 신뢰할 수 없는 데이터는 역직렬화하지 말자 (블랙리스트 방식보다는 화이트리스트 방식을 추천)
- 꼭 해야한다면 객체 역직렬화 필터링을 사용하되, 이마저도 모든 공격을 막아줄 수 없다는 것을 기억하자.
- 클래스가 직렬화를 지원하도록 만들지 말고, 꼭 필요하다면 많이 신경 써서 작성해야 한다.
반응형
'BE > Java' 카테고리의 다른 글
[Effective Java] 아이템 87. 커스텀 직렬화 형태를 고려해보라 (1) | 2023.01.02 |
---|---|
[Effective Java] 아이템 86. Serializable을 구현할지는 신중히 결정하라 (0) | 2022.12.31 |
[Effective Java] 아이템 84. 프로그램의 동작을 스레드 스케줄러에 기대지 말라 (1) | 2022.12.27 |
[Effective Java] 아이템 83. 지연 초기화는 신중히 사용하라 (0) | 2022.12.26 |
[Effective Java] 아이템 82. 스레드 안정성 수준을 문서화하라 (0) | 2022.12.23 |