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();
