You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
그래서 불변식을 지키고 불변을 유지하기 위해 생성자와 접근자에서 Date 객체를 방어적으로 복사하느라 코드가 상당히 길어졌다.
// 방어적 복사를 사용하는 불변 클래스publicfinalclassPeriod {
privatefinalDatestart;
privatefinalDateend;
/** * @param start 시작 시각 * @param end 종료 시각; 시작 시각보다 뒤여야 한다. * @throws IllegalArgumentException 시작 시각이 종료 시각보다 늦을 때 발생한다. * @throws NullPointerException start나 end가 null이면 발행한다. */publicPeriod(Datestart, Dateend) {
this.start = newDate(start.getTime());
this.end = newDate(end.getTime());
if(this.start.compareTo(this.end) > 0) {
thrownewIllegalArgumentException(start + "가 " + end + "보다 늦다.");
}
}
publicDatestart() { returnnewDate(start.getTime()); }
publicDateend() { returnnewDate(end.getTime()); }
publicStringtoString() { returnstart + "-" + end; }
}
이 클래스를 직렬화 한다면?
이 클래스 선언에 implements Serializable을 추가하면 될 것 같다.
하지만 이렇게 해서는 이 클래스의 중요한 불변식을 더는 보장하지 못하게 된다.
문제 ) readObject 메서드가 실질적으로 또 다른 public 생성자이기 때문이다. 따라서 다른 생성자와 똑같은 수준으로 주의를 기울여야 한다.
쉽게 말해 readObject는 매개변수로 바이트 스트림을 받는 생성자라 할 수 있다.
보통의 경우 바이트 스트림은 정상적으로 생성된 인스턴스를 직렬화해 만들어진다.
하지만 불변식을 깨뜨를 의도로 임의 생성한 바이트 스트림을 건네면 정상적인 생성자로는 만들어낼 수 없는 객체를 생성해 낼 수 있다.
문제를 해결하려면?
readObject 메서드가 defaultReadObject를 호출한 다음 역질렬화된 객체가 유효한지 검사해야 한다.
이 유효성 검사에 실패하면 InvalidObjectException을 던지게 하여 잘못된 역직렬화가 일어나는 것을 막을 수 있다.
이 해결법도 문제가 하나 또 있다
정상 인스턴스에서 시작된 바이트 스트림 끝에 private Date의 필드로의 참조를 추가하면 가변 인스턴스를 만들어 낼 수 있다. 공격자는 ObjectInputStream에서 인스턴스를 읽은 후 스트림 끝에 추가된 '악의적인 객체 참조'를 읽어 객체의 내부 정보를 얻을 수 있다.
// 가변 공격의 예publicclassMutablePeriod {
//Period 인스턴스publicfinalPeriodperiod;
//시작 시각 필드 - 외부에서 접근할 수 없어야 한다.publicfinalDatestart;
//종료 시각 필드 - 외부에서 접근할 수 없어야 한다.publicfinalDateend;
publicMutablePeriod() {
try {
ByteArrayOutputStreambos = newByteArrayOutputStream();
ObjectArrayOutputStreamout = newObjectArrayOutputStream(bos);
//유효한 Period 인스턴스를 직렬화한다.out.writeObject(newPeriod(newDate(), newDate()));
/** * 악의적인 '이전 객체 참조', 즉 내부 Date 필드로의 참조를 추가한다. * 상세 내용은 자바 객체 직렬화 명세의 6.4절을 참고하자. */byte[] ref = {0x71, 0, 0x7e, 0, 5}; // 참조 #5bos.write(ref); // 시작 start 필드 참조 추가ref[4] = 4; //참조 #4bos.write(ref); // 종료(end) 필드 참조 추가// Period 역직렬화 후 Date 참조를 훔친다.ObjectInputStreamin = newObjectInputStream(newByteArrayInputStream(bos.toByteArray()));
period = (Period) in.readObject();
start = (Date) in.readObject();
end = (Date) in.readObject();
} catch (IOException | ClassNotFoundExceptione) {
thrownewAssertionError(e);
}
}
}
📚 [Item 88] readObject 메서드는 방어적으로 작성하라
readObject는 실질적 생성자다
문제를 해결하려면?
이 해결법도 문제가 하나 또 있다
위 공격 예시로 Period 인스턴스는 불변식을 유지한 채 생성됐지만, 의도적으로 내부의 값을 수정할 수 있다
이처럼 변경할 수 있는 Period 인스턴스를 획득한 공격자는 이 인스턴스가 불변이라고 가정하는 클래스에 넘겨 엄청난 보안 문제를 일으킬 수 있다.
객체를 역직렬화 할 때는 클라이언트가 소유해서는 안되는 객체 참조를 갖는 필드를 모두 반드시 방어적으로 복사해야 한다.
따라서 readObject에서는 불변 클래스 안의 모든 Private 가변 요소를 방어적으로 복사해야 한다.
방어적 복사와 유효성 검사를 수행하는 readObject 메서드
주목할 점
주의
기본 readObject를 써도 좋을지 판단하는 방법
💡 핵심 정리
안전한 readObject 메서드를 작성하는 지침
The text was updated successfully, but these errors were encountered: