The lab
here is how portswigger describes it
This lab has a "Check stock" feature that parses XML input and returns any unexpected values in the response.
To solve the lab, inject an XML external entity to retrieve the contents of the /etc/passwd file.
what xxe is?#
xml has a feature called entities, little named placeholders that the parser expands into some value as it reads the document. xml also lets document declare its own entities in a section called a doctype, and an external entity is one whose value is loaded from outside the document, such as a file on the server or a url. the problem is that if an application parses xml that we control and its parser still has external entities switched on, which plenty of older ones do by default, then we can declare an entity that points at a local file, reference it in the xml, and the parser will obligingly read that file and drop its contents into the parsed data. if the app then echoes that data back to us, we have just read a file straight off the server.
Step 1 - Find the xml being parsed#
opening a product on the shop and clicking check stock, i intercepted the request in burp and the body is xml.

<?xml version="1.0" encoding="UTF-8"?>
<stockCheck><productId>1</productId><storeId>1</storeId></stockCheck>
reading it simply the check stock feature sends its request as xml a stockCheck element wrapping a productId and a storeId. the server parses that xml and looks up the stock and the part that matters for us is that when it meets a value it does not expect like a product id that is not real, it echoes that value back in the response. that echo is our output channel, whatever we can slip into one of these values comes back to us.
Step 2 - Declare an external entity#
so we add a doctype that declares an external entity pointing at the file we want.
<!DOCTYPE test [ <!ENTITY xxe SYSTEM "file:///etc/passwd"> ]>
breaking that down, <!DOCTYPE test [ ... ]> opens an inline definition block where we can declare our own entities. inside it, <!ENTITY xxe SYSTEM "file:///etc/passwd"> defines an entity named xxe, and the SYSTEM keyword together with the file:// url makes it an external entity whose value is the contents of /etc/passwd. from that point on, anywhere we write &xxe; the parser will substitute the contents of that file.
Step 3 - Reference the Entity and read the file#
now we drop that doctype into the request and set productId to &xxe;, so the file's contents become the product id the server tries to look up. here is the final body.
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE test [ <!ENTITY xxe SYSTEM "file:///etc/passwd"> ]>
<stockCheck><productId>&xxe;</productId><storeId>1</storeId></stockCheck>
when the server parses this, it expands &xxe; into the whole /etc/passwd file, and the product id lookup fails because that is obviously not a real product, so the server reflects the value it could not find back in its error message, and that value is the file. /etc/passwd comes straight back in the response.

with this, the lab is solved!
