Java反序列化漏洞通用利用分析

1 背景

2015年11月6日,FoxGlove Security安全团队的@breenmachine 发布的一篇博客[3]中介绍了如何利用Java反序列化漏洞,来攻击最新版的WebLogic、WebSphere、JBoss、Jenkins、OpenNMS这些大名鼎鼎的Java应用,实现远程代码执行。

然而事实上,博客作者并不是漏洞发现者。博客中提到,早在2015年的1月28号,Gabriel Lawrence (@gebl)和Chris Frohoff (@frohoff)在AppSecCali上给出了一个报告[5],报告中介绍了Java反序列化漏洞可以利用Apache Commons Collections这个常用的Java库来实现任意代码执行,当时并没有引起太大的关注,但是在博主看来,这是2015年最被低估的漏洞。

确实,Apache Commons Collections这样的基础库非常多的Java应用都在用,一旦编程人员误用了反序列化这一机制,使得用户输入可以直接被反序列化,就能导致任意代码执行,这是一个极其严重的问题,博客中提到的WebLogic等存在此问题的应用可能只是冰山一角。

虽然从@gebl和@frohoff的报告到现在已经过去了将近一年,但是@breenmachine的博客中提到的厂商也依然没有修复,而且国内的技术人员对这个问题的关注依然较少。为了帮助大家更好的理解它,尽快避免和修复这些问题,本文对此做了一个深入的漏洞原理和利用分析,最后对上面提到的这些受影响的应用,在全球范围内做一个大概的统计。

2 Java反序列化漏洞简介

序列化就是把对象转换成字节流,便于保存在内存、文件、数据库中;反序列化即逆过程,由字节流还原成对象。Java中的 ObjectOutputStream类的 writeObject()方法可以实现序列化,类 ObjectInputStream类的 readObject()方法用于反列化。下面是将字符串对象先进行序列化,存储到本地文件,然后再通过反序列化进行恢复的样例代码:

问题在于,如果Java应用对用户输入,即不可信数据做了反序列化处理,那么攻击者可以通过构造恶意输入,让反序列化产生非预期的对象,非预期的对象在产生过程中就有可能带来任意代码执行。

所以这个问题的根源在于类 ObjectInputStream在反序列化时,没有对生成的对象的类型做限制;假若反序列化可以设置Java类型的白名单,那么问题的影响就小了很多。

反序列化问题由来已久,且并非Java语言特有,在其他语言例如PHP和Python中也有相似的问题。@gebl和@frohoff的报告中所指出的并不是反序列化这个问题,而是一些公用库,例如Apache Commons Collections中实现的一些类可以被反序列化用来实现任意代码执行。WebLogic、WebSphere、JBoss、Jenkins、OpenNMS这些应用的反序列化漏洞能够得以利用,就是依靠了Apache Commons Collections。这种库的存在极大地提升了反序列化问题的严重程度,可以比作在开启了ASLR地址随机化防御的系统中,出现了一个加载地址固定的共享库,或者类似twitter上的评论中的比喻:

image

@breenmachine的博客中将漏洞归咎于Apache Commons Collections这个库,存在一定的误解。

3 利用Apache Commons Collections实现远程代码执行

参考Matthias Kaiser在11月份的报告[1],我们以Apache Commons Collections 3为例,来解释如何构造对象,能够让程序在反序列化,即调用 readObject()时,就能直接实现任意代码执行。

Map类是存储键值对的数据结构,Apache Commons Collections中实现了类 TransformedMap,用来对 Map进行某种变换,只要调用 decorate()函数,传入key和value的变换函数 Transformer,即可从任意 Map对象生成相应的 TransformedMapdecorate()函数如下:

Transformer是一个接口,其中定义的 transform()函数用来将一个对象转换成另一个对象。如下所示:

Map中的任意项的Key或者Value被修改,相应的 Transformer就会被调用。除此以外,多个 Transformer还能串起来,形成 ChainedTransformer

Apache Commons Collections中已经实现了一些常见的 Transformer,其中有一个可以通过调用Java的反射机制来调用任意函数,叫做 InvokerTransformer,代码如下:

只需要传入方法名、参数类型和参数,即可调用任意函数。因此要想任意代码执行,我们可以首先构造一个 Map和一个能够执行代码的 ChainedTransformer,以此生成一个 TransformedMap,然后想办法去触发 Map中的 MapEntry产生修改(例如 setValue()函数),即可触发我们构造的Transformer。

测试代码如下:

当上面的代码运行到 setValue()时,就会触发 ChainedTransformer中的一系列变换函数:首先通过 ConstantTransformer获得 Runtime类,进一步通过反射调用 getMethod找到 invoke函数,最后再运行命令 calc.exe

但是目前的构造还需要依赖于触发 Map中某一项去调用 setValue(),我们需要想办法通过 readObject()直接触发。

我们观察到java运行库中有这样一个类 AnnotationInvocationHandler,这个类有一个成员变量 memberValuesMap类型,如下所示:

更令人惊喜的是, AnnotationInvocationHandlerreadObject()函数中对 memberValues的每一项调用了 setValue()函数,如下所示:

因此,我们只需要使用前面构造的 Map来构造 AnnotationInvocationHandler,进行序列化,当触发 readObject()反序列化的时候,就能实现命令执行。另外需要注意的是,想要在调用未包含的package中的构造函数,我们必须通过反射的方式,综合生成任意代码执行的payload的代码如下:

以上解释了如何通过Apache Commons Collections 3这个库中的代码,来构造序列化对象,使得程序在反序列化时可以立即实现任意代码执行。

我们可以直接使用工具yoserial[2][5]来生成payload,当中包含了4种通用的payload:Apache Commons Collections 3和4,Groovy,Spring,只要目标应用的Class Path中包含这些库,yoserial生成的payload即可让 readObject()实现任意命令执行。

yoserial当中针对Apache Commons Collections 3的payload也是基于 TransformedMapInvokerTransformer来构造的,而在触发时,并没有采用上文介绍的 AnnotationInvocationHandler,而是使用了 java.lang.reflect.Proxy中的相关代码来实现触发。此处不再做深入分析,有兴趣的读者可以参考yoserial的源码。

4 漏洞利用实例

4.1 利用过程概述

首先拿到一个Java应用,需要找到一个接受外部输入的序列化对象的接收点,即反序列化漏洞的触发点。我们可以通过审计源码中对反序列化函数的调用(例如 readObject())来寻找,也可以直接通过对应用交互流量进行抓包,查看流量中是否包含java序列化数据来判断,java序列化数据的特征为以标记(ac ed 00 05)开头。

确定了反序列化输入点后,再考察应用的Class Path中是否包含Apache Commons Collections库(yoserial所支持的其他库亦可),如果是,就可以使用yoserial来生成反序列化的payload,指定库名和想要执行的命令即可:

通过先前找到的传入对象方式进行对象注入,数据中载入payload,触发受影响应用中 ObjectInputStream的反序列化操作,随后通过反射调用 Runtime.getRunTime.exec即可完成利用。

4.2 WebLogic

参照[3]中的方法,对安装包文件grep受影响的类 InvokerTransformer

接着通过寻找接收外部输入的点,来让我们发送序列化对象。

WebLogic外部只开了一个7001端口,这个端口接受HTTP,T3,SNMP协议,判断协议类型后再把数据路由到内部正确的位置,通过在server上抓包,发现走T3协议时携带了java序列化对象,所以我们只用把这个包文从序列化开始的标记(ac ed 00 05)后加入payload,重放这个数据,完成利用。

以下是breenmachine的完整利用脚本:

在weblogic的利用中,有个小坑是不能破坏原始T3协议数据中包装的java对象。

4.3 Jenkins

Jenkins是一个非常流行的CI工具,在很多企业的内网中都部署了这个系统,这个系统常常和企业的代码相关联,这次也受到了Java反序列化漏洞的影响,非常危险。

同样,通过grep受影响的类 InvokerTransformer

在开放的端口上抓包,定位到Jeenkins的CLI包文中的序列化开始标记(rO0)。 在发送CLI的第一个包文后:

在标记位的地方将base64处理过的payload修改覆盖原始包文中的序列化对象,发包后,完成利用。这里给出一个演示视频:

以下是@breenmachine的完整利用脚本:

4.4 Jboss

Jboss受影响的情况就比之前Jenkins逊色不少,正如之前所说,要成功利用必须要找到程序接受外部输入的点,而此处的利用需要/invoker/jmx的支持,大部分情况下的实际场景,jboss都删除了jmx,所以让此处的利用大打折扣。

分析流程和之前一样,只不过此处接受的点在jmx上,所以通过的协议也和前两个不同,是HTTP协议,不再赘述,详细的jboss分析可以参看Exploit – JBoss

利用如下:

也可以看breenmachine给出的http请求报文:

image

4.5 WebSphere

WebSphere的利用相比较之前几个case就非常粗暴简单了,可惜的是很少会暴露在公网。

找到受影响的lib的位置。

查看端口开放情况后发现WebSphere默认起了10个端口监听所有接口,通过burp suite看到在请求websphere默认端口8880上有一个POST的请求,body中带有base64处理后的java序列化对象,同样的,标记位置仍然是"rO0",我们将生成的payload做base64处理后覆盖之前的序列化对象即可利用。

image

4.6 其它

因为这个安全问题的根源在于 ObjectInputStream处理反序列化时接受外部输入,而又由于其他类似 InvokerTransformer的类的构造函数被调用,从而造成执行,而 InvokerTransformer方便的提供了根据外部输入类名函数名反射执行的作用,所以造成整个程序RCE。

所以该问题并不是像其他一些语言unserialize函数本身存在漏洞,而是在应用本身实现的方式上存在缺陷,导致应用受到RCE的影响,开个脑洞引申一下,可以很明了的发现,远远不止breenmachine所指出的这几个流行web server,更可能影响更多使用了 commonscollections,并且触发 ObjectInputStream反序列化操作的应用,如一些java开发的CMS,中间件等等,甚至不仅仅是PC端,移动端如Android的很多app都可能受到该问题影响。

5 漏洞影响

通过简单的全网分析和POC验证。

Jenkins收到该漏洞影响较大,在自测中,全球暴露在公网的 11059台均受到该问题影响,zoomeye的公开数据中再测试后有 12493受到该漏洞影响,shadon的公开数据中 16368台jenkins暴露公网可能受到影响(未复测shadon数据)。

Weblogic因为公开到公网的数据较少,所以受影响面也稍微少一些,在自测中,全球 486台均受到该问题影响,zoomeye的公开数据中再测试后有 201台收到该漏洞影响,shadon的公开数据中 806 台weblogic可能受到影响(未复测shadon数据)。

Jboss因为需要/invoker/JMXInvokerServlet的支持,所以受影响面稍小(但我们并未具体检测jboss中没有删除/invoker/JMXInvokerServlet的数据),在自测中,全球 29194台jboss暴露在公网,但由于大部分jboss都删除了jmx,所以真正受到影响的覆盖面并不广,zoomeye的公开数据中有 7770台jboss暴露在公网,shadon的公开数据中 46317台jboss暴露在公网。

WebSphere在自测中,全球暴露在公网的 2076台均受到该问题影响,zoomeye的公开数据中再测试后仍有 4511台websphere受到影响,shadon的公开数据中 5537 台websphere可能受到影响(未复测shadon数据)。

image

在本次全网分析中,感谢ztz@nsfocus的seer提供的部分数据

6 修复建议

因为受影响的多家厂商在今年1月拿到POC至今都没有对该问题做任何修复,所以短期内并不会有官方补丁放出,如果很重视这个安全问题并且想要有一个临时的解决方案可以参考NibbleSecurity公司的ikkisoft在github上放出了一个临时补丁 SerialKiller

下载这个jar后放置于classpath,将应用代码中的 java.io.ObjectInputStream替换为 SerialKiller,之后配置让其能够允许或禁用一些存在问题的类, SerialKiller有Hot-Reload,Whitelisting,Blacklisting几个特性,控制了外部输入反序列化后的可信类型。

lib地址:https://github.com/ikkisoft/SerialKiller

7 参考资料

  1. Matthias Kaiser - Exploiting Deserialization Vulnerabilities in Java.

  2. https://github.com/frohoff/ysoserial

  3. foxglovesecurity analysis

  4. github JavaUnserializeExploits

  5. appseccali-2015-marshalling-pickles

原文:http://blog.chaitin.com/2015-11-11_java_unserialize_rce/


  1. String obj = “hello world!”;
    FileOutputStream fos = new FileOutputStream(“object.db”);
    ObjectOutputStream os = new ObjectOutputStream(fos);
    os.writeObject(obj); // 将序列化对象写入文件object.db中
    os.close();
    FileInputStream fis = new FileInputStream(“object.db”); // 从文件object.db中读取数据
    ObjectInputStream ois = new ObjectInputStream(fis); // 通过反序列化恢复对象obj
    String obj2 = (String)ois.readObject();
    ois.close();