This is an automated email from the ASF dual-hosted git repository.
bitstorm pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/wicket.git
The following commit(s) were added to refs/heads/master by this push:
new 348725cf8a Harden XsltTransformer against XXE in the transformed
document (#1537)
348725cf8a is described below
commit 348725cf8aebd82b5ab3127d474c965d7eb39851
Author: Nexory <[email protected]>
AuthorDate: Wed Aug 12 10:29:30 2026 +0200
Harden XsltTransformer against XXE in the transformed document (#1537)
* Harden XsltTransformer against XXE in the transformed document
XsltTransformer.transform() built its TransformerFactory with a plain
TransformerFactory.newInstance() and then transformed the component's
rendered
output as the XML source document. That source is attacker-influenceable
(it is
whatever the decorated component rendered, including user model data), so an
external entity in it was resolved: a crafted document could read local
files
or reach internal URLs.
Enable FEATURE_SECURE_PROCESSING on the factory, mirroring what
XSLTResourceStream already does; on the JDK this also denies external DTD
and
stylesheet access, so the external entity is rejected instead of resolved.
Legitimate stylesheet transforms are unaffected (the existing
OutputTransformerContainer tests still pass).
* Add XXE regression test for XsltTransformer
Confirms that an external general entity in the transformed source document
is
not resolved. Reuses the identity anyName.xsl stylesheet from the
outputTransformer tests; the test fails (the secret leaks into the output)
without the FEATURE_SECURE_PROCESSING guard.
---
.../markup/transformer/XsltTransformerXxeTest.java | 76 ++++++++++++++++++++++
.../wicket/markup/transformer/XsltTransformer.java | 5 ++
2 files changed, 81 insertions(+)
diff --git
a/wicket-core-tests/src/test/java/org/apache/wicket/markup/transformer/XsltTransformerXxeTest.java
b/wicket-core-tests/src/test/java/org/apache/wicket/markup/transformer/XsltTransformerXxeTest.java
new file mode 100644
index 0000000000..bfdd96b307
--- /dev/null
+++
b/wicket-core-tests/src/test/java/org/apache/wicket/markup/transformer/XsltTransformerXxeTest.java
@@ -0,0 +1,76 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.wicket.markup.transformer;
+
+import static org.junit.jupiter.api.Assertions.assertFalse;
+
+import java.io.File;
+import java.nio.file.Files;
+
+import org.apache.wicket.markup.html.basic.Label;
+import org.apache.wicket.util.tester.WicketTestCase;
+import org.junit.jupiter.api.Test;
+
+/**
+ * Tests that {@link XsltTransformer} does not resolve external XML entities
in the document it
+ * transforms. The transformed source document is the decorated component's
rendered output, which
+ * can contain user data, so it must not be able to read external resources
(XXE).
+ */
+class XsltTransformerXxeTest extends WicketTestCase
+{
+ @Test
+ void externalEntityInSourceDocumentIsNotResolved() throws Exception
+ {
+ File secret = File.createTempFile("wicket-xxe", ".txt");
+ String canary = "WICKET-XXE-CANARY-7f55";
+ Files.write(secret.toPath(), canary.getBytes("UTF-8"));
+ try
+ {
+ // The source document (would be the component's render
output) carries an external
+ // general entity pointing at the local secret file.
+ String source = "<?xml version='1.0'?>"
+ + "<!DOCTYPE r [ <!ENTITY xxe SYSTEM '" +
secret.toURI() + "'> ]>"
+ + "<r>&xxe;</r>";
+
+ // A component is only needed so getResourceStream()
can resolve style/locale; the
+ // stylesheet is the existing identity-copy anyName.xsl
from the outputTransformer tests.
+ Label component = new Label("id", "x");
+ tester.startComponentInPage(component);
+ XsltTransformer transformer = new XsltTransformer(
+
"org/apache/wicket/markup/outputTransformer/anyName.xsl");
+
+ String out;
+ try
+ {
+ out = transformer.transform(component,
source).toString();
+ }
+ catch (Exception e)
+ {
+ // The parser is allowed to reject the
DOCTYPE/entity outright; what matters is that
+ // the secret is never exposed.
+ out = "";
+ }
+
+ assertFalse(out.contains(canary),
+ "XsltTransformer resolved an external entity
(XXE): " + out);
+ }
+ finally
+ {
+ secret.delete();
+ }
+ }
+}
diff --git
a/wicket-core/src/main/java/org/apache/wicket/markup/transformer/XsltTransformer.java
b/wicket-core/src/main/java/org/apache/wicket/markup/transformer/XsltTransformer.java
index 523b2c1cac..bae8cf91cc 100644
---
a/wicket-core/src/main/java/org/apache/wicket/markup/transformer/XsltTransformer.java
+++
b/wicket-core/src/main/java/org/apache/wicket/markup/transformer/XsltTransformer.java
@@ -20,6 +20,7 @@ import java.io.FileNotFoundException;
import java.io.StringReader;
import java.io.StringWriter;
+import javax.xml.XMLConstants;
import javax.xml.transform.Transformer;
import javax.xml.transform.TransformerFactory;
import javax.xml.transform.stream.StreamResult;
@@ -98,6 +99,10 @@ public class XsltTransformer implements ITransformer
{
// 1. Instantiate a TransformerFactory.
TransformerFactory tFactory =
TransformerFactory.newInstance();
+ // Harden against XXE: the XML source transformed below
is the component's
+ // rendered output and may embed user data, so disable
external entity, DTD
+ // and stylesheet resolution (as XSLTResourceStream
already does).
+
tFactory.setFeature(XMLConstants.FEATURE_SECURE_PROCESSING, true);
// 2. Use the TransformerFactory to process the
stylesheet Source
// and