Skip to main content
XSLT (Extensible Stylesheet Language Transformations) turns XML into another format, HTML, plain text, even another XML document, by applying a stylesheet’s rules against it. When an application lets you control the stylesheet, or injects unsanitized input into one it builds itself, the transformation engine itself becomes the attack surface: XSLT can read files, make outbound requests, write files, and on several processors, execute arbitrary code directly.

Read-Only Primitives

document() and system-property() work on effectively every XSLT processor, no extensions required. File read, SSRF, and fingerprinting.

RCE Extensions

EXSLT, PHP’s xsl extension, Xalan (Java), and msxsl:script (.NET) each expose their own path to file write or direct code execution, but only when that specific extension is enabled.

Fingerprinting the Processor

Before trying any RCE payload, find out which XSLT engine and version you’re dealing with, each vendor supports a different set of extensions. system-property() is part of the XSLT 1.0 spec and works everywhere.
Pulling all three properties at once gives vendor, vendor URL, and spec version in a single pass.

File Read and SSRF via document()

document() is meant to pull in a second XML source during a transform, nothing stops it from being pointed at a local file path or a URL instead. It works without any extension enabled, making it the first thing worth trying once injection is confirmed.
Pointing document() at an internal IP and port and watching the response time or error message differ for open vs closed ports turns this into a basic port scanner.
Mixing a document() payload into a stylesheet that also legitimately iterates the input XML (the <xsl:for-each> above) keeps the transform’s normal output intact, so the injected read doesn’t visibly break the page while testing.

File Write via EXSLT

The EXSLT common extension (exsl:document on some processors, exploit:document with any arbitrary namespace alias here) writes the transform’s output to a new file on disk instead of just returning it, turning an injection point into an arbitrary file write.
Swap the literal text for a webshell payload and evil.txt for a web-accessible path with an executable extension, and this becomes direct RCE without needing any of the extensions below. Whether it works depends entirely on whether the host’s XSLT processor has EXSLT support compiled in.

RCE by Processor

PHP’s XSLTProcessor ships with its own php:function extension that lets a stylesheet call arbitrary PHP functions directly by name, this is by far the most reliable RCE path when the vendor fingerprint comes back as libxslt.
The same mechanism reads files, lists directories, or drops a webshell to disk, all without needing EXSLT at all.

Don’t Forget XXE

An XSLT document is itself XML, so anywhere a stylesheet is accepted, the surrounding document is also a candidate for a classic DOCTYPE/ENTITY injection, independent of anything XSLT-specific. If the RCE/file-read paths above are not working, drop back to testing standard XXE against the stylesheet file itself (see XXE for the full technique set).

Quick Reference Tools

PayloadsAllTheThings: XSLT Injection

Reference used for this page.
No general-purpose automated scanner exists for this class, exploitation is manual and depends entirely on which processor and extensions the target has enabled. Fingerprint first, confirm scope, then pick the technique that matches the vendor string.