View

반응형

자바에 직렬화가 도입되면서 프로그래머가 어렵지 않게 분산 객체를 만들 수 있게 되었지만, 심각한 보안 문제를 마주해야 했다.

 

자바 직렬화의 근본적인 문제 - 취약점

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

ObjectInputStream의 readObject() 메서드는 Serializable 인터페이스를 구현한 클래스패스 안에 거의 모든 타입의 객체를 만들어 낼 수 있다. 바이트 스트림을 역직렬화하는 과정에서 이 메서드는 그 타입들 안의 모든 코드를 수행할 수 있고, 이로 인해 그 타입들의 코드 전체가 공격 범위에 들어가게 된다. (자바 표준 라이브러리와 서드 파티 라이브러리 등 모두 포함)

가능한 모든 모범 사례를 따르고 모든 직렬화 가능 클래스들을 공격에 대비하도록 작성한다 해도, 우리들의 애플리케이션은 여전히 취약할 수 있다.

가젯(gadget)
역직렬화 과정에서 호출되어 잠재적으로 위험한 동작을 수행하는 메서드

 

여러 가젯을 함께 사용하여 가젯 체인을 구성하면, 가끔씩 공격자가 기반 하드웨어의 네이티브 코드를 마음대로 실행할 수 있는 아주 강력한 가젯 체인도 발견되곤 한다. 공격자가 이러한 가젯들을 체인으로 엮어 공격한다면 시스템을 마비시키고 그로 인해 막대한 피해를 볼 수 도 있다.

따라서 우리는 아주 신중하게 제작한 바이트 스트림만 역직렬화 해야 한다.

 

역직렬화 폭탄(Deserialization bomb)

역직렬화에 시간이 오래 걸리는 짧은 스트림을 역직렬화하는 스트림을 역직렬화 폭탄이라고 한다.

가젯까지 갈 것도 없이, 역직렬화 폭탄은 서비스 거부 공격(DOS 공격)에 쉽게 노출된다.

// 역직렬화 폭탄 - 이 스트림의 역직렬화는 영원히 계속된다.
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개 이하의 객체 참조를 갖는다.

반복문에 의해 이 깊이가 100단계까지 만들어진다. 이 객체를 역직렬화하려면 해시 코드를 계산해야 하는데, 이 과정에서 hashCode() 메서드를 2100 번 넘게 호출해야 한다. 단 몇 개의 객체만 생성해도 스택 깊이 제한에 걸려버린다.

 

직렬화의 대안

그렇다면 우리는 앞선 직렬화의 문제들에 어떻게 대처해야 할까?

자바 직렬화 위험을 회피하는 가장 좋은 방법은 역직렬화 하지 않는 것이다. 우리가 작성하는 새로운 시스템에 자바 직렬화를 써야 할 이유가 전혀 없다. 객체와 바이트 시퀀스를 변환해주는 다른 메커니즘이 많이 있기 때문이다.

 

크로스-플랫폼 구조화된 데이터 표현

자바 직렬화와 다른 메커니즘을 가진 직렬화 시스템

자바 직렬화의 위험성을 회피하면서 다양한 플랫폼 지원, 우수한 성능, 풍부한 지원 도구 등을 제공하는 다른 방식의 매커니즘 방식을 크로스-플랫폼 구조화된 데이터 표현이라 한다.

 

장점

  • 자바의 직렬화보다 훨씬 간단하다.
  • 임의의 객체 그래프를 자동으로 직렬화/역직렬화 하지 않는다.
  • 속성-값 쌍의 집합으로 구성된 간단하고 구조화된 데이터 객체를 사용한다.
  • 간단한 추상화로 아주 강력한 분산 시스템을 구축하고, 자바 직렬화의 문제점을 회피할 수 있다.

대표적으로 JSON과 프로토콜 버퍼(Protocol Buffers)가 있다.

 

객체 역직렬화 필터링(ObjectInputFilter)

레거시 때문에 자바 직렬화를 배제할 수 없을 때의 차선책은 신뢰할 수 없는 데이터는 절대 역직렬화하지 않는 것이다.

만약 직렬화를 피할 수 없고 역직렬화한 데이터가 안전한지 확신할 수 없다면 객체 역직렬화 필터링(java.io.ObjectInputFilter)을 사용하자. 이 기능은 데이터 스트림이 역직렬화되기 전에 필터를 설치하는 기능이다.

블랙리스트에 기록된 클래스를 거부하거나, 화이트리스트에 기록된 클래스만 수용한다. 이미 알려진 위험으로부터만 보호할 수 있는 블랙리스트보다 화이트리스트를 사용하는 것이 좋다.

 

핵심 정리

  • 자바의 직렬화는 위험하니 피해야 한다.
  • 시스템을 처음 설계한다면 JSON이나 프로토콜 버퍼 같은 직렬화 시스템을 사용하자.
  • 신뢰할 수 없는 데이터라면 역직렬화를 하지 말자.
  • 자바 직렬화를 사용해야 한다면 역직렬화 필터링을 사용하되, 이 마저도 모든 공격을 막을 수 없음을 기억하자.
반응형
Share Link

인기 글

최신 글

전체 방문자

Today
Yesterday