Exploiting XXE via Image File Upload

Table of Contents
| Platform | PortSwigger Web Security Academy |
| Topic | XML external entity (XXE) injection |
| Lab | Exploiting XXE via image file upload |
| Difficulty | Practitioner |
| Goal | Read /etc/hostname and post it in a comment |
| Tools | Browser + a text editor |
XML hiding inside an image#
Not every XML parser sits behind an obvious XML endpoint. SVG is an image format that is also XML, so an app that lets you upload an avatar and then processes the SVG server-side (to rasterize or resize it) is quietly running an XML parser on a file we control. If that parser resolves external entities, we have XXE, and the output comes back rendered inside the image.
The target here is /etc/hostname.
The upload point#
The lab is a blog. Open a post so we can reach its comment form, which has an Avatar file upload:

Crafting the malicious SVG#
Instead of a real image, write an SVG that declares an external entity for
/etc/hostname and draws that entity as text, so whatever the parser reads shows
up as the image’s visible content:
<?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>Piece by piece:
<!DOCTYPE test [ <!ENTITY xxe SYSTEM "file:///etc/hostname"> ]>declares the external entityxxepointing at the target file, exactly as in a normal XXE.<svg ...>is a valid 128×128 SVG so the server accepts and renders it as an image.<text ...>&xxe;</text>draws the entity’s value, the file contents, as text on the canvas, so reading the file becomes “reading the picture”.
Save it as test.svg:

Uploading it as an avatar#
On the comment form, fill in the fields and choose test.svg for the Avatar,
then post the comment:


Reading the file out of the rendered image#
Back on the post, our comment appears with the avatar the server generated from the SVG, the file contents are tiny but present in it:

To read it clearly, right-click the avatar and open it on its own:

The avatar is served from the comment’s avatars endpoint, and the rendered text
is the hostname we read:

Submit that hostname to solve the lab.
Real-world impact#
An image upload that “just resizes avatars” turns into arbitrary file-read as the application user the moment the image is SVG and the parser resolves entities:
- Any SVG/XML processing pipeline is in scope — thumbnailers, document converters, and office-file processors all parse XML and are classic XXE sinks.
- Reads secrets and source — the same entity reads
/etc/passwd,.env, config, and keys, not just a hostname. - Can go blind/OOB — if the output isn’t rendered back, the file can still be exfiltrated out-of-band with a malicious external DTD (see the data-exfiltration lab ).
Only ever against systems you are authorized to test.
Remediation#
- Disable DTDs and external entities in whatever library processes uploaded images/SVGs, the same core XXE fix.
- Prefer a hardened image pipeline that rasterizes SVGs without a full XML parser, or sanitize SVGs (strip the DOCTYPE and external references) on upload.
- Validate uploads by content, and consider rejecting SVG where a raster format would do.
Key takeaways#
- SVG is XML, so an avatar/image upload that processes SVG server-side is an XXE sink hiding in plain sight.
- Drawing the entity with a
<text>element makes the file contents render into the image, so you read the file by viewing the picture. - The fix is unchanged: disable external entities/DTDs in the image processor, or don’t parse untrusted SVG at all.
Related posts#
- Exploiting XInclude to Retrieve Files — reading a file when you only control a value in a server-built document.
- Exploiting XXE to Retrieve Files — the classic external-entity file read.
- Blind XXE: Exfiltrate Data via a Malicious External DTD — reading a file when the output isn’t shown back.
- Full series: PortSwigger: XML External Entity Injection .