| Field | Value |
|---|---|
| Platform | PortSwigger Web Security Academy |
| Type | XXE — XInclude File Retrieval |
| Difficulty | Practitioner |
| Objective | Read /etc/passwd via an XInclude injection in the stock check feature |
Exploiting XInclude to Retrieve Files — Writeup¶
Initial Observation¶
Going to a product and clicking "Check stock" — intercepting the request:
productId=1&storeId=2
No XML structure in the request body — this is a form-encoded POST. The lab description tells us the server embeds this value into a server-side XML document before parsing. We're not sending XML directly, so we can't define a DOCTYPE or inject external entities the classic way.
Injecting teto into productId to confirm it gets reflected:
productId=teto&storeId=2
Response: Invalid product ID: teto — the value lands somewhere in the XML that gets processed.
Attack Path¶
XInclude Injection¶
Since we can't control the full document structure, we use XInclude. XInclude is a W3C standard for composing XML documents from sub-documents — it lets you pull in external content using <xi:include> directives. Unlike external entities, XInclude works at the element level, so it can be placed in any data value that ends up inside an XML document. The namespace declaration (xmlns:xi) is all that's needed to activate it:
productId=<foo xmlns:xi="http://www.w3.org/2001/XInclude"><xi:include parse="text" href="file:///etc/passwd"/></foo>&storeId=2
parse="text" tells the parser to include the file as raw text rather than as an XML document. Without it, the parser would try to interpret /etc/passwd as XML and fail because it's not valid XML.
/etc/passwd contents come back in the response as usal, this means lab solved :P