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)andnewTemplates(Source) SAXTransformerFactory.newTransformerHandler(Source)andnewXMLFilter(Source)- Saxon
XsltCompiler.compile(Source)andcompilePackage(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, ordocument()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.enableExtensionFunctionstofalse, so code execution is not a universal consequence.
Remediation
- 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
sanitizehelper cannot make arbitrary XSLT safe. - Explicitly harden JAXP as defense in depth. Set
XMLConstants.FEATURE_SECURE_PROCESSINGtotrue, and setXMLConstants.ACCESS_EXTERNAL_DTDandXMLConstants.ACCESS_EXTERNAL_STYLESHEETto empty strings to deny all external protocols. Do not enablejdk.xml.enableExtensionFunctions. If aURIResolverreturns a non-nullSource, external-access properties do not apply to that resolution; the resolver must deny access or return only approved local resources. - Use a maintained runtime and processing limits. With Java 25, copy
<java_home>/conf/jaxp-strict.properties.templateto 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. - 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
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
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.