对Java解序列化早作防备(二)
t (59 bytes) com.ibm.ba.scg.LookAheadDeserializer.Bicycle
serialVersionUID (8 bytes) 0x4FDAAF97F8CCC0DA = 5754104541168320730
classDescInfo
classDescFlags (1 byte) 0x02 = SC_SERIALIZABLE
fields
count (2 bytes) 3
field[0]
primitiveDesc
prim_typecode (1 byte) I = integer
fieldName
length (2 bytes) 2
text (2 bytes) id
field[1]
primitiveDesc
prim_typecode (1 byte) I = integer
fieldName
length (2 bytes) 9
text (9 bytes) nbrWheels
field[2]
objectDesc
obj_typecode (1 byte) L = object
fieldName
length (2 bytes) 4
text (4 bytes) name
className1
TC_STRING (1 byte) 0x74
length (2 bytes) 0x12 = 18
text (18 bytes) Ljava/lang/String;
classAnnotation
TC_ENDBLOCKDATA (1 byte) 0x78
superClassDesc
TC_NULL (1 byte) 0x70
classdata[]
classdata[0] (4 bytes) 0 = id
classdata[1] (4 bytes) 1 = nbrWheels
classdata[2]
TC_STRING (1 byte) 0x74
length (2 bytes) 8
text (8 bytes) Unicycle
从清单3中你可以看到该序列化对象的类型为com.ibm.ba.scg.LookAheadDeserializer.Bicycle,它的ID为0,只有一个轮子,即它是一个独轮车。
重点是这个二进制格式包含一种文件头,这就允许你对输入进行校验。
类校验
如你在清单3中所看到的,在读取该二进制流时,在对象本身出现之前,首先会看到该序列化对象的类型描述。这种结构就允许实现自己的算法去读取类型描述,并依靠类的名称去决定是否继续读取该序列化流。幸运地是,通过使用Java提供的一个常用于定制类加载的"钩子",你能很容易地实现该功能--即,覆盖resolveClass()方法。这个"钩子"方法非常适合用于提供定制的校验功能,无论序列化流何时包含了不被期望的类,你都可以用这个方法去抛出一个异常。你需要继承类java.io.ObjectInputStream,并覆盖其中的resolveClass()方法。清单4中的代码就利用该项技术确保只有Bicycle类的实例才可被解序列化。
清单4. 定制校验"钩子"程序
package com.ibm.ba.scg.LookAheadDeserializer;
import java.io.IOException;
import java.io.InputStream;
import java.io.InvalidClassException;
import java.io.ObjectInputStream;
import java.io.ObjectStreamClass;
import com.ibm.ba.scg.LookAheadDeserializer.Bicycle;
public class LookAheadObjectInputStream extends ObjectInputStream {
public LookAheadObjectInputStream(InputStream inputStream)
throws IOException {
super(inputStream);
}
/**
* Only deserialize instances of our expected Bicycle class
*/
@Override
protected Class< > resolveClass(ObjectStreamClass desc) throws IOException,
ClassNotFoundException {
if (!desc.getName().equals(Bicycle.class.getName())) {
throw new InvalidClassException(
"Unauthorized deserialization attempt",
desc.getName());
}
return super.resolveClass(desc);
}
}
通过对com.ibm.ba.scg.LookAheadDeserializer类的实例调用readObject()方法,就可以防止对不被期望的对象进行解序列化操作。
作为一个示例应用程序,清单5序列化了两个对象--一个是期望的类(com.ibm.ba.scg.LookAheadDeserializer.Bicycle)的实例,另一个是不被期望的类(java.io.File)的实例--然后使用清单4中的定制校验"钩子"程序去尝试它们进行解序列化。
清单5. 使用定制的"钩子"程序
package com.ibm.ba.scg.LookAheadDeserializer;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.Fil