Skip to content
Open
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
139 changes: 139 additions & 0 deletions 12장_직렬화/item85.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,139 @@
# Serialization

자바 내부 시스템에서 사용되는 객체나 데이터를 외부에서 사용할 수 있도록 Byte 형태로 변환

- Java.io.Serializable 인터페이스를 상속받은 객체와 Primitive 타입의 데이터가 직렬화의 대상
- 기본자료형(Primitive Type)은 정해진 Byte의 변수이기 때문에 Byte 단위로 변환 가능
- 객체의 크기는 가변적이며, 객체를 구성하는 자료형들의 종류와 수에 따라 객체의 크기가 다양하게 바뀔 수 있기 때문에 객체를 직렬화하기 위해 Serializable 인터페이스를 구현
- 객체의 멤버들 중 Serializable 인터페이스가 구현되지 않은 것이 존재하면 안 됨
- Transient가 선언된 멤버는 전송되지 않음
- 객체 내에 Serializable 인터페이스가 구현되지 않은 멤버 때문에 NonSerializableException이 발생하는 경우, Transient를 선언해주면 직렬화 대상에서 제외되기 때문에 문제없이 해당 객체를 직렬화 가능.

### 자바 직렬화의 장점
- 자바 직렬화는 자바 시스템에서 개발에 최적화되어 있음.
- 복잡한 데이터 구조의 클래스의 객체라도 직렬화 기본 조건만 지키면 큰 작업 없이 바로 직렬화를 가능하며 역직렬화도 가능!
당연하게 보이는 장점 중에 하나지만 데이터 타입이 자동으로 맞춰지기 때문에 관련 부분을 큰 신경을 쓰지 않아도 됨! 그렇게 역직렬화가 되면 기존 객체처럼 바로 사용 가능.

#### 참고 : 자바 RMI(Remote Method Invocation)

- 직렬화 된 Java 클래스의 전송, 원격 프로시저 호출(RPC)과 같은 객체 지향적인 원격 메서드 호출을 수행하는 Java API
- 기존 자바 언어의 장점과 풍부한 API를 분산 객체 기술에 이용이 가능하게 됨


### 자바 직렬화를 지양해야하는 이유

![](https://velog.velcdn.com/images/jiyeong/post/ad3f4d20-e00a-49ff-82d8-9d6b46ac9943/image.png)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

'SUID(SerialVersionUID)는 개발자가 직접 관리해야 한다'라고 하는데 실제로 엄격하게 관리하고 있는지, 관리하고 있다면 어떻게 관리하고 있는지 궁금합니다

ps. 참고한 자료라도 해당 내용을 스크랩하는 것보다, 별도로 정리한 뒤 출처를 명확히 밝히는게 더 좋지않을까 생각이 듭니다 :)


[우아한형제들](https://techblog.woowahan.com/2550/)

## 직렬화는 위험하다.

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


### 자바 직렬화는 왜 위험한가?

- 공격 범위가 너무 넓음
- 지속적으로 더 넓어져 방어하기도 어려움
- OutputInputStream의 readObject 메서드를 호출하면서 객체 그래프가 역직렬화(deserialization)됨
- readObject 메서드는 (Serializable 인터페이스를 구현했다면) 클래스패스 안의 거의 모든 타입의 객체를 만들어낼 수 있는 사실상 마법같은 생성자.
- 바이트 스트림을 역직렬화하는 과정에서 readObject 메서드는 그 타입들 안의 모든 코드를 수행 가능
- 즉, **그 타입들의 코드 전체가 악의적인 공격 범위에 들어갈 수 있음**!

- 자바의 표준 라이브러리나 서드파티 라이브러리, 그리고 어플리케이션 자신의 클래스들도 공격 범위에 포함됨

> 자바의 역직렬화는 명백하고 현존하는 위험이다.
자바 하부 시스템 RMI(Remote Method Invocation), JMX(Java Management Extension), JMS(Java Messaging System)
을 통해 간접적으로 쓰이고 있기 때문이다.
신뢰할 수 없는 스트림을 역직렬화하면 원격 코드 실행(remote code execution, RCE), 서비스 거부(denial-of-service, Dos) 등이 공격으로 이어질 수 있다. 잘못한 게 아무것도 없는 애플리케이션이라도 이런 공격에 취약해질 수 있다.


- 역직렬화 과정에서 호출되어 잠재적인 위험한 동작을 수행하는 메서드를 **가젯(gadget)** 이라고 한다.
- 하나의 가젯이 또는 여러 개의 가젯이 마음대로 코드를 수행하게 할 수 있기 때문에 아주 신중하게 제작된 바이트 스트림만 역직렬화해야 한다.

#### 역직렬화 폭탄 예시 - 이 스트림의 역직렬화는 영원히 계속된다!

- 역직렬화에 시간이 오래 걸리는 짧은 스트림을 **역직렬화 폭탄**(deserialization bomb)이라고 함.
- 역직렬화에 시간이 오래 걸리는 짧은 스트림을 역직렬화하는 것만으로도 서비스 거부 공격에 쉽게 노출될 수 있다.
- 아래 코드는 HashSet과 문자열을 이용해 역직렬화 폭탄을 테스트 한 예시

```
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개 이하의 객체 참조를 가지며 스트림의 전체 크기는 5744바이트.
- 그러나 역직렬화는 끝나지 않음.
- 문제는 HashSet 인스턴스를 역직렬화하려면 그 원소들의 해시코드를 계산해야 한다는 데 있다. 역직렬화과 영원히 계속된다는 것도 문제지만, 무언가 잘못되었다는 신호조차 주지 않는다는 것도 큰 문제이다.
- 단 몇 개의 객체만 생성해도 스택 깊이 제한에 걸려버린다.

## 해결법

- 신뢰할 수 없는 바이트 스트림을 역직렬화하는 일 자체가 스스로를 공격에 노출하는 행위이다.
- 직렬화 위험을 회피하는 가장 좋은 방법은 **아무것도 역직렬화하지 않는 것**이다.
- 우리가 작성하는 새로운 시스템에서 자바 직렬화를 써야 할 이유는 전혀 없다.

- 객체와 바이트 시퀀스를 변환해주는 다른 메커니즘이 많이 있다.
- 자바 직렬화의 여러 위험을 회피하면서 다양한 플랫폼 지원, 우수한 성능, 풍부한 지원 도구, 활발한 커뮤니티와 전문가 집단 등 수많은 이점까지 제공한다.


### 크로스 플랫폼 구조화된 데이터 표현(Cross-Platform Structured-Data Representation)

- 자바 직렬화보다 훨씬 더 간단!
- 임의 객체 그래프를 자동으로 직렬화 및 역직렬화 하지 않음
- 대신 속성-값 쌍의 집합으로 구성된 간단하고 구조화된 데이터 객체를 사용
- 기본 타입 몇 개와 배열 타입만 지원

크로스 플랫폼 구조화된 데이터 표현은 **JSON과 프로토콜 버퍼**가 있다.
- JSON은 더글라스 크록퍼드가 브라우저와 서버의 통신용으로 설계했고, 프로토콜 버퍼는 구글이 서버 사이에 데이터를 교환하고 저장하기 위해 설계했다.
- JSON은 자바스크립트용, 프로토콜 버퍼는 C++용이다.

#### JSON과 프로토콜 버퍼의 차이

- JSON은 텍스트 기반이라 사람이 읽을 수 있고, 프로토콜 버퍼는 이진 표현이라 효율이 훨씬 높다.
- JSON은 오직 데이터를 표현하는 데만 쓰이지만, 프로토콜 버퍼는 문서를 위한 스키마를 제공하고 올바로 쓰도록 강요한다.
- 효율은 프로토콜 버퍼가 훨씬 좋지만 텍스트 기반 표현에는 JSON이 아주 효과적이다.

- **자바 직렬화는 크로스-플랫폼 구조화된 데이터 표현 방법으로 대체**해야 한다.
- 예) JSON, protocol buffer 등이 있다. 프로토콜 버퍼는 이진 표현이라 효율이 훨씬 높으며 JSON은 텍스트 기반이라 사람이 읽을 수 있는 장점이 있다.

### 어쩔 수 없이 직렬화를 사용해야 할 때
- 레거시 시스템 때문에 직렬화를 배제할 수 없을 때의 차선책은 신뢰할 수 없는 데이터는 절대 역직렬화하지 않는 것이다.
- 신뢰할 수 없는 발신원으로부터의 RMI는 절대 수용해서는 안된다.

직렬화를 피할 수 없고 역직렬화한 데이터가 안전한지 확신할 수 없다면 **객체 역직렬화 필터링**(java.io.ObjectInputFilter)을 사용하자(자바 9)

### 객체 역직렬화 필터링
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

객체 역직렬화 필터링(java.io.ObjectInputFilter)의 간단한 예시가 궁금합니다


객체 역직렬화 필터링은 데이터 스트림이 역직렬화되기 전에 필터를 설치하는 기능이다.
- 클래스 단위로, 특정 클래스를 받아들이거나 거부할 수 있다.
- 기본 수용 모드에서는 블랙리스트에 기록된 잠재적으로 위험한 클래스들을 거부한다.
- 기본 거부 모드에서는 **화이트리스트**에 기록된 안전하다고 알려진 클래스들만 수용한다.
- 블랙리스트 방식 보다는 화이트리스트 방식을 사용할 것!
- 화이트리스트를 자동으로 생성해주는 스왓(SWAT, Serial Whitelist Application Trainer)이라는 도구도 있다.
- 필터링 기능은 메모리를 과하게 사용하거나 객체 그래프가 너무 깊어지는 사태로부터 보호해준다. 그러나 직렬화 폭탄은 걸러내지 못한다.


- 자바 직렬화를 사용하는 시스템 관리 시 **크로스 플랫폼 구조화된 데이터 표현으로 마이그레이션**하는 것을 추천한다.

### 정리

- 직렬화는 위험하니 피해야 한다.
- JSON이나 프로토콜 버퍼 같은 대안을 사용하자.
- 신뢰할 수 없는 데이터는 역직렬화하지 말자.
- 꼭 해야 한다면 객체 역직렬화 필터링을 사용하되, 이마저도 사실 모든 공격을 막아줄수는 없다는 것을 꼭 알고 있자.
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

  • DTO 정의할 때
  • JPA Entity 정의할 때
  • Kafka 프로듀서, 컨슈머를 정의할 때(Serializer, Deserializer 설정)
  • 등등

위에 정의된 목록은 간단하게 implements Serializable를 선언하거나 Serializer/Deserializer등을 설정해주어 프레임웍이나 라이브러리 내부적으로 직렬화/역직렬화를 사용하는 경우인데

  1. 개발자가 직접 사용하고 있진 않지만 내부적으로 직렬화/역직렬화를 사용하고 있어서 많이 신경써서 사용해야하는 케이스가 있을까요?
  2. 실제 업무에서 개발자가 직접 java.io.ObjectOutPutStream 등을 사용해서 직렬화를 하는 사례나 경험이 있을까요?