XSLT injection

Compiling an attacker-controlled XSLT stylesheet

Description

An XSLT stylesheet is written in XML, but it is an executable template language rather than ordinary XML data. If a request body, parameter, upload, or unrestricted path selects the stylesheet that an application compiles, an attacker can control transformation instructions.

Java APIs that compile or use stylesheet instructions include:

  • JAXP TransformerFactory.newTransformer(Source) and newTemplates(Source)
  • SAXTransformerFactory.newTransformerHandler(Source) and newXMLFilter(Source)
  • Saxon XsltCompiler.compile(Source) and compilePackage(Source)

By contrast, TransformerFactory.newTransformer() creates an identity transformation without a stylesheet. For a transformer created from a trusted stylesheet, the first argument of Transformer.transform(Source, Result) is the XML data to transform. Securely parsing that XML remains a separate requirement from trusting the stylesheet.

Potential impact

  • Changed transformation and output: An attacker can control templates, conditions, XPath expressions, and output structure.
  • File or network access: A processor that permits external access may retrieve local or network resources through external DTDs, xsl:import, xsl:include, or document() URIs.
  • Resource exhaustion: Complex or repetitive stylesheets can consume excessive CPU and memory.
  • Code invocation: Extension functions or equivalent processor features may invoke application or third-party code when enabled. JDK 25 defaults jdk.xml.enableExtensionFunctions to false, so code execution is not a universal consequence.

Remediation

  1. Keep stylesheets under server control. Do not compile complete stylesheets supplied or modified by untrusted users. Map a small set of user choices to complete, immutable application-owned resources rather than appending request strings to paths. A generic escape or sanitize helper cannot make arbitrary XSLT safe.
  2. Explicitly harden JAXP as defense in depth. Set XMLConstants.FEATURE_SECURE_PROCESSING to true, and set XMLConstants.ACCESS_EXTERNAL_DTD and XMLConstants.ACCESS_EXTERNAL_STYLESHEET to empty strings to deny all external protocols. Do not enable jdk.xml.enableExtensionFunctions. If a URIResolver returns a non-null Source, external-access properties do not apply to that resolution; the resolver must deny access or return only approved local resources.
  3. Use a maintained runtime and processing limits. With Java 25, copy <java_home>/conf/jaxp-strict.properties.template to a separate configuration file and test it with -Djava.xml.config.file=... to assess compatibility with stricter settings. Apply suitable throughput, depth, XPath-operator, time, and memory limits. These reduce damage but do not make attacker-controlled stylesheets trustworthy.
  4. Isolate intentional stylesheet-authoring features. Require authorization and run them in a separate service with minimal XML data and permissions, disabling external resources and extension functions.

Examples

Before

java
import java.io.StringReader;
import jakarta.servlet.http.HttpServletRequest;
import javax.xml.transform.Source;
import javax.xml.transform.Transformer;
import javax.xml.transform.TransformerFactory;
import javax.xml.transform.stream.StreamResult;
import javax.xml.transform.stream.StreamSource;

public final class InsecureXslt {
    public void transform(HttpServletRequest request) throws Exception {
        Source stylesheet = new StreamSource(request.getInputStream());
        Transformer transformer = TransformerFactory.newInstance().newTransformer(stylesheet);

        Source xml = new StreamSource(new StringReader("<report/>"));
        transformer.transform(xml, new StreamResult(System.out));
    }
}

The entire request body is compiled as transformation instructions, giving the sender control of the stylesheet.

After

java
import java.io.InputStream;
import java.io.StringReader;
import jakarta.servlet.http.HttpServletRequest;
import javax.xml.XMLConstants;
import javax.xml.transform.Source;
import javax.xml.transform.Transformer;
import javax.xml.transform.TransformerFactory;
import javax.xml.transform.stream.StreamResult;
import javax.xml.transform.stream.StreamSource;

public final class SecureXslt {
    private static final String REPORT_STYLESHEET = "/xsl/report.xsl";

    public void render(HttpServletRequest request) throws Exception {
        TransformerFactory factory = TransformerFactory.newInstance();
        factory.setFeature(XMLConstants.FEATURE_SECURE_PROCESSING, true);
        factory.setAttribute(XMLConstants.ACCESS_EXTERNAL_DTD, "");
        factory.setAttribute(XMLConstants.ACCESS_EXTERNAL_STYLESHEET, "");

        try (InputStream stream = SecureXslt.class.getResourceAsStream(REPORT_STYLESHEET)) {
            if (stream == null) {
                throw new IllegalStateException("Missing trusted stylesheet");
            }
            Transformer transformer = factory.newTransformer(new StreamSource(stream));
            Source xml = new StreamSource(new StringReader(request.getParameter("xml")));
            transformer.transform(xml, new StreamResult(System.out));
        }
    }
}

The stylesheet is a fixed application resource, while request data is used only as the XML to transform. Explicitly denying external DTD and stylesheet access adds defense in depth; it does not replace the trust boundary.

References