Contents
item85. 자바 직렬화의 대안을 찾으라
   Apr 12, 2023     5 min read

아이템 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번 넘게 호출해야한다.
  • 더욱 더 무서운 점은 무언가 잘못되었다는 신호조차 주지 않는다.

Untitled


➡️해결책

  • 애초에 신뢰할 수 없는 바이트 스트림을 역직렬화하는 일 자체가 스스로를 공격에 노출하는 행위다.

직렬화 위험을 회피하는 가장 좋은 방법은 아무것도 역직렬화하지 않는 것이다.

글쓴이는 위의 표현을 비유를 통해서 알려준다. 표현이 좋아서 남겨놓음

“승리하는 유일한 길은 전쟁하지 않는 것이다.”

✅크로스-플랫폼 구조화된 데이터 표현(Cross-platform structured-data representation)

  • JSON & 프로토콜 버퍼(Protocol Buffers 혹은 Protobuf)
 JSONProtocol Buffers
설계 목적브라우저와 서버의 통신용으로 설계구글이 서버 사이에 데이터를 교환하고 저장하기 위해 설계
언어자바스크립트용C++용
차이점텍스트 기반→ 사람이 읽을 수 있음이진 표현 → 효율이 훨씬 높다 , 텍스트 표현도 지원
 오직 데이터를 표현하는 데만 사용문서를 위한 스키마(타입)를 제공하고 올바르게 쓰도록 강요함

✅직렬화를 피할 수 없고 역직렬화한 데이터가 안전한지 완전히 확실할 수 없을때

  • 객체 역직렬화 필터링(java.io.ObjectInputFilter)를 사용하자. → 자바 9에 추가가되었음. 이전 버전에서도 사용 가능
  • 객체 역직렬화 필터링은 데이터 스트림이 역직렬화되기 전에 필터를 설치하는 기능
  • 클래스 단위로, 특정 클래스를 받아들이거나 거부할 수 있음

  • 기본 수용모드
블랙리스트기록된 잠재적으로 위험한 클래스들을 거부
화이트리스트기록된 안전하고 알려진 클래들만 수용

보통 화이트리스트 방식을 추천한다.

  • 화이트리스트를 자동 생성해주는 도구
    • 스왓(SWAT,Serial Whitelist Application Trainer)
      • 필터링 기능은 메모리를 과하게 사용하거나 객체 그래프가 너무 깊어지는 사태로부터 보호해준다.
      • 하지만 직렬화 폭탄은 걸러내지 못함.

➡️핵심정리

  • 직렬화는 위험하니 피하자.
  • 시스템을 처음부터 설계한다면 JSON, Protocol Buffers 같은 대안을 사용하자.
  • 신뢰할 수 없는 데이터는 역직렬화하지 말자.
  • 꼭 해야한다면, 객체 역직렬화 필터링을 사용하자. → 한계 : 모든 공격을 막아 줄 수는 없다.

참조

[자바 직렬화, 그것이 알고싶다. 훑어보기편우아한형제들 기술블로그](https://techblog.woowahan.com/2550/)