직렬화(Serialize) & 역직렬화(Deserialize)
직렬화(serialize)란 자바 시스템 내부에서 사용되는 Object 또는 Data를 외부 자바 시스템에서도 사용할 수 있도록 바이트 스트림(stream of byte) 형태로 연속적인(serial) Data로 변환하는 포맷 변환 기술을 말한다. 역직렬화(Deserialize)는 byte로 변환된 데이터를 원 상태로 Object나 Data로 변환하는 기술이다.
JVM(Java Virtual Machine)의 힙(heap) 또는 스택(stack) 메모리에 상주하고 있는 객체 데이터를 직렬화로 byte 형태로 변환 후 데이터베이스나 파일과 같은 외부 저장소에 저장해 둬 다른 컴퓨터가 해당 파일을 가져와 역직렬화를 통해 자바 객체로 변환하여 JVM(Java Virtual Machine) 메모리에 적재하는 것으로 이해하면 된다.
바이트 스트림(stream of byte)
스트림은 클라이언트나 서버 간 출발지 또는 목적지로 입출력(I/O)하기 위한 데이터가 전송되는 통로를 의미한다.
자바는 스트림의 기본 단위를 바이트로 두고 있어 네트워크, 데이터베이스로 전송하기 위해 최소 단위인 바이트 스트림으로 변환하여 처리한다.
직렬화(Serialize)의 사용 목적
직렬화를 응용한다면 휘발성이 있는 캐싱 데이터를 영구 저장이 필요할 때 사용할 수 있다. JVM(Java Virtual Machine)의 메모리에서 상주되고 있는 객체 데이터가 시스템이 종료되더라도 영속화(Persistence)를 해두면 재사용이 필요할 때 유용하게 사용할 수 있다.
서블릿 세션(Servlet Session)
단순히 세션을 서블릿 메모리 위에 운용한다면 따로 직렬화가 필요하지 않으나, 세션 데이터를 저장하거나 공유할 때 직렬화를 사용할 수 있다. 세션 데이터를 데이터 베이스에 저장하거나 톰캣의 세션 클러스링을 통해 각 서버 간 데이터 공유가 필요할 때 유용하다.
캐시(Cache)
데이터베이스로부터 조회한 객체 데이터를 다른 모듈에 사용할 때 다시 데이터베이스를 조회할 필요 없이 객체를 직렬화하여 보관해 두었다가 역직렬화를 통해 사용할 수 있다. 자바 내에 캐시를 직렬화하여 사용하는 것이 간편하기 때문에 많이 사용된다. 그러나, 현재는 Redis, Memcached와 같은 캐시 데이터베이스를 많이 사용하는 편이다.
자바 RMI(Remote Method Invocation)
자바 RMI는 원격 시스템 간의 메시지 교환을 위해 사용되는 기술이다. 메시지에 객체 데이터를 직렬화하여 송신하는 기술이며, 현재는 소켓을 주로 이용하기 때문에 잘 사용되지 않는다.
직렬화(Serialize) vs JSON
직렬화는 외부 파일이나 네트워크를 통해 클라이언트 간 객체 데이터를 주고 받을 때 사용된다. 그러나 우리에게 더 친숙한 csv, JSON과 같은 데이터 포맷이 있는데 굳이 직렬화를 사용하는 이유가 궁금하기도 하다. JSON은 웹(Web) 뿐만이 아니라 게임에서도 설정 파일로 쓰거나, 데이터를 교환할 때 범용적으로 사용된다. 그러나 직렬화는 오직 자바 시스템 내 사용할 수 있다.
왜 사용할까?
직렬화는 자바의 고유 기술인 만큼 자바 개발에 있어 최적화되어 있다. 또한 자바의 거대한 레퍼런스 타입에 어떠한 제약 없이 외부에 내보낼 수 있다는 장점을 가진다.
예를 들어 기본형(char, int, double) 타입이나 배열(array)과 같은 타입들은 여러 프로그래밍 언어에서 공통적으로 사용하는 타입이기 때문에 JSON으로도 충분히 상호 이용에 문제가 없다.
그러나, 자바의 온갖 컬렉션이나 참조형 타입들은 어떤가? 자바는 사용자에 따라 객체를 만들고 커스텀으로 타입을 만들 수 있기 때문에 단순 파일 포맷 만으로는 호환될 수 있는 타입에 한계가 있다. 이를 외부에 내보내기 위해선 각 데이터를 매칭시키는 별도의 파싱(parsing)이 필요하다.
그에 반해, 직렬화를 이용하면 파이썬이나 자바스크립트 같은 다른 언어에서 사용할 수 없을지라도, 직렬화의 기본 조건만 지킨다면 별 어려움 없이 바로 외부로 보낼 수 있다. 그리고 직렬화된 데이터를 다시 역직렬화를 통해 읽어 들이면 원 상태로 복구할 수 있기 때문에 자바의 참조형 타입의 기능을 그대로 사용할 수 있다. 즉 별다른 파싱(parsing)이 없이 말이다.
이러한 장점들도 있으나, 치명적인 단점도 존재한다.
최근 추세는 범용적인 JSON을 이용하는 추세이며, JSON을 사용할 지 직렬화를 사용할 지에 대한 판단은 목적에 따라 적절히 써야 하며, 되도록 직렬화를 사용하는 것보다 JSON을 사용하는 것을 권장한다.
직렬화의 문제점
직렬화를 사용하기 위해선 Serializable 인터페이스를 클래스에 implements하면 간단하게 직렬화가 가능한 클래스로 만들 수 있게 된다. 그러나 간편한 사용 방법에 비해 직렬화를 구현한 비용은 매우 비싼 편이다. 직렬화를 구현한 순간부터 많은 위험성을 안게 된다.
직렬화가 된 데이터는 용량이 크다.
직렬화는 객체에 저장된 데이터 값 뿐만이 아니라 타입 정보, 클래스 메타 정보를 가지고 있어 용량이 많이 차지된다. 이는 JSON과 비교해 볼 때 파일 용량 크기가 거의 2배 이상 차이가 난다. 따라서 데이터베이스, 캐시 등 외부에 저장할 때, 장기간 저장되는 행위는 지양해야 된다.
역직렬화는 위험하다.
직렬화된 외부 데이터를 역직렬화하는 과정에서 나도 모르게 공격당할 위험이 있다.
역직렬화 과정에서 호출되어 잠재적으로 위험한 동작을 수행하는 메서드를 가젯(gadget)이라고 부르는데 바이트 스트림을 역직렬화하는 ObjetInputStream의 readObject() 메서드를 호출하게 되면 객체 그래프가 역직렬화되어 classpath 안의 모든 타입의 객체를 만들게 되고 해당 타입 객체 내 모든 코드를 수행할 수 있게 되므로 나의 프로그램 코드 전체가 공격 범위로 들어가는 위험성이 생긴다.
객체를 직렬화하여 외부로 전송하는 과정에 중간에 누가 파일을 가로채 바이트 내용을 조작하여, 송신자가 역직렬화하는 과정에서 객체 데이터에 위험한 값을 대입시켜 불변을 깨는 식으로도 공격할 수 있다. 이러한 원인은 역직렬화는 생성자 없이 인스턴스화가 가능하기 때문이다. 이를 보이지 않는 생성자라 표현하기도 한다.
직렬화를 대체할 수 없다면
어쩔 수 없이 Serializable을 구현한 클래스를 사용해야 한다면 역직렬화 필터링(ObjectInputFilter)과 같은 역직렬화 방어 기법들을 사용하는 방법이 있다. 데이터 스트림이 역직렬화되기 전 필터 조건문을 수행하여 특정 클래스만 허용하거나 제외하도록 할 수 있다. 그러나 직렬화를 통한 공격에 대비한다고 할지라도 여전히 취약한 부분이 존재하기 때문에 가장 좋은 방법은 역직렬화를 안 하는 것이다.
자바 직렬화, 그것이 알고싶다. 훑어보기편 | 우아한형제들 기술블로그
{{item.name}} 자바의 직렬화 기술에 대한 대한 이야기입니다. 간단한 질문과 답변 형태로 자바 직렬화에 대한 간단한 설명과 직접 프로젝트를 진행하면서 겪은 경험에 대해 이야기해보려 합니다.
techblog.woowahan.com
Java Serialization and Deserialization - Studytonight
Java Serialization and Deserialization Serialization means converting an object to a byte stream. Deserialization, as the name suggests, is the reverse process of serialization. Deserialization generates an object from a byte stream. Serialization and Dese
www.studytonight.com
The Anatomy of Deserialization Attacks
Gain insights into the anatomy of deserialization attacks, a common exploit used by malicious actors with this post by Busra Demir at Cobalt.
www.cobalt.io
'Java' 카테고리의 다른 글
일급 컬렉션(First Class Collection) (1) | 2024.07.16 |
---|---|
가변 인자(Vararags) (0) | 2024.06.10 |
for loop vs stream forEach (0) | 2024.03.04 |