Exploiting XInclude to retrieve files

2 min read Easy PortSwigger
XXE
Contents

On this page

The lab

here is how portswigger describes it

This lab has a "Check stock" feature that embeds the user input inside a server-side XML document that is subsequently parsed.

Because you don't control the entire XML document you can't define a DTD to launch a classic XXE attack.

To solve the lab, inject an XInclude statement to retrieve the contents of the /etc/passwd file.

this closes out the xxe series. in every lab so far we controlled the whole xml body, so we could bolt a doctype onto the front and declare our own entities. here we cannot, the server takes just our input and drops it into an xml document it builds itself, so there is no place for us to put a doctype. XInclude is the trick that still works when all we control is one value inside someone else's document.

what XInclude is?#

XInclude is a feature of xml for pulling one document into another, its whole job is to say here, at this spot, paste in the contents of that other resource. it works at the level of a single element rather than needing a doctype at the top, so you can trigger it from any part of the document you happen to control. that is exactly our situation, we do not own the document but we do own one value in it, and one value is all XInclude needs. and because the resource it pastes in can be a local file, we can aim it at /etc/passwd.

Step 1 - Find the xml being built#

we open a product page, click check stock, and intercept the post request in burp. the productId value is taken and placed into a server side xml document before it is parsed, so that parameter is our one foothold inside the document.

1111

Step 2 - Inject the XInclude statement#

we set the productId parameter to this.

XML
<foo xmlns:xi="http://www.w3.org/2001/XInclude"><xi:include parse="text" href="file:///etc/passwd"/></foo>

here is why this shape and what each part does. we cannot add a doctype, so instead of declaring an entity we hand the parser a small chunk of xml that carries an XInclude instruction, and when the parser processes the document our chunk is inside, it obeys that instruction and pastes the file in for us.

the outer <foo> is just a wrapper element to hang everything on, the name does not matter. xmlns:xi="http://www.w3.org/2001/XInclude" declares the XInclude namespace and binds it to the xi prefix, which is what makes the parser recognise xi:include as a real include instruction rather than a meaningless tag. <xi:include ... /> is the instruction itself. its href="file:///etc/passwd" says which resource to pull in, the local passwd file, and parse="text" tells it to bring that file in as plain text. that text part matters, because /etc/passwd is not valid xml, so if the parser tried to read it as xml it would choke, and asking for text sidesteps that and lets the raw contents through.

after sending the request the response comes back with the contents of /etc/passwd where our product id lookup would normally go.

2222

with this, we solved the lab!