티스토리 뷰

728x90
반응형

자바 직렬화의 대안을 찾으라


💡직렬화(Serialization)란?

  • 객체를 직렬화하여 전송 가능한 형태로 만드는 것을 의미합니다.
  • 자바의 I/O 처리는 정수, 문자열, 바이트 단위의 처리만 지원하기 때문에 복잡한 객체 또는 데이터를 외부 자바 시스템에서도 사용할 수 있도록 바이트 형태로 데이터를 변환하는 기술을 직렬화라고 부릅니다.
  • 시스템적으로는 JVM 메모리에 상주하고 있는(힙 또는 스택) 객체 데이터를 바이트 형태로 변환하는 기술입니다.
  • 직렬화에서는 java.io.ObjectOutputStream 패키지가 사용되며, 스트림에 객체를 출력하는 역할을 하며 ObjectOutputStream 객체의 writeObject 메서드는 객체를 직렬화한 후에 스트림으로 보내는 기능을 합니다.

 

💡역직렬화(Deserialization)란?

  • 직렬화된 객체 또는 데이터를 역으로 직렬화하여 다시 객체 형태로 만드는 것을 의미합니다. 저장된 파일을 읽거나 전송된 스트림 데이터를 읽어 원래 객체의 형태로 복원합니다.
  • 역직렬화에서는 java.io.ObjectInputStream 패키지가 사용되며, 스트림으로부터 객체를 입력하는 역할을 수행하며 ObjectInputStream 객체의 readObject 메서드는 스트림에서 객체를 역직렬화하여 데이터를 읽어오고 필드를 미리 세팅한 객체의 변수에 저장하는 기능을 말합니다.

 

💡자바에서 직렬화가 위험한 이유

  • 자바에서 ObjectInputStream의 readObject 메서드를 통해 객체의 그래프가 역직렬화되는데 문제는 바이트 스트림을 역직렬화 하는 과정에서 그 객체 안에 있는 모든 코드를 수행할 수 있는데, 이는 코드 전체가 공격 범위에 들어간다는 것입니다. 모든 직렬화 가능 클래스들을 공격에 대비하더라도 애플리케이션을 취약하게 만들 수 있습니다.

💡직렬화 역직렬화 예제 코드

@AllArgsConstructor
@ToString
public class Member implements Serializable {

    private String name;
    private int age;
}

public class Main {

    public static final String FILE_PATH = "src/member.txt";

    public static void main(String[] args) {
        try {
            Member member = new Member("홀길동", 30);
            doSerializable(member);
            Member serializationMember = doUnSerializable();

            System.out.println(serializationMember); // Member(name=홀길동, age=30)
        } catch (ClassNotFoundException | IOException e) {
            e.printStackTrace();
        }
    }

    // 직렬화
    private static void doSerializable(Member member) throws IOException {
        FileOutputStream fos = new FileOutputStream(FILE_PATH);
        ObjectOutputStream oos = new ObjectOutputStream(fos);
        oos.writeObject(member);
        oos.close();
    }

    // 역직렬화
    private static Member doUnSerializable() throws IOException, ClassNotFoundException {
        FileInputStream fis = new FileInputStream(FILE_PATH);
        ObjectInputStream ois = new ObjectInputStream(fis);
        Member member = (Member) ois.readObject();
        ois.close();
        return member;
    }
}

 

💡직렬화를 사용하지 말자

  • 신뢰할 수 없는 바이트 스트림을 역직렬화하는 작업은 스스로를 공격에 노출시키는 행위로 처음부터 직렬화, 역직렬화를 사용하지 않는게 가장 안전합니다. 객체와 바이트 시퀀스를 변환해주는 다른 기술들이 많이 있는데, 직렬화 시스템 혹은 크로스-플랫폼 구조화된 데이터 표현이라고 합니다. 
  • 위와 같은 방식은 임의 객체 그래프를 직렬화/역직렬화하는 대신 기본 타입 몇개와 배열 타입만 지원합니다. 이정도의 추상화로도 충분히 직렬화의 문제점을 회피할 수 있습니다. 이런 데이터의 표현으로는 JSON과 프로토콜 버퍼가 있습니다.

📜 JSON

  • 더클라스 크록퍼드(Douglas Crockford)가 브라우저와 서버의 통신용으로 설계
  • 자바 스크립트용으로 만들어졌습니다.
  • 텍스트 기반이라 사람이 읽을 수 있습니다.
  • 오직 데이터를 표현하기 위해 사용됩니다.

📜 프로토콜 버퍼

  • 구글이 서버 사이에 데이터를 교환하고 저장하기 위한 목적으로 설계
  • C++용으로 만들어졌습니다.
  • 이진 표현이라 효율이 높습니다.
  • 문서를 위한 스키마를 제공하고 올바로 쓰도록 강요합니다.

 

💡신뢰할 수 없는 데이터는 절대 역직렬화하지 말라

  • 레거시 시스템 때문에 자바 직렬화를 완전히 배제할 수 없을 때의 차선책으로는 신뢰할 수 없는 데이터는 절대 역직렬화하지 않는 것입니다. 자바의 공식 보안 코딩 지침에서는 "신뢰할 수 없는 데이터의 역직렬화는 본질적으로 위험하므로 절대로 피해야 한다" 라고 조언하고 있습니다. 
  • 하지만 직렬화를 피할 수 없고 역직렬화한 데이터가 안전한지 완전히 확신할 수 없는 경우 객체 역직렬화 필터링을 사용할 수 있습니다.

💡객체 역직렬화 필터링(java.io.ObjectInputFilter)

  • 자바 9에 추가되었으며, 이전 버전에서도 사용할 수 있도록 이식되었습니다.
  • 객체 역직렬화 필터링은 데이터 스트림이 역직렬화되기 전에 필터를 설치하는 기능입니다.
  • 클래스 단위로 특정 클래스를 받아들이거나 거부할 수 있습니다.
    • 기본 수용 모드 : 블랙리스트에 기록된 잠재적으로 위험한 클래스들을 거부합니다.
    • 기본 거부 모드 : 화이트리스트에 기록된 안전하다고 알려진 클래스들만 수용합니다.
  • 블랙리스트 방식보다는 화이트리스트 방식을 추천하고 있습니다. 그 이유눈 블랙리스트 방식은 이미 알려진 위험으로부터만 보호할 수 있기 때문입니다.
  • 필터링 기능은 메모리를 과하게 사용하거나 객체 그래프가 너무 깊어지는 사태로부터 보호해줍니다. 하지만 직렬화 폭탄은 걸러내지 못합니다.(deserialzation bomb)

 

 

 

 

 

 

728x90
반응형