郑国祥,安恒信息技术有限公司Web安全研究员。从事web安全研究——漏洞挖掘,源码审计,web防御。2015年获得腾讯的TSRC漏洞之王,2016年活的御洞圣杰的称号。
Struts2的基本的工作原理
Struts2提交HTTP的请求,进行Action Map的映射,解析URL,解析之后,进行任务链的调度,把某一个URL请求映射进来。这个会加载进来,解析完成之后,进行Map链式的存储。处理完成,进行Action的一个调用,Action调用完成,通过返回结果,查找到对应内容,返回对应的模板。模板处理完成之后,再调用后处理的拦截器,内容处理完封装成一个Response,反馈给用户,
Struts2的漏洞历史
从2007年的时候S2-001,后面像S2-002、S2-003等等,到现在是S2-046。Struts2的001到046过程当中,所有的严重漏洞大家可以看到有这么几个。这是2007年的S2-001,问题出现在验证表单的时候,如果这个表单内容验证失败的话会执行一个表达式,导致一个代码执行。后面就是2008年的时候,S2-003,它是在一个参数拦截器里面产生了一个OGNL表达式的执行。2010年S2-005,它绕过了之前的那种防护方式,执行了一些OGNL的表达式,导致了一些代码的执行。S2-008默认的情况下开了一个DevMod。2012年的S2-009和2013年的S2-013,还有2013年7月份的S2-016,大家比较熟悉的就是2013年的S2-016,因为这个漏洞当时产生的影响是非常巨大的,包括很多政府金融等一些产业的互联网的厂商,都因为这个漏洞造成了一些损失。大概在4年之后,就是今年的4月份,我们安恒发现了一个S2-045,可能会比之前S2-016等等那些漏洞危害性更大,因为它覆盖的版本及范围可能会更广泛。它影响的范围,比如说是2.3.5,其实2.3.5是在S2-016里面的,在S2-016受影响的,可能也会受S2-045或者是S2-046的影响。后面比如说2.3.5到2.3.10,后面就是一个2.5到2.5.10。我们看2.5到2.5.10的话,2.5后面基本上没有出过什么比较严重的漏洞,后面包括2.5到2.5.10是公认的比较安全的,这次可能基本上都沦陷了。它主要的问题就是,因为在处理一个文件上传请求的时候,一个文件上传的请求程序异常,被Struts2捕获了,就会做一个解析。
S2-045的漏洞危害
攻击者可以制造一些恶意的HTTP数据包,利用这个漏洞在受影响的数据库上执行任意的系统命令。一般系统画像,很多用户基本上都是有了这个漏洞,基本上直接一丢过去就可以拿到权限,包括造成了一些拒绝服务、数据泄漏,或者是网站上一些数据的提取等等。而且这个漏洞不像之前的漏洞,很多银行的网站开了一个DMI的话,产生了一个远程代码执行,但是S2-045、S2-045不需要任何前置条件,也不需要提供任何插件。文件上传加包,默认是在Struts2的框架里面的,所以说这个漏洞是非常严重的。
安恒Sumap统计
我们对全网做了统计,美国、中国等Struts2的使用量非常巨大在爆发的时候,有一部分是在S2-016的影响下,可能修复了S2-016,因为后面没有出现比较大的漏洞,基本上不会去升级。现在出了S2-045,Struts2都是有问题的,除了S2-001,所以这个影响是非常巨大的,这边基本上都是红色的状况。这是中国区范围的分布,比如浙江、四川等等一些地方,虽然使用量非常小,在银行里面会使用得比较广泛。
这是S2-045漏洞修复后的一个图。大概爆发之后,基本上修复厂商出补丁也是很快的,后面基本上都修复了。修复之后,这边的颜色对比可能会浅了很多。但是量还是非常大的,还是存在大量的有漏洞的机器。这边是中国的一个显示,比如像浙江的、广东的,四川的是明显的减少了,这边是很红的一片,现在颜色都变淡了。
如何挖掘此类漏洞
这里针对的是Struts2的一个介绍。首先可以利用Fuzz的一些技术,一个HTTP的请求包,包括请求方式,还有后面跟着是一个请求URL,后面就是一个HTTP的协议版本,下面就是一个HTTP的一个头,后面跟着就是一个对应。这些是正常的请求,边上对应的像Reponse200的话,就是对应的是Response。这是一个HTTP的Post的请求,有些会在包里面有很多的数据。这是一个Post的一种形式,可以通过这种方式做一些文件的上传,包括有一些跟原始的不太一样的。如果没有加文件名的话就是一个普通的表单,但是如果加上文件名的话,就是一个文件类型的。
我们大致了解了HTTP请求大致是什么格式之后,我们可以想办法做一些Fuzz。我们可以选一些Payload,我们知道表达式里面的一些Payload基本上都是通过百分号,一个大括号,这是下面0.UNIQUEID是一个普通的,像Method:,这也是Struts2里面支持的一些特性,这是参数前缀。还有Action,现在可能不支持了,现在可能支持上面两种,还有一些其他类型也可以加进去。为什么要加0.UNIQUEID,后面可能会讲到。基本上Fuzz-Payload就是这些,也可以自己加。所有的Payload可以自己加。
我们要利用Java Jvmti技术去Hook,在解析OGNL表达式的时候产生一些异常。之前Fuzz的Payload里面加了一个0.,这样做的方式就是为了让OGNL解析这个表达式的时候产生异常。产生这个异常的时候,会把这个异常的一些字符串,包括0.UNIQUEID,就是一个特征值,会把字符串抛出来,表达式里面异常的是一个语法异常的类,而我们可以通过某种方式,Hook掉之后,因为出异常的都会去调这样一个类。所以说我们把这个类提取出来之后,我们可以去判断这个异常的信息里面是否包含之前讲的这样一个特征,就是UNIQUEID,如果包含的话,我们就可以认为这个请求过来,我们可以获取到一些恶意的Payload,可以进入到我们的表达式里面执行,这样的话,我们就可以拿来做一些利用。包括我们分析整个链路,拿到这个链路请求之后,我们可以做一些简单的服务。
这是我们之前发现S2-045的一个Fuzz的结果。这边有一个Struts2的版本,之前测试的时候在2.3.31上做的一个Fuzz,包括Fuzz的Payload是怎么样的,这边显示的是发现了一个Struts2的,下面是一个SEQ的特征,我们可以看到原始的请求数据包。下面是一个函数的调用站,这个请求到漏洞触发点整个函数的调用流程是怎么样的,我们都可以从这个函数的调用站里面找到。我们可以看一下,这是之前Fuzz出来的一个解析请求的类。就是因为这边会做一个解析,会把HTTP的请求包调用出来之后,去做一个请求包的解析。如果出现异常的话,他会把这个异常捕获起来。捕获起来的话,会去封装一个错误的消息类。这个错误的消息类里面的一些抛出来的异常里面的那些值,都是被我们可以控制的。这个控制又丢给OGNL表达式去执行,直接造成一个远程代码执行。
根据这个异常的报错信息,我们在跑Struts2应用的时候都会有一些日志信息,我们可以根据日志信息去查找到底是哪些加包出问题了。这边就是一个标准的调用站的错误链,比如说这边应该是一个文件Base的那一类,再做解析,HTTP请求的时候,导致了这样一个类。根据这个类,我们可以去查找,这是一个类里面的,这里显示有一种情况下,会抛一个可控的异常点。这样的话,我们就可以利用这种方式直接做一些代码的执行。
碰到这种情况,我们还可以去考虑另外一种情况。并不是只有这种情况下才能抛出可控的异常,我们可以把完整的一些源码下载下来之后,可以通过走所Throw New等方式,我们可以把所有的异常出来的点都搜一遍,我们可以看哪些异常的错误信息可以控制。比如说这边就有一个其实可以被当作我们可以控制的那些异常点来做处理,丢给表达式去执行。还有一个上传上来一个文件,文件名里面如果包含了一个0字节的时候,就会抛一个可控的异常点。这两种情况下,后面是可以控制的S2-046的Bypass的这种情况。
我们之前的步骤仅仅只是找到了那些可以利用的一些输入点。比如说Struts2里面默认有一个Security Member Access,就算你拿到了一个执行的权限,比如执行一些系统命令,这些是有Security Member Access的保护,我们要突破这些防护。这里是做了一个黑名单,包括哪些Class是无法加载的,OGNL里面的一些内容,下面OGNL的一些Access,包括在S2-032的时候,因为利用了Bypass的技术,就加入了白名单里面了。还有黑名单的一些包名,包括一些Java的一些,还有几个类可以Bypass之前的一个黑名单的防护,这些都加进去了。但是我们看了这些黑名单之后,就必须要想办法去突破这些黑名单的防护。
首先我们可以来关注一下OGNL表达式里面用的最多的就是一个OGNL Context。这个里面有好几个Map里面,OGNL Context里面就是一个Map,有好几个属性,像一些字符串等等。这些字符串我们拿来根本就没法用,没法修改,比如像Context里面的一些属性,我们可以看到有一个属性可以控制。但是这个Context是属于第9个属性,这个属性在OGNL表达式里面,Context维护了所有的Struts2的一个框架的类。我们可以看一下,这边就是一些黑名单的类,Context没有在黑名单的那些包里面或者类里面。我们可以看一下什么是Context,翻译成中文的意思就是一个容器,通常来讲,它支持了Struts2的一些运行环境,包括哪个类需要哪些属性,或者哪个类需要哪些对象,可以通过Container注射进去,可以写一个标签一样的东西,包括还包含了一些第一例的条件控制,包括Struts2运行起来是否是DEV的模式,包括像Security Member Access初始化限制的一些类,就是之前在这个PPT里面介绍的这些,都是通过Context的方式注册进去的。我们可以看到,这边就是一个Context Build简单的构造,包括Struts DEV很多的属性,也会加载Struts里面的一些默认的属性,包括哪些类可以加载,哪些类不可以加载,都是在Container的一个容器里面。
了解了Container之后,我们了解一下Security Member Access的初始化过程。我们可以看到,所有的表达式通过工厂模式来创建的。我们可以看到,比如这里有一个方法,OGNL Value Stack,可以把一些属性注册到Container里面去,包括还有一个把Allow Static Method Acess的属性,注册过来,有这样一个标签一样的内容。这边是OGNL Value Stack的一个类,会构建一个防护。我们可以看到,在过程当中的一些内容,包括设置一个黑名单,就是Security Member Access黑名单设置一些属性,包括哪些类或者哪些包下载一些类,都不允许调用的,这个属性值都是通过OGNL这个类里面获取的。
这边是一个OGNL Util里面,Struts-Default.xml里面,就是一个单域模型,表示只有一个OGNL Util的识别,我们只要控制这个,就可以控制Security Member Access里面的一些包括黑名单的包名,黑名单的一系列东西。因为Container又提供了不在黑名单里面的,我们可以通过操控Container,获取OGNL Util的对象,通过这个对于,我们可以把里面的一些东西设置为空,这样的话,Security Member Access这些黑名单属性就相当于是空的。我们所有的一些限制类,包括像Java、反射的一些属性都是可以绕过,可以直接去调用的。
这边Security Member Access的EXP,是绕过防护限制的。首先会定义一个Default Member的Access,一种是老版本里面的Struts2,有一个Security Member,我们只需要去覆盖Security Member Access就可以,这是老版本里面的情况。还有一种就是在新版本里面,在OGNL Container里面没有这个对象的应用,所以说我们需要通过刚才讲的那种方式,通过Container去获取OGNL Until之后,再把这个OGNL Unitl里面的一些黑名单、包名抹掉,再设置回去。然后再通过Security Member Access去覆盖原始的Security Member Access。这样的话,Defaul Member就是为了允许方法的调用,可以直接调用Jave里面其执行一些系统的命令。这里就是兼容了老版本跟新版本的一个情况。
以后Struts2可能还会出现S2-047、S2-048,大家可以对OGNL表达式解析的语法做一些Hook,可以做监控,做Fuzz,基本上可以Fuzz出来。包括一些逻辑上的问题,比如说可能在某个Action里面应用了某个类,但是在Struts2内部,某些地方也通过某种方式去获取属性,获取到这些属性之后,直接用OGNL表达式进行,就可以控制这个表达式。挖掘这种漏洞可能需要一些时间,还有应急方面的成份。
上一篇:腾讯徐少培:浏览器地址栏之困
下一篇:陈钟:主持人