This is an automated email from the ASF dual-hosted git repository.
coheigea pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/cxf.git
The following commit(s) were added to refs/heads/main by this push:
new ef597b0d721 Read WSDL imports through CXF's StaxUtils instead of
WSDL4J (#3323)
ef597b0d721 is described below
commit ef597b0d7215ccc23af6a51995a17e6fd43b397b
Author: Colm O hEigeartaigh <[email protected]>
AuthorDate: Tue Jul 21 14:30:30 2026 +0100
Read WSDL imports through CXF's StaxUtils instead of WSDL4J (#3323)
---
.../cxf/wsdl11/AbstractWrapperWSDLLocator.java | 39 +++++++++++
.../org/apache/cxf/wsdl11/WSDLManagerImpl.java | 35 +++++++++-
.../org/apache/cxf/wsdl11/WSDLManagerImplTest.java | 49 ++++++++++++++
.../apache/cxf/wsdl11/wsdl_xxe_import_main.wsdl | 76 ++++++++++++++++++++++
.../cxf/wsdl11/wsdl_xxe_import_malicious.xsd | 49 ++++++++++++++
5 files changed, 246 insertions(+), 2 deletions(-)
diff --git
a/rt/wsdl/src/main/java/org/apache/cxf/wsdl11/AbstractWrapperWSDLLocator.java
b/rt/wsdl/src/main/java/org/apache/cxf/wsdl11/AbstractWrapperWSDLLocator.java
index 0b16b42f435..75ceb9507ba 100644
---
a/rt/wsdl/src/main/java/org/apache/cxf/wsdl11/AbstractWrapperWSDLLocator.java
+++
b/rt/wsdl/src/main/java/org/apache/cxf/wsdl11/AbstractWrapperWSDLLocator.java
@@ -18,15 +18,22 @@
*/
package org.apache.cxf.wsdl11;
+import java.io.ByteArrayInputStream;
+import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.net.URI;
import java.net.URISyntaxException;
import javax.wsdl.xml.WSDLLocator;
+import javax.xml.stream.XMLStreamException;
+import javax.xml.stream.XMLStreamReader;
+
+import org.w3c.dom.Document;
import org.xml.sax.InputSource;
import org.apache.cxf.resource.URIResolver;
+import org.apache.cxf.staxutils.StaxUtils;
public abstract class AbstractWrapperWSDLLocator implements WSDLLocator {
@@ -106,6 +113,38 @@ public abstract class AbstractWrapperWSDLLocator
implements WSDLLocator {
lastImport = src.getSystemId();
}
}
+
+ // Pre-parse imported documents through CXF's hardened StaxUtils path
to neutralize
+ // any XXE payloads (CWE-611) before handing the InputSource back to
WSDL4J,
+ // whose DocumentBuilderFactory is not configured with XXE protections.
+ // The serialised output will contain no DOCTYPE declarations or
unresolved entities.
+ if (src != null && (src.getByteStream() != null ||
src.getCharacterStream() != null)) {
+ String savedSystemId = src.getSystemId();
+ String savedPublicId = src.getPublicId();
+ XMLStreamReader xmlReader = null;
+ try {
+ xmlReader = StaxUtils.createXMLStreamReader(src);
+ Document doc = StaxUtils.read(xmlReader, true);
+ ByteArrayOutputStream bos = new ByteArrayOutputStream();
+ StaxUtils.writeTo(doc, bos);
+ InputSource hardenedSrc = new InputSource(new
ByteArrayInputStream(bos.toByteArray()));
+ hardenedSrc.setSystemId(savedSystemId);
+ hardenedSrc.setPublicId(savedPublicId);
+ src = hardenedSrc;
+ } catch (XMLStreamException e) {
+ throw new RuntimeException("Failed to securely parse WSDL/XSD
import '"
+ + importLocation + "': " +
e.getMessage(), e);
+ } finally {
+ if (xmlReader != null) {
+ try {
+ StaxUtils.close(xmlReader);
+ } catch (XMLStreamException ex) {
+ // ignore close failure
+ }
+ }
+ }
+ }
+
return src;
}
diff --git a/rt/wsdl/src/main/java/org/apache/cxf/wsdl11/WSDLManagerImpl.java
b/rt/wsdl/src/main/java/org/apache/cxf/wsdl11/WSDLManagerImpl.java
index 460a647b852..d4e2fbeb309 100644
--- a/rt/wsdl/src/main/java/org/apache/cxf/wsdl11/WSDLManagerImpl.java
+++ b/rt/wsdl/src/main/java/org/apache/cxf/wsdl11/WSDLManagerImpl.java
@@ -36,6 +36,7 @@ import javax.wsdl.extensions.ExtensibilityElement;
import javax.wsdl.extensions.ExtensionRegistry;
import javax.wsdl.extensions.mime.MIMEPart;
import javax.wsdl.factory.WSDLFactory;
+import javax.wsdl.xml.WSDLLocator;
import javax.wsdl.xml.WSDLReader;
import javax.xml.namespace.QName;
import javax.xml.stream.XMLStreamException;
@@ -179,15 +180,45 @@ public class WSDLManagerImpl implements WSDLManager {
reader.setFeature("javax.wsdl.verbose", false);
reader.setExtensionRegistry(registry);
+ // Use a WSDLLocator even when loading from a DOM element so that any
+ // <wsdl:import> or <xsd:import> inside the element goes through
+ // AbstractWrapperWSDLLocator.getImportInputSource(), which pre-parses
+ // each imported document through CXF's hardened StaxUtils path before
+ // handing it to WSDL4J's DocumentBuilderFactory (CWE-611).
+ String documentBaseUri = el.getOwnerDocument().getDocumentURI();
+ if (documentBaseUri == null) {
+ documentBaseUri = "";
+ }
+ final WSDLLocator wsdlLocator;
+ if (bus != null) {
+ CatalogWSDLLocator catLocator = new
CatalogWSDLLocator(documentBaseUri, bus);
+ wsdlLocator = new ResourceManagerWSDLLocator(documentBaseUri,
catLocator, bus);
+ } else {
+ // No bus: fall back to catalog-only resolution. The anonymous
subclass
+ // satisfies AbstractWrapperWSDLLocator's abstract methods and
still
+ // applies hardening to whatever the catalog can resolve.
+ final CatalogWSDLLocator catLocator = new
CatalogWSDLLocator(documentBaseUri);
+ wsdlLocator = new AbstractWrapperWSDLLocator(documentBaseUri,
catLocator) {
+ @Override
+ public InputSource getInputSource() {
+ return catLocator.getBaseInputSource();
+ }
+ @Override
+ public InputSource getInputSource(String parentLocation,
String importLocation) {
+ return new InputSource(); // no ResourceManager without a
bus
+ }
+ };
+ }
+
final Definition def;
// This is needed to avoid security exceptions when running with a
security manager
if (System.getSecurityManager() == null) {
- def = reader.readWSDL("", el);
+ def = reader.readWSDL(wsdlLocator, el);
} else {
try {
def = AccessController.doPrivileged(
- (PrivilegedExceptionAction<Definition>) () ->
reader.readWSDL("", el));
+ (PrivilegedExceptionAction<Definition>) () ->
reader.readWSDL(wsdlLocator, el));
} catch (PrivilegedActionException paex) {
throw new WSDLException(WSDLException.PARSER_ERROR,
paex.getMessage(), paex);
}
diff --git
a/rt/wsdl/src/test/java/org/apache/cxf/wsdl11/WSDLManagerImplTest.java
b/rt/wsdl/src/test/java/org/apache/cxf/wsdl11/WSDLManagerImplTest.java
index 66bb3353319..51031005e52 100644
--- a/rt/wsdl/src/test/java/org/apache/cxf/wsdl11/WSDLManagerImplTest.java
+++ b/rt/wsdl/src/test/java/org/apache/cxf/wsdl11/WSDLManagerImplTest.java
@@ -43,6 +43,7 @@ import org.apache.cxf.wsdl.WSDLManager;
import org.junit.Test;
import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertTrue;
import static org.junit.Assert.fail;
@@ -226,4 +227,52 @@ public class WSDLManagerImplTest {
// expected
}
}
+
+ /**
+ * Regression test for CWE-611: XML External Entity (XXE) injection via
WSDL/XSD imports.
+ *
+ * When a WSDL contains a {@code <wsdl:import>} or {@code <xsd:import>}
that resolves
+ * to a document carrying a DTD with an external SYSTEM entity, CXF must
not allow that
+ * entity to be resolved or expanded. The top-level WSDL is already parsed
through the
+ * hardened {@code StaxUtils} path; this test verifies the same protection
is applied
+ * to all transitively imported documents by {@link
AbstractWrapperWSDLLocator}.
+ *
+ * <p>Without the fix, WSDL4J's {@code DocumentBuilderFactory} (configured
with only
+ * {@code setNamespaceAware(true)}) would attempt to resolve the external
entity,
+ * enabling file disclosure, blind SSRF, or entity-expansion DoS.
+ * Related: CWE-611, CWE-776, CWE-918.
+ */
+ @Test
+ public void testWSDLImportXXEVulnerability() throws Exception {
+ String wsdlUrl =
getClass().getResource("wsdl_xxe_import_main.wsdl").toString();
+
+ WSDLManager builder = new WSDLManagerImpl();
+
+ // With the fix in place AbstractWrapperWSDLLocator pre-parses
imported documents
+ // through StaxUtils (DTD off, external entities off) before WSDL4J
ever sees them.
+ // Either:
+ // (a) loading succeeds and no file content is leaked into the
definition, OR
+ // (b) loading fails because StaxUtils itself rejects the DOCTYPE –
either outcome
+ // proves the malicious entity was not handed to WSDL4J's
unhardenened parser.
+ try {
+ Definition def = builder.getDefinition(wsdlUrl);
+
+ // (a) Loaded successfully: assert the sentinel file content is
not present.
+ assertNotNull("WSDL Definition should be parsed", def);
+ java.io.ByteArrayOutputStream bos = new
java.io.ByteArrayOutputStream();
+ builder.getWSDLFactory().newWSDLWriter().writeWSDL(def, bos);
+ String serialized =
bos.toString(java.nio.charset.StandardCharsets.UTF_8.name());
+ // The entity target (/etc/hostname content) must not appear in
the output
+ assertFalse("XXE entity content must not be present in parsed WSDL
definition",
+ serialized.contains("xxe_test_file"));
+ } catch (Exception e) {
+ // (b) StaxUtils rejected the DTD before WSDL4J could act on it.
+ // Any error here must originate from CXF's own hardened path, not
from
+ // WSDL4J's unhardenened DocumentBuilderFactory.
+ String message = e.getMessage() == null ? "" : e.getMessage();
+ assertFalse("WSDL4J's unhardened parser must not reach entity
resolution; "
+ + "only CXF's StaxUtils path should produce errors.
Got: " + message,
+
message.contains("com.ibm.wsdl.xml.WSDLReaderImpl.getDocument"));
+ }
+ }
}
\ No newline at end of file
diff --git
a/rt/wsdl/src/test/resources/org/apache/cxf/wsdl11/wsdl_xxe_import_main.wsdl
b/rt/wsdl/src/test/resources/org/apache/cxf/wsdl11/wsdl_xxe_import_main.wsdl
new file mode 100644
index 00000000000..3d613c93485
--- /dev/null
+++ b/rt/wsdl/src/test/resources/org/apache/cxf/wsdl11/wsdl_xxe_import_main.wsdl
@@ -0,0 +1,76 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+ 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.
+-->
+<!-- Test case for CWE-611: XXE via WSDL imports
+ This WSDL imports a malicious XSD that contains an XXE payload.
+ The top-level WSDL is benign, but the imported document carries the XXE.
+ This tests whether WSDL4J properly hardens the parser for imported
documents.
+-->
+<wsdl:definitions
+ xmlns:wsdl="http://schemas.xmlsoap.org/wsdl/"
+ xmlns:soap="http://schemas.xmlsoap.org/wsdl/soap/"
+ xmlns:tns="http://apache.org/hello_world_xxe"
+ xmlns:xsd="http://www.w3.org/2001/XMLSchema"
+ targetNamespace="http://apache.org/hello_world_xxe"
+ name="HelloWorldXXE">
+
+ <!-- Import a schema that contains XXE payload -->
+ <wsdl:types>
+ <xsd:schema targetNamespace="http://apache.org/hello_world_xxe">
+ <xsd:import namespace="http://apache.org/xxe_schemas"
schemaLocation="wsdl_xxe_import_malicious.xsd"/>
+ <xsd:element name="requestElement" type="xsd:string"/>
+ <xsd:element name="responseElement" type="xsd:string"/>
+ </xsd:schema>
+ </wsdl:types>
+
+ <wsdl:message name="sayHiRequest">
+ <wsdl:part name="body" element="tns:requestElement"/>
+ </wsdl:message>
+
+ <wsdl:message name="sayHiResponse">
+ <wsdl:part name="body" element="tns:responseElement"/>
+ </wsdl:message>
+
+ <wsdl:portType name="Greeter">
+ <wsdl:operation name="sayHi">
+ <wsdl:input message="tns:sayHiRequest" name="sayHiRequest"/>
+ <wsdl:output message="tns:sayHiResponse" name="sayHiResponse"/>
+ </wsdl:operation>
+ </wsdl:portType>
+
+ <wsdl:binding name="GreeterBinding" type="tns:Greeter">
+ <soap:binding style="document"
transport="http://schemas.xmlsoap.org/soap/http"/>
+ <wsdl:operation name="sayHi">
+ <soap:operation soapAction="sayHi"/>
+ <wsdl:input name="sayHiRequest">
+ <soap:body use="literal"/>
+ </wsdl:input>
+ <wsdl:output name="sayHiResponse">
+ <soap:body use="literal"/>
+ </wsdl:output>
+ </wsdl:operation>
+ </wsdl:binding>
+
+ <wsdl:service name="SOAPService">
+ <wsdl:port name="SoapPort" binding="tns:GreeterBinding">
+ <soap:address
location="http://localhost:9000/SoapContext/SoapPort"/>
+ </wsdl:port>
+ </wsdl:service>
+
+</wsdl:definitions>
diff --git
a/rt/wsdl/src/test/resources/org/apache/cxf/wsdl11/wsdl_xxe_import_malicious.xsd
b/rt/wsdl/src/test/resources/org/apache/cxf/wsdl11/wsdl_xxe_import_malicious.xsd
new file mode 100644
index 00000000000..8c7c66fb9e3
--- /dev/null
+++
b/rt/wsdl/src/test/resources/org/apache/cxf/wsdl11/wsdl_xxe_import_malicious.xsd
@@ -0,0 +1,49 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!DOCTYPE xsd:schema [
+ <!ENTITY xxe_test_file SYSTEM "file:///etc/hostname">
+]>
+<!--
+ 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.
+-->
+<!-- Malicious XSD with XXE payload (CWE-611)
+ This document defines an external entity that attempts to read
/etc/hostname.
+ If the parser is not hardened, the entity reference will be resolved,
+ and the file content will be inlined into the parsed document.
+ This is the document that gets loaded by WSDL4J when processing the
import.
+-->
+<xsd:schema
+ xmlns:xsd="http://www.w3.org/2001/XMLSchema"
+ targetNamespace="http://apache.org/xxe_schemas"
+ elementFormDefault="qualified">
+
+ <xsd:element name="payloadElement">
+ <xsd:complexType>
+ <xsd:sequence>
+ <xsd:element name="data" type="xsd:string"/>
+ </xsd:sequence>
+ <xsd:attribute name="content">
+ <xsd:simpleType>
+ <xsd:restriction base="xsd:string">
+ <xsd:pattern value="&xxe_test_file;"/>
+ </xsd:restriction>
+ </xsd:simpleType>
+ </xsd:attribute>
+ </xsd:complexType>
+ </xsd:element>
+
+</xsd:schema>