Skip to content

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

@ideasidus

Description

@ideasidus

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

✨ 핵심 내용

  • 쓰지 마라
  • 피해야 한다.
  • JSON 이나 Protocol Buffers, 다른 거 써라.
  • 꼭 써야하면 필터링을 사용하되 이 역시 모든 공격을 다 막아줄 수는 없다.
  • 클래스가 직렬화를 지원하게 만들지 말고, 꼭 지원해야 한다면 정말 신경써서 작성해야한다.

💡 새롭게 알게 된 점

📚 정리

  1. 직렬화는 프로그래머가 어렵지 않게 분산 객체를 만들 수 있다는 구호는 매력적이었으나, 보이지 않는 생성자, API와 구현 사이의 모호해진 경계, 잠재적인 정확성 문제, 성능, 보안, 유지보수성 등 그 대가가 컸다.
    1. 지지자들은 장점이 위험성을 압도한다고 생각했지만, 지금까지 경험한 바로는 그 반대다
    2. 샌프란시스코 교통국이 랜섬웨어 공격을 받아 요금 징수 시스템이 마비되는 취약점도 있었다!
  2. 직렬화의 근본적인 문제는 공격 범위가 너무 넓고 지속적으로 더 넓어져 방어하기 어렵다.
    1. 자바 표준 라이브러리나 아파치 커먼즈 같은 서드파티 라이브러리는 물론 애플리케이션 자신의 클래스도 공격 범위에 포함된다.
    2. 모든 모범사례를 따르고 공격에 대비해도 여전히 취약할 수 있다.

      자바의 역직렬화는 명백하고 현존하는 위험이다. 이 기술은 지금도 애플리케이션에서 직접 혹은, 자바 하부 시스템(RMI(Remote Method Invocation), JMX(Java Managemnet Extension), JMS(Java Messaging System) 등)을 통해 간접적으로 쓰이고 있기 때문이다. 신뢰할 수 없는 스트림을 역직렬화하면 원격 코드 실행(Remote Code Execution, REC), 서비스 거부(Denial-Of-Service, DoS) 등의 공격으로 이어질 수 있다. 잘못한 게 아무것도 없는 애플리케이션이라도 이런 공격에 취약해 질 수 있다.

      -- CERT 조정센터 기술관리자 Robert Seacord

  3. 자바 라이브러리와 널리 쓰이는 서드파티 라이브러리에서 직렬화 가능 타입들을 연구해 역직렬화 과정에서 호출돼 잠재적으로 위험한 동작을 수행하는 매서드들을 찾아봤다. 이런 메서드를 gadget 이라 부르며 여러 gadget 을 모아 gadget-chain을 만들 수도 있다. 가끔 공격자가 기반 하드웨어의 네이티브 코드를 마음대로 실행할 수도 있는 강력한 가젯 체인도 발견된다. 그러므로 아주 신중하게 제작한 바이트 스트림만 역직렬화 해야한다.
  4. 가젯까지 갈 것도 없이 역직렬화에 시간이 오래 걸리는 짧은 스트림을 역 직렬화 하는 것으로도 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 페이지 참조
  1. 신뢰할 수 없는 바이트 스트림을 역직렬화하는 일 자체가 스스로를 공격에 노출하는 행위다. 직렬화 위험을 회피하는 가장 좋은 방법은 아무것도 역직렬화하지 않는 것이다. 여러분이 작성하는 새로운 시스템에서 자바 직렬화를 써야 할 이유는 전혀 없다.
  2. 자바 직렬화와 구문하여 크로스-플랫폼 구조화된 데이터 표현(cross-platform structured-data representaion)이라고 하는 이 표현들의 선두주자는 JSON과 Protocol Buffers 이다. 이들은 언어 중립적이라고는 하지만 JSON은 Javascript 용으로, Protobuf는 C++용으로 만들어 져 있다.
    1. 둘의 가장 큰 차이는 JSON은 텍스트 기반이라 사람이 읽을 수 있고, protobuf는 이진표현이라 효율이 훨씬 높다.
    2. JSON은 데이터를 표현하는 데만 쓰이지만, protobuf는 문서를 위한 스키마(type)을 제공하고 올바로 쓰도록 강요한다. (String only / with type)
    3. 효율은 protobuf가 훨씬 좋지만, 텍스트 기반 표현에는 JSON이 아주 효과적이다. protobuf는 이진 표현뿐 아니라 사람이 읽을 수 있는 텍스트 표현(pbtxt)도 지원한다.
  3. 레거시 시스템 때문에 자바 역직렬화를 완전히 배제할 수 없을 때는 신뢰할 수 없는 데이터는 절대 역직렬화 하지 않아야 한다.
    1. 직렬화를 피할 수 없고 역직렬화한 데이터가 안전한지 완전히 확신할 수 없다면 객체 역직렬화 필터링(java.io.ObjectInputFilter)을 사용할 수 있다. 클래스 단위로 특정 클래스를 받아 들이거나 거부할 수 있다.
    2. '기본 수용' 모드에서는 블랙리스트에 기록된 잠재적으로 위험한 클래스들을 거부한다.
    3. '기본 거부' 모드에서는 화이트리스트에 기록된 안전하다고 알려진 클래스들만 수용한다.
    4. 블랙리스트 방식보다는 화이트리스트 방식을 추천한다.
    5. 여러분의 applicaton을 위한 화이트 리스트를 자동으로 생성해주는 SWAT(Serial Whitelist Applicationi Trainer)이라는도구도 있다.
    6. 필터링은 메모리를 과하게 사용하거나 객체 그래프가 너무 깊어지는 사태로부터도 보호해준다. 하지만 앞서 나온 직렬화 폭탄은 걸러내지 못한다.
  4. 직렬화는 안타깝게도 자바 생태계 곳곳에서 사용되고 있다. 이런 시스템을 관리해야 한다면, 시간과 노력을 들여서라도 크로스-플랫폼 구조화된 데이터 표현으로 마이그레이션하는 것을 심각하게 고민해보자.

📢 댓글로 각자의 학습 내용을 공유해주세요!

Metadata

Metadata

Assignees

Labels

Type

No type

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions