目录
序列化
反序列化
Hessian1.0
Hessian2.0
Hessian反序列化核心:MapDeserializer#readMap的利用
总结
序列化
HessianOutput&Hessian2Output都是抽象类AbstractHessianOutput的实现类
HessianOutput#writeObject和Hessian2Output#writeObject的写法是一样的
首先获取序列化器 Serializer
的实现类,并调用其 writeObject
方法序列化数据
public void writeObject(Object object) throws IOException {if (object == null) {this.writeNull();} else {Serializer serializer = this.findSerializerFactory().getObjectSerializer(object.getClass());serializer.writeObject(object, this);}}
对于自定义类型,默认情况下会用 UnsafeSerializer来进行序列化相关操作(这里先不讲为什么,反序列化部分会讲的,简单类比即可,莫急)
关注UnsafeSerializer#writeObject,
该方法兼容了 Hessian/Hessian2 两种协议的数据结构,会调用传入的AbstractHessianOutput的writeObjectBegin
方法开始写入数据
public void writeObject(Object obj, AbstractHessianOutput out) throws IOException {if (!out.addRef(obj)) {Class<?> cl = obj.getClass();int ref = out.writeObjectBegin(cl.getName());if (ref >= 0) {this.writeInstance(obj, out);} else if (ref == -1) {this.writeDefinition20(out);out.writeObjectBegin(cl.getName());this.writeInstance(obj, out);} else {this.writeObject10(obj, out);}}}
看一下AbstractHessianOutput#writeObjectBegin
public int writeObjectBegin(String type) throws IOException {this.writeMapBegin(type);return -2;}
虽然writeObjectBegin
是 AbstractHessianOutput 的方法,但并不是每个实现类都对其进行了重写
如HessianOutput就没有,而Hessian2Output对其进行了以下重写
public int writeObjectBegin(String type) throws IOException {int newRef = this._classRefs.size();int ref = this._classRefs.put(type, newRef, false);if (newRef != ref) {if (8192 < this._offset + 32) {this.flushBuffer();}if (ref <= 15) {this._buffer[this._offset++] = (byte)(96 + ref);} else {this._buffer[this._offset++] = 79;this.writeInt(ref);}return ref;} else {if (8192 < this._offset + 32) {this.flushBuffer();}this._buffer[this._offset++] = 67;this.writeString(type);return -1;}}
那么也就是说写入自定义数据类型(Object)时,Hessian1.0会调用
writeMapBegin
方法将其标记为 Map 类型,调用writeObject10来写入自定义数据。Hessian2.0将会调用writeDefinition20
和Hessian2Output#writeObjectBegin
方法写入自定义数据,就不将其标记为 Map 类型。
反序列化
HessianInput&Hessian2Input都是抽象类AbstractHessianInput的实现类
Hessian1.0
对于Hessian1.0,正如我们上文所说,在写入自定义类型时会将其标记为 Map 类型,所以HessianInput 中,没有针对 Object 的读取,而是都将其作为 Map 读取
由于Hessian会将序列化的结果处理成一个Map,所以序列化结果的第一个byte
总为M
(ASCII为77),然后HessianInput#readObject的那一长串switch case根据的tag就是取的第一个byte的ASCII值
跟一下HessianInput#readObject,果然停到了77
跟进_serializerFactory.readMap
public Object readMap(AbstractHessianInput in, String type) throws HessianProtocolException, IOException {Deserializer deserializer = this.getDeserializer(type);if (deserializer != null) {return deserializer.readMap(in);} else if (this._hashMapDeserializer != null) {return this._hashMapDeserializer.readMap(in);} else {this._hashMapDeserializer = new MapDeserializer(HashMap.class);return this._hashMapDeserializer.readMap(in);}}
先是调用getDeserializer
public Deserializer getDeserializer(Class cl) throws HessianProtocolException {Deserializer deserializer;if (this._cachedDeserializerMap != null) {deserializer = (Deserializer)this._cachedDeserializerMap.get(cl);if (deserializer != null) {return deserializer;}}deserializer = this.loadDeserializer(cl);if (this._cachedDeserializerMap == null) {this._cachedDeserializerMap = new ConcurrentHashMap(8);}this._cachedDeserializerMap.put(cl, deserializer);return deserializer;}
接着调用loadDeserializer
protected Deserializer loadDeserializer(Class cl) throws HessianProtocolException {Deserializer deserializer = null;for(int i = 0; deserializer == null && this._factories != null && i < this._factories.size(); ++i) {AbstractSerializerFactory factory = (AbstractSerializerFactory)this._factories.get(i);deserializer = factory.getDeserializer(cl);}if (deserializer != null) {return deserializer;} else {deserializer = this._contextFactory.getDeserializer(cl.getName());if (deserializer != null) {return deserializer;} else {ContextSerializerFactory factory = null;if (cl.getClassLoader() != null) {factory = ContextSerializerFactory.create(cl.getClassLoader());} else {factory = ContextSerializerFactory.create(_systemClassLoader);}deserializer = factory.getDeserializer(cl.getName());if (deserializer != null) {return deserializer;} else {deserializer = factory.getCustomDeserializer(cl);if (deserializer != null) {return deserializer;} else {Object deserializer;if (Collection.class.isAssignableFrom(cl)) {deserializer = new CollectionDeserializer(cl);} else if (Map.class.isAssignableFrom(cl)) {deserializer = new MapDeserializer(cl);} else if (Iterator.class.isAssignableFrom(cl)) {deserializer = IteratorDeserializer.create();} else if (Annotation.class.isAssignableFrom(cl)) {deserializer = new AnnotationDeserializer(cl);} else if (cl.isInterface()) {deserializer = new ObjectDeserializer(cl);} else if (cl.isArray()) {deserializer = new ArrayDeserializer(cl.getComponentType());} else if (Enumeration.class.isAssignableFrom(cl)) {deserializer = EnumerationDeserializer.create();} else if (Enum.class.isAssignableFrom(cl)) {deserializer = new EnumDeserializer(cl);} else if (Class.class.equals(cl)) {deserializer = new ClassDeserializer(this.getClassLoader());} else {deserializer = this.getDefaultDeserializer(cl);}return (Deserializer)deserializer;}}}}}
我们主要看下面这两段逻辑
①
代码使用 Map.class.isAssignableFrom(cl) 条件来检查变量 cl 是否实现了 Map 接口。
如果条件成立(即 cl 实现了 Map 接口),则代码创建一个 MapDeserializer 对象,并将变量 cl 作为参数传递给构造方法。
②
如果那一连串条件判断都不符合(即自定义类),则调用getDefaultDeserializer
跟进getDefaultDeserializer
protected Deserializer getDefaultDeserializer(Class cl) {if (InputStream.class.equals(cl)) {return InputStreamDeserializer.DESER;} else {return (Deserializer)(this._isEnableUnsafeSerializer ? new UnsafeDeserializer(cl, this._fieldDeserializerFactory) : new JavaDeserializer(cl, this._fieldDeserializerFactory));}}
而_isEnableUnsafeSerializer默认为true
this._isEnableUnsafeSerializer = UnsafeSerializer.isEnabled() && UnsafeDeserializer.isEnabled();
这也就是说,对于自定义类型,默认情况会用 UnsafeDeserializer
来进行反序列化相关操作
这里我要多说一句,77那一处仅仅是传入的类被“标记为Map”,而不是让这个类强转为Map,具体说就是让第一个byte为M,因此这并不影响后续getDeserializer还是得看该类本身的属性,如果其是自定义类,那么还是会得到UnsafeDeserializer
比如我这里自定义了一个Person类,反序列化虽然进到77,但不妨碍取的还是UnsafeDeserializer
再者,我反序列化了一个HashMap,同样是进到77,但取到的则是MapDeserializer
OK小插曲到此为止。
取到deserializer之后,便会调用deserializer.readMap
而Hessian反序列化漏洞的利用的正是MapDeserializer.readMap,这里暂按下不表。
Hessian2.0
对于Hessian2.0,正如一开始所说,序列化时Hessian2.0不会将传入的类标记为Map类型,所以在进Switch判断时就纯看自身了。
但宗旨是不变的,你是什么类,实现什么接口,你就会取到什么对应的deserializer
如果你想取到MapDeserializer,那传入的类就必须与Map相关
我们尝试用Hessian2Input来反序列化一个HashMap
Hessian2Input#readObject进到72
成功取到MapDeserializer
取到MapDeserializer 之后,便会调用其readMap方法
Hessian反序列化核心:MapDeserializer#readMap的利用
public Object readMap(AbstractHessianInput in) throws IOException {Object map;if (this._type == null) {map = new HashMap();} else if (this._type.equals(Map.class)) {map = new HashMap();} else if (this._type.equals(SortedMap.class)) {map = new TreeMap();} else {try {map = (Map)this._ctor.newInstance();} catch (Exception var4) {throw new IOExceptionWrapper(var4);}}in.addRef(map);while(!in.isEnd()) {((Map)map).put(in.readObject(), in.readObject());}in.readEnd();return map;}
啰嗦地解读一下
-
首先,代码定义了一个变量
map
用于存储读取后的映射对象。 -
接着,通过一系列条件判断来确定要实例化的具体映射类型:
- 如果
_type
为 null,则实例化一个 HashMap 对象并赋值给map
。 - 如果
_type
类型为 Map.class,则同样实例化一个 HashMap 对象。 - 如果
_type
类型为 SortedMap.class,则实例化一个 TreeMap 对象。 - 否则,尝试通过反射实例化
_ctor
表示的类,并将结果赋值给map
。如果实例化过程中出现异常,则抛出 IOException。
- 如果
-
在确定了要实例化的映射类型后,代码调用
in.addRef(map)
将实例化后的映射对象添加到引用表中。 -
进入一个循环,通过
in.isEnd()
方法检查输入流是否结束。在循环中,代码通过in.readObject()
方法读取键值对并将其放(put)进映射对象中。(注意这里调用的还是HessianInput&Hessian2Input的readObject方法,而非原生,只不过是目标是属性,比如一个HashMap里存了一个key为EqualsBean,那么就会按照EqualsBean的特性走一遍switchcase等等等等流程再存进HashMap里) -
循环直到输入流结束标记被读取。
-
最后,代码调用
in.readEnd()
方法读取映射的结束标记,并返回读取、填充后的映射对象map
。
其实说那么多,关键就在那个put的点上
map.put对于HashMap会触发key.hashCode()、key.equals(k),而对于TreeMap会触发key.compareTo()
总结
当Hessian反序列化Map
类型的对象的时候,会自动调用其put
方法,而put方法又会牵引出各种相关利用链打法。