PlatformPortSwigger Web Security Academy
TopicXML external entity (XXE) injection
LabExploiting XXE via image file upload
DifficultyPractitioner
GoalRead /etc/hostname and post it in a comment
ToolsBrowser + 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:

The lab blog home page with a View post button
Open a blog post to reach its comment form.

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 entity xxe pointing 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:

A terminal showing cat test.svg with the SVG containing the XXE DOCTYPE and a text element referencing the entity
test.svg: an external entity for /etc/hostname, drawn as text inside a valid 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:

The Leave a comment form with a comment, name, the test.svg avatar selected, and the Post Comment button
Attach test.svg as the avatar and submit the comment.
The comment confirmation page: Thank you for your comment!
The comment is accepted; the server has processed our SVG into an avatar image.

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:

The posted comment showing a small avatar rendered from the malicious SVG
Our comment with its rendered avatar. The text we drew is the contents of /etc/hostname.

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

Right-click context menu on the avatar with Open image in new tab highlighted
Open the avatar image directly to see it full size.

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

The avatar image opened on its own at the avatars endpoint, showing the rendered /etc/hostname text
The rendered avatar: the text is the contents of /etc/hostname, read via XXE in the SVG. Submit it to solve the lab.

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.