这是小弟写的第一篇网文,文笔一般,请大家多包涵。近期发生了很多事,使我醒悟要回归自我的同时,要作些改变,所以开始写网文,且把这些网文分享至个人的微信公众号(公众号名:永记士多)和CSDN博客(博客账号:rossrocket,昵称:永记士多)。此微信公众号和CSDN博客现定位为:专门研究web应用的漏洞,性能,并发量,框架选择等问题,技术涵盖编程语言(java,php,python)、应用框架(sturts2,spring thinkphp,Laravel)、操作系统(主要是linux与windows),数据库(mysql,oracle,db2,redis,mongodb等),web service(apache、nginx、lighttpd、tomcat、jboss、weblogic等)等等,力求不做人云亦云的技术人员,要实践验证真理,做web应用里的“生活调查团”。
下面是这一次的小研究:struts2-045漏洞分析。这个是近期比较严重涉及面比较广的一个漏洞,但这不是最新的漏洞,因为就在我分析的当天,另一个重大漏洞(struts2-046漏洞)被通报这了。以下为大家探讨,struts2-045漏洞是一个怎样的漏洞?黑客是怎么利用这个漏洞进行攻击。
1. 漏洞概述
首先看看该漏洞的相关通报:安恒信息安全研究院WEBIN实验室高级安全研究员nike.zheng发现著名J2EE框架——Struts2存在远程代码执行的严重漏洞; 目前Struts2官方已经确认漏洞(漏洞编号S2-045,CVE编号:cve-2017-5638),并定级为高风险,影响版本范围为Struts 2.3.5 – Struts 2.3.31和Struts 2.5 – Struts 2.5.10。Struts2作为老著名的老牌的mvc框架,这里就不多说。这里介绍一下其危险性。
1.1漏洞的危害性
有些网站把此漏洞定性为“strut2史上最大的漏洞”。是不是最大的漏洞的,不好说,但其危害性是巨大的,这是毋庸置疑的,因为这个漏洞能允许黑客远程代码执行,相当于整台服务器已托管给黑客,黑客可以利用此漏洞获取web应用的源程序,修改web应用内容,获取数据库密码并盗取数据库信息,更改系统代码,更改数据库密码等等。以下我们做个POC测试:
(一).查看数据在库的地址与密码
(1)先查看当前目录,从中我们可以知道使用tomcat的目录,那么就可以知道server.conf的:
(2)能过脚本可以查找server.xml和jdbc.properties的路径分别为: F:\tmp\Apache Software Foundation\apache-tomcat-7.0.76\conf 和F:\tmp\Apache Software Foundation\apache-tomcat-7.0.76\webapps\ROOT\WEB-INF\classes。
(3)显示jdbc.properties的内容,从而得到数据库的地址与密码:
(二)更改页面代码
可以利用此漏洞修改任何的代码文件,如我要更改首页的页面,要其显示”Diaoyu Islands are Chinese”,可进行如下操作:
在浏览器输入地址http://192.168.10.170:8080/index,显示下信息表示修改成功:
1.2漏洞的条件
此漏洞另一个危害性是其几乎无条件性。只要使用了struts2框架的应用,所有的url请求都会被此漏洞利用,即使应用中没有上传功能或使用第三方插件上传文件(如ueditor,kindeditor)也有此问题,除非修改了配置struts.multipart.parser为cos或pell。但这种情况非常少,因为大部分的struts2应用会使用apache的commons-fileupload为上传文件的解析器,而不配置struts.multipart.parser或配置为默认的jakarta,那么大部分的应用都会存在此漏洞。
2. 原因分析
这样的漏洞,我一开始以为是struts2应用中存在调用cmd或shell的代码需被黑客利用,但经过分析,发现struts2代码中不存在runtime.exe()或ProcessBuilder.start() 等语句。后来经过我一轮的分析与派查,才发现此漏洞的本质是Ognl表达式注入,与SQL流入有异曲同式之秒。下面深入分析此漏洞的原因。
2.1 Ognl简述
既然此漏洞是一个Ognl漏洞,那么先了解一下什么是Ognl,是用来干什么的。先看看网上截取的有关Ognl的描述。
这段话我们只要知道两点就行了:1.Ognl是个一个表达式语言。2.可以用来获取和设置java对象的属性。其实针对此次漏洞我们可以理解Ognl有以下功能:通一定语法组装的字符串,经解释后,可以获取对象属性或调用对象的方式(与struts2标签库有点相似)。这是一个非常容给黑客攻击的特性来的。
2.2 Strtuts2代码分析
现在我们分析一下Struts2的在什么情况下会调用Ognl表达式。下面的流程图是代码执行的过程。
针对此流程图,作以下分析:
a. 对序号(1)的方法,StrutsPrepareAndExecuteFilter的doFilter方法是Struts2框架的入口。所有web请求都会执行此方法后进入相应的Action类调用相应的方法。
b. 对序号(2)的方法,PrepareOperations的wrapRequest方法包装出Struts2自己的request对象。
c. 对序号(4)的判断,此判断为关键,下面的POC实例中的第一个表达式就是为了些判断为真,而调用JakartaMultiPartRequest类。
d. 对序号(6)的方法,调JakartaMultiPartRequest的parse方法对文件解码,而此方法捕获序号(7)和序号(8)方法的异常,发现异常则调用序号(9)的方法。具体代码如下图:
e. 对序号(8)的方法,JakartaMultiPartRequest的parse方法是一定会抛出异常的,因为这是POC代码里HEADER的内容根本不是“multipart/form-data”编码的,而是一段Ognl表达式。所以是一定会走到序号(9)的。
f. 对序号(9)的方法,LocalizedTextUtil的findText是xwork框架上的工具库方法。LocalizedTextUtil是xwork解决我们前端国际化问题的一个方案。其中findText是搜索关键字的方法,里面是一个复杂的算法,但是其中有针对里面的参数defautlMessage(e.getMessage())调用了Ognl表达式解释方法进行解释,而此参数defautlMessage是就是前端Heaher里的Content-typre的值。
g. 对序号(16)的方法,OgnlTextParser的evaluate方法就是最终解释Ognl表达式的语句。黑客就是向此处注入代码ProcessBuilder或Runtime的代码,从而调用cmd或shell命令,从而操制操作系统。
2.3 POC代码研究
以下网上的一段POC代码里的一段header内容,即使没有Ognl基础也很容易看懂。
这个header内容只看Content-Type的内容,这Content-Type内容可以分为两部分。第一部分不用多说只是为了本文中《2.2Strtuts2流程分析》的流程图的序号(4)的判断为真而已;第二部分就是Ognl注入的代码,就是黑客用来控制系统的代码,其实细看没什么大了解,大部人都可以看得懂,不需要学习Ognl。大家再看的模块(3),这就是执行cmd命令的地方,相当于在服务器上执行了以下java代码:
2.修复方式
官方的解决方法是:升级到Apache Struts 2.3.32或2.5.10.1版。我们看看升级包怎么解决这个问题。其实很简单,就是更改类JakartaMultiPartRequest的buildErrorMessage(Throwable e, Object[] args)方法,改变调用LocalizedTextUtil.findText的调用的方式。原有的调用方式如下:
更改后的代码如下:
方案比简解决,当e.getMessage()(这个值就是前端Header里Content-type内容)不作为而LocalizedTextUtil.findText的defatulMessage参数而是作为args参数传入。
附录1
POC测试境(不是性能问题,不列硬件信息)
1.攻击端
操作系统 | Fedora 25 64位(linux系统) |
脚本解释器 | Python 2.7.12 |
2.服务器端
操作系统 |
Windows 7 32位 |
Web service |
apache-tomcat-7.0.76 |
Struts2版本 |
2.3.15.1 |
Ognl版本 |
3.0.6 |
杀毒软件 |
360 |
附录2:
相关知识
1. ProcessBuilder的redirectErrorStream 属性
最初,此属性为 false,意思是子进程的标准输出和错误输出被发送给两个独立的流,这些流可以通过 Process.getInputStream() 和Process.getErrorStream() 方法来访问。如果将值设置为 true,标准错误将与标准输出合并。这使得关联错误消息和相应的输出变得更容易。在此情况下,合并的数据可从 Process.getInputStream() 返回的流读取,而从Process.getErrorStream() 返回的流读取将直接到达文件尾。
个人认为如果想更深入的了解STRUTS与OGNL的关系,需要了解ValueStack的使用,其中涉及的“对象栈”的概念也比较重要。有兴趣的同志可以多读读以下这篇文章:《STRUTS2中的OGNL》(http://blog.csdn.net/v123411739/article/details/24052989)