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

robertlazarski pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/axis-axis2-java-core.git

commit 1d3457d12399c7721ed3f52e7031d1d24d169460
Author: Robert Lazarski <[email protected]>
AuthorDate: Mon May 18 07:09:59 2026 -1000

    Harden WSDL import parsing against XXE and SSRF
    
    Security fix for XXE (CWE-611) and SSRF via imported WSDL/XSD documents.
    wsdl4j 1.6.3 creates its own DocumentBuilderFactory without XXE hardening
    when resolving <wsdl:import> and <xsd:import> targets. Axis2's existing
    DefaultEntityResolver protects only the base document parse.
    
    Three-layer fix:
    
    1. AxisService.createClientSideAxisService(URL): default
       javax.wsdl.importDocuments to false. This is the only code path where
       a remote URL is fetched at runtime. Opt-in via system property
       axis2.wsdl.importDocuments=true.
    
    2. SecureWSDLLocator: when imports are opt-in enabled, fetches imported
       documents and validates them with a hardened XML parser (disallow
       DOCTYPE) before returning to wsdl4j. Rejects documents containing
       DOCTYPE declarations.
    
    3. XMLUtils.getDOMFactory(): defense-in-depth hardening with
       disallow-doctype-decl, external-general-entities=false,
       external-parameter-entities=false. Does not fix the wsdl4j vector
       (separate DocumentBuilderFactory) but hardens all other Axis2 XML
       parsing.
    
    WSDL11ToAxisServiceBuilder retains importDocuments=true because it
    processes local/classpath WSDLs (.aar deployment) where imports are
    safe and necessary. 402 kernel tests pass.
    
    Co-Authored-By: Claude Opus 4.6 (1M context) <[email protected]>
---
 .../org/apache/axis2/description/AxisService.java  |  12 +-
 .../description/WSDL11ToAxisServiceBuilder.java    |   5 +
 .../org/apache/axis2/util/SecureWSDLLocator.java   | 164 +++++++++++++++++++++
 .../kernel/src/org/apache/axis2/util/XMLUtils.java |   9 ++
 4 files changed, 188 insertions(+), 2 deletions(-)

diff --git a/modules/kernel/src/org/apache/axis2/description/AxisService.java 
b/modules/kernel/src/org/apache/axis2/description/AxisService.java
index 71221dad48..5fcfeb6a44 100644
--- a/modules/kernel/src/org/apache/axis2/description/AxisService.java
+++ b/modules/kernel/src/org/apache/axis2/description/AxisService.java
@@ -2372,8 +2372,16 @@ public class AxisService extends AxisDescription {
             String namespaceURI = doc.getDocumentElement().getNamespaceURI();
             if (Constants.NS_URI_WSDL11.equals(namespaceURI)) {
                 WSDLReader reader = 
WSDLUtil.newWSDLReaderWithPopulatedExtensionRegistry();
-                reader.setFeature("javax.wsdl.importDocuments", true);
-                Definition wsdlDefinition = 
reader.readWSDL(getBaseURI(wsdlURL.toString()), doc);
+                boolean allowImports = 
Boolean.getBoolean("axis2.wsdl.importDocuments");
+                reader.setFeature("javax.wsdl.importDocuments", allowImports);
+                Definition wsdlDefinition;
+                if (allowImports) {
+                    // Use hardened WSDLLocator to prevent XXE in imported 
documents
+                    wsdlDefinition = reader.readWSDL(
+                            new 
org.apache.axis2.util.SecureWSDLLocator(wsdlURL.toString()));
+                } else {
+                    wsdlDefinition = 
reader.readWSDL(getBaseURI(wsdlURL.toString()), doc);
+                }
                 if (wsdlDefinition != null) {
                     
wsdlDefinition.setDocumentBaseURI(getDocumentURI(wsdlURL.toString()));
                 }
diff --git 
a/modules/kernel/src/org/apache/axis2/description/WSDL11ToAxisServiceBuilder.java
 
b/modules/kernel/src/org/apache/axis2/description/WSDL11ToAxisServiceBuilder.java
index 9ba96d1563..7a8eb37e27 100644
--- 
a/modules/kernel/src/org/apache/axis2/description/WSDL11ToAxisServiceBuilder.java
+++ 
b/modules/kernel/src/org/apache/axis2/description/WSDL11ToAxisServiceBuilder.java
@@ -2317,6 +2317,11 @@ public class WSDL11ToAxisServiceBuilder extends 
WSDLToAxisServiceBuilder {
 
         // switch off the verbose mode for all usecases
         reader.setFeature(JAVAX_WSDL_VERBOSE_MODE_KEY, false);
+        // Import resolution is enabled here because WSDL11ToAxisServiceBuilder
+        // processes local/classpath WSDLs (e.g., from .aar deployment) where
+        // imports are safe and necessary. The remote-URL code path in
+        // AxisService.createClientSideAxisService() defaults to false and uses
+        // a hardened WSDLLocator when opt-in is enabled.
         reader.setFeature("javax.wsdl.importDocuments", true);
 
         Definition def;
diff --git a/modules/kernel/src/org/apache/axis2/util/SecureWSDLLocator.java 
b/modules/kernel/src/org/apache/axis2/util/SecureWSDLLocator.java
new file mode 100644
index 0000000000..54497e9f56
--- /dev/null
+++ b/modules/kernel/src/org/apache/axis2/util/SecureWSDLLocator.java
@@ -0,0 +1,164 @@
+/*
+ * 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.axis2.util;
+
+import javax.wsdl.xml.WSDLLocator;
+import org.xml.sax.InputSource;
+
+import java.io.ByteArrayInputStream;
+import java.io.ByteArrayOutputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.net.URI;
+import java.net.URL;
+
+/**
+ * A {@link WSDLLocator} that fetches imported WSDL/XSD documents and
+ * validates them with a hardened XML parser before returning them to
+ * wsdl4j. This prevents XXE (XML External Entity) attacks via
+ * {@code <!DOCTYPE>} declarations in imported documents.
+ *
+ * <p>wsdl4j's internal {@code WSDLReaderImpl.getDocument()} creates its
+ * own {@code DocumentBuilderFactory} without XXE hardening. By supplying
+ * this locator, Axis2 intercepts import resolution and ensures that
+ * every imported document is parsed by a secure parser first. If the
+ * document contains a DOCTYPE declaration, parsing fails and the
+ * import is rejected before wsdl4j's vulnerable parser sees it.
+ *
+ * @see org.apache.axis2.description.AxisService#createClientSideAxisService
+ * @see org.apache.axis2.description.WSDL11ToAxisServiceBuilder
+ */
+public class SecureWSDLLocator implements WSDLLocator {
+
+    private final String baseURI;
+    private String latestImportURI;
+
+    public SecureWSDLLocator(String baseURI) {
+        this.baseURI = baseURI;
+    }
+
+    @Override
+    public InputSource getBaseInputSource() {
+        try {
+            return createSecureInputSource(baseURI);
+        } catch (Exception e) {
+            throw new RuntimeException("Failed to load base WSDL: " + baseURI, 
e);
+        }
+    }
+
+    @Override
+    public String getBaseURI() {
+        return baseURI;
+    }
+
+    @Override
+    public InputSource getImportInputSource(String parentLocation, String 
importLocation) {
+        try {
+            String resolved = resolveURI(parentLocation, importLocation);
+            latestImportURI = resolved;
+            return createSecureInputSource(resolved);
+        } catch (Exception e) {
+            throw new RuntimeException(
+                    "Failed to securely load imported document: " + 
importLocation
+                    + " (resolved from " + parentLocation + ")", e);
+        }
+    }
+
+    @Override
+    public String getLatestImportURI() {
+        return latestImportURI;
+    }
+
+    @Override
+    public void close() {
+        // nothing to close
+    }
+
+    /**
+     * Fetches the document at the given URI, validates it with a hardened
+     * SAX parser to verify it contains no DOCTYPE declaration, then
+     * returns a new InputSource over the validated bytes.
+     */
+    private InputSource createSecureInputSource(String uri) throws Exception {
+        byte[] content = fetchBytes(uri);
+
+        // Validate with hardened SAX parser — throws SAXParseException if
+        // DOCTYPE is present. SAX avoids building a DOM tree in memory.
+        javax.xml.parsers.SAXParserFactory spf = 
javax.xml.parsers.SAXParserFactory.newInstance();
+        spf.setNamespaceAware(true);
+        spf.setFeature("http://apache.org/xml/features/disallow-doctype-decl";, 
true);
+        
spf.setFeature("http://xml.org/sax/features/external-general-entities";, false);
+        
spf.setFeature("http://xml.org/sax/features/external-parameter-entities";, 
false);
+        spf.setXIncludeAware(false);
+
+        org.xml.sax.XMLReader xmlReader = spf.newSAXParser().getXMLReader();
+        xmlReader.setEntityResolver(new DefaultEntityResolver());
+        xmlReader.parse(new InputSource(new ByteArrayInputStream(content)));
+
+        // Validated — return a fresh InputSource for wsdl4j
+        InputSource is = new InputSource(new ByteArrayInputStream(content));
+        is.setSystemId(uri);
+        return is;
+    }
+
+    private static final int CONNECT_TIMEOUT =
+            Integer.getInteger("axis2.wsdl.import.connect.timeout", 5000);
+    private static final int READ_TIMEOUT =
+            Integer.getInteger("axis2.wsdl.import.read.timeout", 15000);
+    private static final long MAX_IMPORT_SIZE =
+            Long.getLong("axis2.wsdl.import.maxsize", 10 * 1024 * 1024);
+
+    private static byte[] fetchBytes(String uri) throws IOException {
+        URL url = new URL(uri);
+        String protocol = url.getProtocol();
+        if (!"http".equalsIgnoreCase(protocol) && 
!"https".equalsIgnoreCase(protocol)) {
+            throw new IOException("WSDL import rejected: untrusted protocol '"
+                    + protocol + "' in URI: " + uri);
+        }
+        java.net.URLConnection conn = url.openConnection();
+        conn.setConnectTimeout(CONNECT_TIMEOUT);
+        conn.setReadTimeout(READ_TIMEOUT);
+        try (InputStream in = conn.getInputStream()) {
+            ByteArrayOutputStream out = new ByteArrayOutputStream();
+            byte[] buf = new byte[8192];
+            int n;
+            long total = 0;
+            while ((n = in.read(buf)) != -1) {
+                total += n;
+                if (total > MAX_IMPORT_SIZE) {
+                    throw new IOException("WSDL import exceeds size limit of "
+                            + MAX_IMPORT_SIZE + " bytes for URI: " + uri);
+                }
+                out.write(buf, 0, n);
+            }
+            return out.toByteArray();
+        }
+    }
+
+    private static String resolveURI(String parent, String relative) {
+        try {
+            URI parentURI = new URI(parent);
+            return parentURI.resolve(relative).toString();
+        } catch (Exception e) {
+            // Fallback: treat as absolute
+            return relative;
+        }
+    }
+}
diff --git a/modules/kernel/src/org/apache/axis2/util/XMLUtils.java 
b/modules/kernel/src/org/apache/axis2/util/XMLUtils.java
index 565d0b8970..7afe6b3f63 100644
--- a/modules/kernel/src/org/apache/axis2/util/XMLUtils.java
+++ b/modules/kernel/src/org/apache/axis2/util/XMLUtils.java
@@ -122,6 +122,15 @@ public class XMLUtils {
         try {
             dbf = DocumentBuilderFactory.newInstance();
             dbf.setNamespaceAware(true);
+            // XXE hardening — Axis2 never processes DTDs in any protocol
+            // (SOAP, WSDL, XSD are all XML Schema-based). Disabling DOCTYPE
+            // declarations at the factory level provides defense-in-depth
+            // beyond the DefaultEntityResolver applied at parse time.
+            
dbf.setFeature("http://apache.org/xml/features/disallow-doctype-decl";, true);
+            
dbf.setFeature("http://xml.org/sax/features/external-general-entities";, false);
+            
dbf.setFeature("http://xml.org/sax/features/external-parameter-entities";, 
false);
+            dbf.setXIncludeAware(false);
+            dbf.setExpandEntityReferences(false);
         }
         catch (Exception e) {
             //log.error(Messages.getMessage("exception00"), e );

Reply via email to