This is an automated email from the ASF dual-hosted git repository.

coheigea pushed a commit to branch coheigea/schema-xxe
in repository https://gitbox.apache.org/repos/asf/cxf-xjc-utils.git

commit 7e6f1294029a6cfe61e573e67d1a6e370b1fbb93
Author: Colm O hEigeartaigh <[email protected]>
AuthorDate: Fri Oct 23 07:37:02 2026 +0100

    Disable external DTD/entity resolution when compiling schemas (XXE)
---
 .../apache/cxf/maven_plugin/XSDToJavaRunner.java   |  29 +++++-
 .../org/apache/cxf/maven_plugin/SchemaXXETest.java | 110 +++++++++++++++++++++
 .../cxf/maven_plugin/XSDToJavaRunnerTest.java      |   8 +-
 3 files changed, 140 insertions(+), 7 deletions(-)

diff --git 
a/cxf-xjc-plugin/src/main/java/org/apache/cxf/maven_plugin/XSDToJavaRunner.java 
b/cxf-xjc-plugin/src/main/java/org/apache/cxf/maven_plugin/XSDToJavaRunner.java
index add6301..2ee7ac9 100644
--- 
a/cxf-xjc-plugin/src/main/java/org/apache/cxf/maven_plugin/XSDToJavaRunner.java
+++ 
b/cxf-xjc-plugin/src/main/java/org/apache/cxf/maven_plugin/XSDToJavaRunner.java
@@ -67,6 +67,11 @@ import org.sonatype.plexus.build.incremental.BuildContext;
  */
 public class XSDToJavaRunner {
     static Class<?> modelLoaderClass;
+
+    // The JAXP system property (JAXP 1.5) that restricts which protocols may 
be used to resolve
+    // external DTDs and external entities; an empty value forbids all such 
resolution. This is the
+    // system-property name, which differs from the equivalent factory 
attribute in XMLConstants.
+    private static final String ACCESS_EXTERNAL_DTD_PROPERTY = 
"javax.xml.accessExternalDTD";
     
     final String[] args;
     final List<String> cpList;
@@ -208,13 +213,29 @@ public class XSDToJavaRunner {
             // set up the context class loader so that the user-specified 
plugin
             // on classpath can be loaded from there with jaxb-xjc 2.3.0
             ClassLoader origLoader = 
Thread.currentThread().getContextClassLoader();
+            // Parse the schema (and binding) documents with external DTD and 
external entity
+            // resolution disabled, so that a crafted schema cannot read local 
files or reach the
+            // network through a DOCTYPE or an external entity (an XXE 
attack). XML Schema has no
+            // use for DTDs, so legitimate schemas are unaffected; schema 
import/include, which is
+            // resolved through the entity resolver and catalog, is not 
touched by this property.
+            final String prevAccessExternalDtd = 
System.getProperty(ACCESS_EXTERNAL_DTD_PROPERTY);
+            System.setProperty(ACCESS_EXTERNAL_DTD_PROPERTY, "");
+            final Model model;
             try {
-                
Thread.currentThread().setContextClassLoader(opt.getUserClassLoader(origLoader));
-                opt.parseArguments(args);
+                try {
+                    
Thread.currentThread().setContextClassLoader(opt.getUserClassLoader(origLoader));
+                    opt.parseArguments(args);
+                } finally {
+                    Thread.currentThread().setContextClassLoader(origLoader);
+                }
+                model = loadModel(opt);
             } finally {
-                Thread.currentThread().setContextClassLoader(origLoader);
+                if (prevAccessExternalDtd == null) {
+                    System.clearProperty(ACCESS_EXTERNAL_DTD_PROPERTY);
+                } else {
+                    System.setProperty(ACCESS_EXTERNAL_DTD_PROPERTY, 
prevAccessExternalDtd);
+                }
             }
-            Model model = loadModel(opt); 
             if (model == null) {
                 listener.message(xsdFile, "Failed to create model");
                 return -1;
diff --git 
a/cxf-xjc-plugin/src/test/java/org/apache/cxf/maven_plugin/SchemaXXETest.java 
b/cxf-xjc-plugin/src/test/java/org/apache/cxf/maven_plugin/SchemaXXETest.java
new file mode 100644
index 0000000..09d8b0b
--- /dev/null
+++ 
b/cxf-xjc-plugin/src/test/java/org/apache/cxf/maven_plugin/SchemaXXETest.java
@@ -0,0 +1,110 @@
+/**
+ * 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.cxf.maven_plugin;
+
+import java.io.File;
+import java.nio.charset.StandardCharsets;
+import java.nio.file.Files;
+import java.util.Collections;
+
+import org.junit.Assert;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.rules.TemporaryFolder;
+
+/**
+ * Verifies that a schema compiled by {@link XSDToJavaRunner} cannot read 
local files (or reach the
+ * network) through a DOCTYPE or an external entity declared in the schema 
document (an XXE attack).
+ */
+public class SchemaXXETest {
+
+    @Rule
+    public TemporaryFolder tmp = new TemporaryFolder();
+
+    private int compile(String xsdText) throws Exception {
+        File xsd = tmp.newFile("schema-" + System.nanoTime() + ".xsd");
+        Files.write(xsd.toPath(), xsdText.getBytes(StandardCharsets.UTF_8));
+        File out = tmp.newFolder("gen-" + System.nanoTime());
+        XJCErrorListener listener = new XJCErrorListener(new 
XJCBuildContext());
+        // "-d" directs generated code into the temporary folder so the test 
does not write into
+        // the working directory (the module source tree).
+        return new XSDToJavaRunner(
+            new String[] {"-d", out.getAbsolutePath(), xsd.toURI().toString()},
+            listener, out, Collections.<String>emptyList()).run();
+    }
+
+    // A schema that references an external general entity pointing at a real 
local file. If the
+    // parser dereferenced it, the file would be read during compilation and 
the compile would
+    // succeed; with external entities disabled the compile must fail instead.
+    private String schemaReadingFile(File target) {
+        return "<?xml version='1.0'?>\n"
+            + "<!DOCTYPE xs:schema [ <!ENTITY xxe SYSTEM '" + target.toURI() + 
"'> ]>\n"
+            + "<xs:schema xmlns:xs='http://www.w3.org/2001/XMLSchema'"
+            + " targetNamespace='urn:t' xmlns:t='urn:t'>\n"
+            + "  <xs:element name='root' type='xs:string'>\n"
+            + "    
<xs:annotation><xs:documentation>&xxe;</xs:documentation></xs:annotation>\n"
+            + "  </xs:element>\n"
+            + "</xs:schema>\n";
+    }
+
+    @Test
+    public void externalEntityInSchemaIsNotResolved() throws Exception {
+        File secret = tmp.newFile("secret.txt");
+        Files.write(secret.toPath(), 
"SECRET".getBytes(StandardCharsets.UTF_8));
+
+        // Must NOT compile cleanly: resolving the external entity (reading 
the file) is refused.
+        int rc = compile(schemaReadingFile(secret));
+        Assert.assertEquals("external entity in a schema must not be resolved 
during compile", -1, rc);
+    }
+
+    @Test
+    public void legitimateSchemaStillCompiles() throws Exception {
+        // A normal schema (no DOCTYPE, no entities) is unaffected by the 
hardening.
+        int rc = compile(
+            "<?xml version='1.0'?>\n"
+            + "<xs:schema xmlns:xs='http://www.w3.org/2001/XMLSchema'"
+            + " targetNamespace='urn:t' xmlns:t='urn:t'>\n"
+            + "  <xs:element name='root' type='xs:string'/>\n"
+            + "</xs:schema>\n");
+        Assert.assertEquals("a schema with no external references must still 
compile", 0, rc);
+    }
+
+    @Test
+    public void accessExternalDtdPropertyIsRestoredAfterRun() throws Exception 
{
+        String sentinel = "file,jar:file";
+        String prev = System.getProperty("javax.xml.accessExternalDTD");
+        System.setProperty("javax.xml.accessExternalDTD", sentinel);
+        try {
+            compile(
+                "<?xml version='1.0'?>\n"
+                + "<xs:schema xmlns:xs='http://www.w3.org/2001/XMLSchema'"
+                + " targetNamespace='urn:t' xmlns:t='urn:t'>\n"
+                + "  <xs:element name='root' type='xs:string'/>\n"
+                + "</xs:schema>\n");
+            Assert.assertEquals("the caller's accessExternalDTD setting must 
be restored",
+                sentinel, System.getProperty("javax.xml.accessExternalDTD"));
+        } finally {
+            if (prev == null) {
+                System.clearProperty("javax.xml.accessExternalDTD");
+            } else {
+                System.setProperty("javax.xml.accessExternalDTD", prev);
+            }
+        }
+    }
+}
diff --git 
a/cxf-xjc-plugin/src/test/java/org/apache/cxf/maven_plugin/XSDToJavaRunnerTest.java
 
b/cxf-xjc-plugin/src/test/java/org/apache/cxf/maven_plugin/XSDToJavaRunnerTest.java
index 6198fa6..da142ed 100644
--- 
a/cxf-xjc-plugin/src/test/java/org/apache/cxf/maven_plugin/XSDToJavaRunnerTest.java
+++ 
b/cxf-xjc-plugin/src/test/java/org/apache/cxf/maven_plugin/XSDToJavaRunnerTest.java
@@ -41,16 +41,18 @@ public class XSDToJavaRunnerTest {
         final XJCErrorListener listener = new XJCErrorListener(context);
 
         final File outputFile = outputFolder.newFile();
+        final File genDir = outputFolder.newFolder("gen");
         final String xjb = 
getClass().getResource("/schemas/wsdl/test.xjb").toExternalForm();
         final String xsd = 
getClass().getResource("/schemas/wsdl/test.xsd").toExternalForm();
         final String catalog = 
getClass().getResource("/schemas/configuration/catalog.cat").toExternalForm();
-        
+
         new XSDToJavaRunner(
             new String [] {
                 "-catalog", catalog,
                 "-b", xjb,
-                xsd 
-            }, 
+                "-d", genDir.getAbsolutePath(),
+                xsd
+            },
             listener, 
             outputFile, 
             Collections.<String>emptyList()).run();

Reply via email to