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