packagerule76;/**
* readObject 메서드는 방어적으로 구현하라
*
* 생성자에서 인자의 유효성을 검사한다면, readObject도 마찬가지로 인자의 유효성을 검사해야 한다.
* 역직렬화할 때는 클라이언트가 private 필드의 참조를 클라이언트가 컨트롤할 수 있는 참조로 바꿀 수 없도록 방어적으로 구현해야 한다.
*
* @author gwon
* @history
* 2019. 6. 4. initial creation
*/publicclassRule76{}
packagerule76.compare1;importjava.io.ByteArrayInputStream;importjava.io.ByteArrayOutputStream;importjava.io.IOException;importjava.io.ObjectInputStream;importjava.io.ObjectOutputStream;importjava.io.Serializable;/**
* 유효성을 파괴하는 클래스
*
* readObject 메서드가 실질적으로 public 생성자나 마찬가지며, 생성자를 구현할 때와 같은 점에 주의해야 한다.
* 생성자와 마찬가지로 유효성 검사를 하지 않을 경우, 쉽게 클래스의 유효성을 망가뜨릴 수 있게 된다.
* 기본 직렬화를 사용해서 readObject를 구현하지 않을 경우, 악의적인 사용자는 바이트스트림을 조작하여 데이터의 유효성을 파괴한다.
*
*
* @author gwon
* @history
* 2019. 6. 4. initial creation
*/publicclassValidationConstructorClassNotGoodimplementsSerializable{privatefinalIntegernotZero;// 유효성 체크를 하는 생성자publicValidationConstructorClassNotGood(intnum){this.notZero=num;// 0일 경우 에러if(this.notZero==0){thrownewIllegalArgumentException();}}@OverridepublicStringtoString(){return"ValidationConstructorClassNotGood [notZero="+notZero+"]";}publicstaticvoidmain(String[]args)throwsIOException,ClassNotFoundException{// 1로 객체 생성ValidationConstructorClassNotGoodoriginal=newValidationConstructorClassNotGood(1);System.out.println(original);// ValidationConstructorClassNotGood [notZero=1]// 직렬화byte[]serializedBytes;try(ByteArrayOutputStreambaos=newByteArrayOutputStream()){try(ObjectOutputStreamoos=newObjectOutputStream(baos)){oos.writeObject(original);serializedBytes=baos.toByteArray();}}/**
* 이 클래스를 직렬화 할 경우, 가장 마지막 byte의 값이 nonZero 값이다.
* nonZero을 0으로 변경 (유효성 파괴)
*/serializedBytes[serializedBytes.length-1]=0x00;// 역직렬화ValidationConstructorClassNotGoodcopy;try(ByteArrayInputStreambais=newByteArrayInputStream(serializedBytes)){try(ObjectInputStreamois=newObjectInputStream(bais)){ObjectobjectMember=ois.readObject();copy=(ValidationConstructorClassNotGood)objectMember;}}// nonZero 값이 0으로 변경됨을 확인System.out.println(copy);// ValidationConstructorClassNotGood [notZero=0]}}
packagerule76.compare1;importjava.io.ByteArrayInputStream;importjava.io.ByteArrayOutputStream;importjava.io.IOException;importjava.io.ObjectInputStream;importjava.io.ObjectOutputStream;importjava.io.Serializable;/**
* 역직렬화를 할 때도 유효성을 체크하는 클래스
*
* readObject 메서드가 실질적으로 public 생성자나 마찬가지며, 생성자를 구현할 때와 같은 점에 주의해야 한다.
* 생성자와 마찬가지로 유효성 검사를 하지 않을 경우, 쉽게 클래스의 유효성을 망가뜨릴 수 있게 된다.
* readObject에서 public 생성자와 동일한 유효성을 체크하도록 해야 한다.
*
* @author gwon
* @history
* 2019. 6. 4. initial creation
*/publicclassValidationConstructorClassGoodCaseimplementsSerializable{privatefinalIntegernotZero;// 유효성 체크를 하는 생성자publicValidationConstructorClassGoodCase(intnum){this.notZero=num;// 0일 경우 에러if(this.notZero==0){thrownewIllegalArgumentException();}}// compare : 유효성 체크를 하는 역직렬화 readObjectprivatevoidreadObject(ObjectInputStreams)throwsClassNotFoundException,IOException{s.defaultReadObject();// 0일 경우 에러if(notZero==0){thrownewIllegalArgumentException();}}@OverridepublicStringtoString(){return"ValidationConstructorClassGoodCase [notZero="+notZero+"]";}publicstaticvoidmain(String[]args)throwsIOException,ClassNotFoundException{// 1로 객체 생성ValidationConstructorClassGoodCaseoriginal=newValidationConstructorClassGoodCase(1);System.out.println(original);// ValidationConstructorClassGoodCase [notZero=1]// 직렬화byte[]serializedBytes;try(ByteArrayOutputStreambaos=newByteArrayOutputStream()){try(ObjectOutputStreamoos=newObjectOutputStream(baos)){oos.writeObject(original);serializedBytes=baos.toByteArray();}}/**
* 이 클래스를 직렬화 할 경우, 가장 마지막 byte의 값이 nonZero 값이다.
* nonZero을 0으로 변경 (유효성 파괴)
*/serializedBytes[serializedBytes.length-1]=0x00;// 역직렬화// compare : readObject의 유효성 체크에 걸려 IllegalArgumentException 발생ValidationConstructorClassGoodCasecopy;try(ByteArrayInputStreambais=newByteArrayInputStream(serializedBytes)){try(ObjectInputStreamois=newObjectInputStream(bais)){ObjectobjectMember=ois.readObject();copy=(ValidationConstructorClassGoodCase)objectMember;}}// compare : 도달하지 못함.System.out.println(copy);}}
packagerule76.compare2;importjava.io.ByteArrayInputStream;importjava.io.ByteArrayOutputStream;importjava.io.ObjectInputStream;importjava.io.ObjectOutputStream;importjava.util.Date;/**
* 특정 객체를 직렬화 하여 생긴 바이트스트림에 해당 객체 내부의 필드의 대한 참조 값을 추가로 붙여 쓸 수 있다.
* 악의적인 사용자는 해당 참조값을 자신이 컨트롤할 수 있는 변수에 할당 한 후, 해당 객체에서 접근 불가능하도록 한(private) 필드 등을 조작할 수 있다.
*
* 이를 방어하기 위해, 해당 객체는 역직렬화할 때 클라이언트가 가질 수 없어야 하는 객체 참조를 담은 모든 필드를 방어적으로 복사애햐한다.
* 즉, readObject 메서드 안에서 private로 선언된 필드들을 복사해서 참조를 해도 변경할 수 없도록 해야 한다.
*
* @author gwon
* @history
* 2019. 6. 4. initial creation
*/publicclassMutableClass{// 접근 불가능한(private) 필드를 훔칠 클래스 (훔쳐짐)publicfinalPeriodNotGoodperiodNotGood;publicfinalDatestartNotGood;publicfinalDateendNotGood;// 접근 불가능한(private) 필드를 훔칠 클래스 (훔칠 수 없음)publicfinalPeriodGoodperiodGood;publicfinalDatestartGood;publicfinalDateendGood;publicMutableClass(){try{/**
* 훔칠 수 있는 periodNotGood
*/ByteArrayOutputStreambos=newByteArrayOutputStream();ObjectOutputStreamout=newObjectOutputStream(bos);out.writeObject(newPeriodNotGood(newDate(),newDate()));// Period.start 필드의 참조를 뒤에 추가로 붙여 씀byte[]ref={0x71,0,0x7e,0,5};bos.write(ref);// Period.end 필드의 참조를 뒤에 추가로 붙여 씀ref[4]=4;bos.write(ref);ObjectInputStreamin=newObjectInputStream(newByteArrayInputStream(bos.toByteArray()));periodNotGood=(PeriodNotGood)in.readObject();startNotGood=(Date)in.readObject();// start의 참조를 훔침endNotGood=(Date)in.readObject();// end의 참조를 훔침/**
* 훔칠 수 없는 periodGood
*/ByteArrayOutputStreambos2=newByteArrayOutputStream();ObjectOutputStreamout2=newObjectOutputStream(bos2);out2.writeObject(newPeriodGood(newDate(),newDate()));// Period.start 필드의 참조를 뒤에 추가로 붙여 씀byte[]ref2={0x71,0,0x7e,0,5};bos2.write(ref2);// Period.end 필드의 참조를 뒤에 추가로 붙여 씀ref2[4]=4;bos2.write(ref2);ObjectInputStreamin2=newObjectInputStream(newByteArrayInputStream(bos2.toByteArray()));periodGood=(PeriodGood)in2.readObject();startGood=(Date)in2.readObject();// start의 참조를 훔침endGood=(Date)in2.readObject();// end의 참조를 훔침}catch(Exceptione){thrownewAssertionError(e);}}publicstaticvoidmain(String[]args){MutableClass.periodNotGoodTest();MutableClass.periodGoodTest();}/**
* 훔칠 수 있는 PeriodNotGood 테스트
*
* 값이 조작됨
*/publicstaticvoidperiodNotGoodTest(){System.out.println("########### PeriodNotGood Test ###########");MutableClassmutableClass=newMutableClass();DateendOfBrokenClass=mutableClass.endNotGood;// 참조를 훔친 변수PeriodNotGoodbrokenClass=mutableClass.periodNotGood;System.out.println(brokenClass);// Period [start=Wed Jun 05 00:32:51 KST 2019, end=Wed Jun// 05 00:32:51 KST 2019]endOfBrokenClass.setYear(55);// 값 조작System.out.println(brokenClass);// Period [start=Wed Jun 05 00:32:51 KST 2019, end=Sun Jun// 05 00:32:51 KDT 1955]}/**
* 훔칠 수 없는 PeriodGood 테스트
*
* 방어적으로 복사하여 값이 조작되지 않음
*/publicstaticvoidperiodGoodTest(){System.out.println("########### PeriodGood Test ###########");MutableClassmutableClass=newMutableClass();DateendOfBrokenClass=mutableClass.endGood;// 참조를 훔친 변수PeriodGoodbrokenClass=mutableClass.periodGood;System.out.println(brokenClass);// Period [start=Wed Jun 05 00:32:51 KST 2019, end=Wed Jun// 05 00:32:51 KST 2019]endOfBrokenClass.setYear(55);// 값 조작System.out.println(brokenClass);// Period [start=Wed Jun 05 00:32:51 KST 2019, end=Wed Jun// 05 00:32:51 KST 2019]}}
packagerule76.compare2;importjava.io.IOException;importjava.io.ObjectInputStream;importjava.io.Serializable;importjava.util.Date;/**
*
* @author gwon
* @history
* 2019. 6. 4. initial creation
*/publicclassPeriodGoodimplementsSerializable{// compare : 방어적 복사를 위해 final 제거 됨privateDatestart;privateDateend;publicPeriodGood(Datestart,Dateend){this.start=newDate(start.getTime());this.end=newDate(end.getTime());if(this.start.compareTo(this.end)>0){thrownewIllegalArgumentException();}}// 유효성 체크를 하는 역직렬화 readObjectprivatevoidreadObject(ObjectInputStreams)throwsClassNotFoundException,IOException{s.defaultReadObject();// compare : private 필드를 모두 방어적으로 복사해서 해당 참조값이 변경되도 어떠한 영향이 없도록 함.start=newDate(start.getTime());end=newDate(end.getTime());if(this.start.compareTo(this.end)>0){thrownewIllegalArgumentException();}}@OverridepublicStringtoString(){return"Period [start="+start+", end="+end+"]";}}