目录
序列化
反序列化
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方法又会牵引出各种相关利用链打法。