JAVA/Effective Java

[아이템 85] 자바 직렬화의 대안을 찾으라

꾸준함. 2024. 5. 12. 14:06

자바 직렬화란?

  • 자바 객체를 바이트 스트림으로 변환하는 메커니즘
  • 객체를 파일에 저장하거나 네트워크를 통해 DB 혹은 메모리로 전송할 때 유용
  • 직렬화된 객체는 나중에 다시 역직렬화될 수 있어 객체의 상태를 보존하고 복원할 수 있음
  • 직렬화를 사용하기 위해서는 클래스가 Serializable 인터페이스 구현체여야 함
  • 직렬화된 객체는 ObjectOutputStream을 사용하여 생성된 출력 스트림으로 쓰이며 이를 통해 객체를 스트림에 쓰면 자동으로 필드 값들이 바이트로 변환되어 출력
  • 역직렬화는 직렬화된 객체를 읽어와서 자바 객체로 변환하는 것이며 이를 위해 ObjectInputStream을 사용
  • 직렬화를 통해 생성된 바이트 스트림은 플랫폼에 독립적이기 때문에 타 플랫폼에서도 역직렬화가 가능

 

자바 직렬화의 취약점

앞선 설명만 보면 자바 직렬화가 매력적인 기능 같아 보이지만 다음 문제점들이 존재합니다.

  • 보이지 않는 생성자
  • API와 구현 사이의 모호해진 경계
  • 잠재적인 정확성 문제
  • 성능
  • 보안
  • 유지보수성

 

직렬화의 근본적인 문제는 공격 범위가 너무 넓고 지속적으로 더 넓어져 방어하기 어렵다는 점입니다.

  • ObjectInputStream의 readObject 메서드가 객체 그래프를 역직렬화하면서 공격 범위가 넓어짐
    • readObject 메서드는 Serializable 인터페이스를 구현한 classpath 내 거의 모든 타입의 객체를 만들어낼 수 있는 생성자
    • 바이트 스트림을 역직렬화하면서 readObject 메서드가 타입 객체 내 모든 코드를 수행할 수 있어 코드 전체가 공격 범위가 됨
    • 자바 표준 라이브러리, Apache Commons Collections과 같은 써드 파티 라이브러리, 그리고 애플리케이션 자신의 클래스들로 공격 범위에 포함
    • 신뢰할 수 없는 스트림을 역직렬화할 경우 원격 코드 실행(RCE), 서비스 거부(DoS) 등의 공격으로 이어질 수 있음
    • ex) 2016년 11월 샌프란시스코 서영 교통국 랜섬웨어 공격

 

앞서 열거했듯이 자바의 역직렬화는 여러 보안 문제를 야기할 수 있기 때문에, 신중하게 제작된 바이트 스트림만을 역직렬화하는 것을 권장합니다.

  • 안타깝게도 이미 널리 쓰이고 있는 라이브러리들에서도 공격자가 기반 하드웨어의 native 코드를 마음대로 실행할 수 있는 아주 강력한 가젯 체인이 발견되기도 함
 

Java-Deserialization-Cheat-Sheet/README.md at master · GrrrDog/Java-Deserialization-Cheat-Sheet

The cheat sheet about Java Deserialization vulnerabilities - GrrrDog/Java-Deserialization-Cheat-Sheet

github.com

 

  • 또한, 역직렬화에 시간이 오래 걸리는 짧은 스트림을 역직렬화하는 것만으로도 DoS 공격에 쉽게 노출 가능
    • ex) Deserialization Bomb

 

Deserialization Bomb 예시

 

 

위 코드의 문제점

  • 바이트 길이는 5744 밖에 안되는데 HashSet 인스턴스를 역직렬화하는 과정에서 각각의 원소들의 해시코드를 계산해야 하기 때문에 프로그램이 거의 영원히 실행됨
  • 반복문에 의해 깊이 100단계까지 만들어졌고 이는 HashSet을 역직렬화하기 위해서는 hashCode 메서드를 2^100번 호출한다는 의미
    • 참고로 2^100은 1.2676506e+30

 

  • 역직렬화가 영원히 계손된다는 것도 문제지만 console에 잘못되었다는 신호조차 주지 않는 것이 가장 큰 문제
  • 위 코드는 단 몇 개의 객체만 생성하더라도 stackoverflow 에러가 발생할 것

 

자바 직렬화의 대안

  • 궁극적으로 직렬화 위험을 회피하는 가장 좋은 방법은 아무것도 역직렬화하지 않는 것
  • 혹은 객체와 바이트 시퀀스를 변환해주는 다른 메커니즘인 크로스-플랫폼 구조화된 데이터 표현을 사용하는 것을 권장
    • 자바 직렬화의 여러 위험을 회피하면서 다양한 플랫폼 지원, 우수한 성능, 풍부한 지원 도구, 그리고 활발한 커뮤니티와 전문가 집단 등 수많은 이점 제공
    • 자바 직렬화보다 훨씬 간단
    • 임의 객체 그래프를 자동으로 직렬화 및 역직렬화하지 않음
    • key-value 쌍의 집합으로 구성하고 간단하고 구조화된 데이터 객체 사용
    • 기본 타입 몇 개와 배열 타입만 지원하여 간단
    • ex) JSON, Protobuf

 

1. JSON

  • JavaScript Object Notation
  • 브라우저와 서버의 통신용으로 설계됨
  • 텍스트 기반이라 사람도 해석할 수 있다는 점이 큰 장점
  • 데이터를 표현하는데 자주 쓰임

 

2. Protobuf

  • C++용으로 만들어졌으며 서버 간 데이터를 교환하고 저장하기 위해 설계됨
  • 이진 표현이라 효율이 훨씬 높고 사람이 해석할 수 있도록 텍스트 표현인 pbtxt 지원
  • 문서를 위한 스키마를 제공하고 올바로 쓰도록 강요

 

자바 직렬화를 피할 수 없는 레거시 프로젝트인 경우

  • 신뢰할 수 없는 데이터는 절대 역직렬화하지 말 것
  • 직렬화를 할 수 밖에 없다면 자바 9 버전부터 추가된 객체 역직렬화 필터링을 적용하여 역직렬화한 데이터가 안전한지 유무를 판단
    • 데이터 스트림이 역직렬화되기 전에 필터리를 설치하는 기능
    • 특정 클래스를 받아들이거나 거부할 수 있음
    • 기본 수용이 아닌 기본 거부 모드를 적용해 화이트리스트에 기록된 안전 하다고 알려진 클래스들만 수용하도록 처리하는 것을 권장
    • SWAT이란 도구를 통해 화이트 리스트를 자도으로 생성 가능
    • 필터링 기능은 메모리를 과하게 사용하거나 객체 그래프가 너무 깊어지는 사태로부터 보호할 수 있지만 앞서 소개한 Deserialization Bomb은 걸러내지 못함


 

정리

  • 직렬화/역직렬화는 보안 위험이 존재하므로 가급적 피하는 것을 권장
  • 시스템을 밑바닥부터 설계한다면 JSON이나 Protobuf와 같은 크로스-플랫폼 구조화된 데이터 표현을 사용하는 것을 권장
  • 습관적으로 클래스를 Serializable 구현체로 만들지 말고, 꼭 그렇게 구현해야 한다면 신경 써서 작성하는 것을 권장
  • 신뢰할 수 없는 데이터는 절대 역직렬화지 말 것
  • 직렬화를 반드시 해야할 수밖에 없는 레거시 시스템에서는 자바 9 버전부터 적용된 객체 역직렬화 필터링을 사용하는 것을 권장
    • 단, Deserialization Bomb과 같은 공격은 막아줄 수 없다는 점을 기억해야 함

 

참고

이펙티브 자바

반응형