You are viewing a plain text version of this content. The canonical link for it is here.
Posted to java-commits@axis.apache.org by ro...@apache.org on 2026/05/19 14:06:49 UTC
(axis-axis2-java-core) branch master updated (11b2d27224 -> d93657ea86)
This is an automated email from the ASF dual-hosted git repository.
robertlazarski pushed a change to branch master
in repository https://gitbox.apache.org/repos/asf/axis-axis2-java-core.git
from 11b2d27224 Create empty 2.0.2 release notes placeholder
new 18325e6d7f docs: Update release process for site.xml check and manual dist upload
new 1d3457d123 Harden WSDL import parsing against XXE and SSRF
new 7ac84d8339 Harden schema import resolution against SSRF (CWE-918)
new 0afbb74417 Fix SSRF bypass via relative path + remote base URI, harden WSDL locators
new d93657ea86 Switch getXMLSchema() resolver from blocklist to allowlist
The 5 revisions listed above as "new" are entirely new to this
repository and will be described in separate emails. The revisions
listed as "add" were already present in the repository and have only
been added to this reference.
Summary of changes:
.../deployment/resolver/AARBasedWSDLLocator.java | 13 +-
.../resolver/AARFileBasedURIResolver.java | 10 ++
.../deployment/resolver/WarBasedWSDLLocator.java | 14 +-
.../resolver/WarFileBasedURIResolver.java | 28 +++-
.../org/apache/axis2/description/AxisService.java | 12 +-
.../description/WSDL11ToAxisServiceBuilder.java | 5 +
.../description/WSDLToAxisServiceBuilder.java | 41 ++++++
.../org/apache/axis2/util/SecureWSDLLocator.java | 164 +++++++++++++++++++++
.../kernel/src/org/apache/axis2/util/XMLUtils.java | 9 ++
.../apache/axis2/deployment/URIResolverTest.java | 46 +++++-
src/site/markdown/release-process.md | 38 ++++-
11 files changed, 359 insertions(+), 21 deletions(-)
create mode 100644 modules/kernel/src/org/apache/axis2/util/SecureWSDLLocator.java
(axis-axis2-java-core) 03/05: Harden schema import resolution against SSRF (CWE-918)
Posted by ro...@apache.org.
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 7ac84d8339ad80f8f7f7977cf0caaa28fc3b7b98
Author: Robert Lazarski <ro...@gmail.com>
AuthorDate: Mon May 18 07:35:24 2026 -1000
Harden schema import resolution against SSRF (CWE-918)
Companion fix to the XXE hardening. xmlschema-core's DefaultURIResolver
follows absolute schemaLocation URLs with no scheme or host restriction,
enabling blind SSRF from the Axis2 JVM. Three vectors addressed:
1. WSDLToAxisServiceBuilder.getXMLSchema(): when no custom resolver is
set, install a restrictive URIResolver that blocks http/https/ftp/jar/
file schemes on schemaLocation targets. Callers who need remote
resolution can supply their own resolver via setCustomResolver().
2. AARFileBasedURIResolver: block remote URLs when falling through to
super.resolveEntity() for absolute URIs. Return empty InputSource
instead of fetching.
3. WarFileBasedURIResolver: same fix as AAR resolver.
Updated URIResolverTest to verify remote URLs are blocked (was
previously asserting they were fetched — that was the SSRF).
403 kernel tests pass.
Co-Authored-By: Claude Opus 4.6 (1M context) <no...@anthropic.com>
---
.../resolver/AARFileBasedURIResolver.java | 10 ++++++++
.../resolver/WarFileBasedURIResolver.java | 9 +++++++
.../description/WSDLToAxisServiceBuilder.java | 25 ++++++++++++++++++
.../apache/axis2/deployment/URIResolverTest.java | 30 +++++++++++++++++++---
4 files changed, 70 insertions(+), 4 deletions(-)
diff --git a/modules/kernel/src/org/apache/axis2/deployment/resolver/AARFileBasedURIResolver.java b/modules/kernel/src/org/apache/axis2/deployment/resolver/AARFileBasedURIResolver.java
index 661ea336cd..165ef7214e 100644
--- a/modules/kernel/src/org/apache/axis2/deployment/resolver/AARFileBasedURIResolver.java
+++ b/modules/kernel/src/org/apache/axis2/deployment/resolver/AARFileBasedURIResolver.java
@@ -58,6 +58,16 @@ public class AARFileBasedURIResolver extends DefaultURIResolver {
lastImportLocation = URI.create(baseUri).resolve(schemaLocation);
if (isAbsolute(lastImportLocation.toString())) {
+ // Block remote URLs to prevent SSRF. Only allow resolution
+ // of absolute URIs that are local file paths.
+ String loc = lastImportLocation.toString();
+ if (loc.regionMatches(true, 0, "http:", 0, 5)
+ || loc.regionMatches(true, 0, "https:", 0, 6)
+ || loc.regionMatches(true, 0, "ftp:", 0, 4)
+ || loc.regionMatches(true, 0, "jar:", 0, 4)) {
+ log.warn("Blocked remote schema resolution in AAR deployment: " + loc);
+ return new InputSource(new java.io.ByteArrayInputStream(new byte[0]));
+ }
return super.resolveEntity(
targetNamespace, schemaLocation, baseUri);
} else {
diff --git a/modules/kernel/src/org/apache/axis2/deployment/resolver/WarFileBasedURIResolver.java b/modules/kernel/src/org/apache/axis2/deployment/resolver/WarFileBasedURIResolver.java
index e0f6ff4cc6..d418b4b646 100644
--- a/modules/kernel/src/org/apache/axis2/deployment/resolver/WarFileBasedURIResolver.java
+++ b/modules/kernel/src/org/apache/axis2/deployment/resolver/WarFileBasedURIResolver.java
@@ -43,6 +43,15 @@ public class WarFileBasedURIResolver extends DefaultURIResolver {
String baseUri) {
//no issue with
if (isAbsolute(schemaLocation)) {
+ // Block remote URLs to prevent SSRF. Only allow resolution
+ // of absolute URIs that are local file paths.
+ if (schemaLocation.regionMatches(true, 0, "http:", 0, 5)
+ || schemaLocation.regionMatches(true, 0, "https:", 0, 6)
+ || schemaLocation.regionMatches(true, 0, "ftp:", 0, 4)
+ || schemaLocation.regionMatches(true, 0, "jar:", 0, 4)) {
+ log.warn("Blocked remote schema resolution in WAR deployment: " + schemaLocation);
+ return new InputSource(new java.io.ByteArrayInputStream(new byte[0]));
+ }
return super.resolveEntity(
targetNamespace, schemaLocation, baseUri);
} else {
diff --git a/modules/kernel/src/org/apache/axis2/description/WSDLToAxisServiceBuilder.java b/modules/kernel/src/org/apache/axis2/description/WSDLToAxisServiceBuilder.java
index 8cfb1fbbdf..5fdfe4f697 100644
--- a/modules/kernel/src/org/apache/axis2/description/WSDLToAxisServiceBuilder.java
+++ b/modules/kernel/src/org/apache/axis2/description/WSDLToAxisServiceBuilder.java
@@ -141,6 +141,31 @@ public abstract class WSDLToAxisServiceBuilder {
if (customResolver != null) {
schemaCollection.setSchemaResolver(customResolver);
+ } else {
+ // Install a restrictive resolver that blocks remote schema
+ // resolution (SSRF via absolute schemaLocation URLs). The
+ // default URIResolver in xmlschema-core follows any absolute
+ // URI including http://, https://, ftp://, and jar://.
+ // Local file:// and relative paths are allowed for co-packaged
+ // schemas in .aar/.war deployments.
+ schemaCollection.setSchemaResolver(
+ new org.apache.ws.commons.schema.resolver.URIResolver() {
+ private final org.apache.ws.commons.schema.resolver.DefaultURIResolver
+ delegate = new org.apache.ws.commons.schema.resolver.DefaultURIResolver();
+ public org.xml.sax.InputSource resolveEntity(
+ String ns, String loc, String base) {
+ if (loc != null
+ && (loc.regionMatches(true, 0, "http:", 0, 5)
+ || loc.regionMatches(true, 0, "https:", 0, 6)
+ || loc.regionMatches(true, 0, "ftp:", 0, 4)
+ || loc.regionMatches(true, 0, "jar:", 0, 4))) {
+ throw new RuntimeException(
+ "Remote schemaLocation blocked: " + loc
+ + " (use setCustomResolver to opt in)");
+ }
+ return delegate.resolveEntity(ns, loc, base);
+ }
+ });
}
return schemaCollection.read(element);
diff --git a/modules/kernel/test/org/apache/axis2/deployment/URIResolverTest.java b/modules/kernel/test/org/apache/axis2/deployment/URIResolverTest.java
index e4d6d2fbc9..92ef18d604 100644
--- a/modules/kernel/test/org/apache/axis2/deployment/URIResolverTest.java
+++ b/modules/kernel/test/org/apache/axis2/deployment/URIResolverTest.java
@@ -27,17 +27,39 @@ import org.xml.sax.InputSource;
public class URIResolverTest extends TestCase {
- public void testResolveEntity() {
+ /**
+ * Verify that remote http/https URLs are blocked by the SSRF
+ * hardening in AAR and WAR resolvers. The resolvers should return
+ * an empty InputSource instead of fetching the remote URL.
+ */
+ public void testRemoteUrlBlocked() {
AARFileBasedURIResolver aar = new AARFileBasedURIResolver(null);
- WarFileBasedURIResolver war = new WarFileBasedURIResolver(null);
InputSource inputSource = aar.resolveEntity(null,
"http://www.test.org/test.xsd",
"http://www.test.org/schema.xsd");
assertNotNull(inputSource);
- assertEquals(inputSource.getSystemId(), "http://www.test.org/test.xsd");
+ // Should return empty InputSource, not one with the remote URL
+ assertNull("AAR resolver must block remote http URLs (SSRF)",
+ inputSource.getSystemId());
+
+ WarFileBasedURIResolver war = new WarFileBasedURIResolver(null);
inputSource = war.resolveEntity(null, "http://www.test.org/test.xsd",
"http://www.test.org/schema.xsd");
assertNotNull(inputSource);
- assertEquals(inputSource.getSystemId(), "http://www.test.org/test.xsd");
+ assertNull("WAR resolver must block remote http URLs (SSRF)",
+ inputSource.getSystemId());
+ }
+
+ /**
+ * Verify that https URLs are also blocked.
+ */
+ public void testHttpsUrlBlocked() {
+ AARFileBasedURIResolver aar = new AARFileBasedURIResolver(null);
+ InputSource inputSource = aar.resolveEntity(null,
+ "https://www.test.org/test.xsd",
+ "https://www.test.org/schema.xsd");
+ assertNotNull(inputSource);
+ assertNull("AAR resolver must block remote https URLs (SSRF)",
+ inputSource.getSystemId());
}
}
(axis-axis2-java-core) 01/05: docs: Update release process for site.xml check and manual dist upload
Posted by ro...@apache.org.
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 18325e6d7f3660ff86746a8bfb3f0dc6ae04c1ff
Author: Robert Lazarski <ro...@gmail.com>
AuthorDate: Mon May 18 01:31:13 2026 -1000
docs: Update release process for site.xml check and manual dist upload
Add preparation step to verify release notes sidebar link in site.xml
and create placeholder release notes for the next version (required by
the distribution assembly). Replace the obsolete dist.py reference
with manual artifact verification and SVN upload instructions.
Co-Authored-By: Claude Opus 4.6 (1M context) <no...@anthropic.com>
---
src/site/markdown/release-process.md | 38 ++++++++++++++++++++++++++++++++----
1 file changed, 34 insertions(+), 4 deletions(-)
diff --git a/src/site/markdown/release-process.md b/src/site/markdown/release-process.md
index 680d52a0b7..96fd7da8fa 100644
--- a/src/site/markdown/release-process.md
+++ b/src/site/markdown/release-process.md
@@ -111,11 +111,20 @@ You may also execute a dry run of the release process: mvn release:prepare -Ddry
3. Check that the Maven site can be generated and deployed successfully, and that it has the expected content.
-To generate the entire documentation in one place, complete with working inter-module links, execute the site-deploy phase (and check the files under target/staging). A quick and reliable way of doing that is to use the following command: mvn -Dmaven.test.skip=true clean package site-deploy
+ To generate the entire documentation in one place, complete with working inter-module links, execute the site-deploy phase (and check the files under target/staging). A quick and reliable way of doing that is to use the following command: mvn -Dmaven.test.skip=true clean package site-deploy
-4. Check that the source distribution is buildable.
+4. Verify that `src/site/site.xml` includes the new release version in the Release Notes
+ sidebar navigation. If missing, add an entry like:
-5. Check that the source tree is buildable with an empty local Maven repository.
+ <item name="X.Y.Z" href="release-notes/X.Y.Z.html"/>
+
+ Also create an empty placeholder release note for the *next* version under
+ `src/site/markdown/release-notes/` (e.g., `2.0.2.md`). The distribution assembly
+ references this file and the build will fail without it.
+
+5. Check that the source distribution is buildable.
+
+6. Check that the source tree is buildable with an empty local Maven repository.
If any problems are detected, they should be fixed on the trunk (except for issues specific to the
release branch) and then merged to the release branch.
@@ -190,7 +199,28 @@ If you have multiple keys, you can define a ~/.gnupg/gpg.conf file for a default
3. Login to Nexus and close the staging repository. For more details about this step, see
[here](https://maven.apache.org/developers/release/maven-project-release-procedure.html) and [here](https://infra.apache.org/publishing-maven-artifacts.html#promote).
-4. Execute the `target/checkout/etc/dist.py` script to upload the source and binary distributions to the development area of the <a href="https://dist.apache.org/repos/dist/"> repository. </a>
+4. Upload the source and binary distributions to the ASF dist area. The artifacts
+ are in `target/checkout/modules/distribution/target/`. Before uploading, verify
+ the GPG signatures and SHA512 checksums:
+
+ cd target/checkout/modules/distribution/target
+ for f in axis2-*.zip; do
+ echo "=== $f ==="
+ gpg --verify "$f.asc" "$f"
+ sha512sum "$f" | cut -d' ' -f1
+ cat "$f.sha512" | cut -d' ' -f1
+ done
+
+ Then copy to your local SVN checkout of the ASF dist area and commit:
+
+ mkdir -p /path/to/dist/axis/axis2/java/core/X.Y.Z
+ cp axis2-X.Y.Z-bin.zip axis2-X.Y.Z-bin.zip.asc axis2-X.Y.Z-bin.zip.sha512 \
+ axis2-X.Y.Z-src.zip axis2-X.Y.Z-src.zip.asc axis2-X.Y.Z-src.zip.sha512 \
+ axis2-X.Y.Z-war.zip axis2-X.Y.Z-war.zip.asc axis2-X.Y.Z-war.zip.sha512 \
+ /path/to/dist/axis/axis2/java/core/X.Y.Z/
+ cd /path/to/dist/axis
+ svn add axis2/java/core/X.Y.Z
+ svn commit -m "Stage Axis2 X.Y.Z release candidate"
5. Create a staging area for the Maven site:
(axis-axis2-java-core) 05/05: Switch getXMLSchema() resolver from blocklist to allowlist
Posted by ro...@apache.org.
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 d93657ea86bcc3d27010c29947855f91da94a405
Author: Robert Lazarski <ro...@gmail.com>
AuthorDate: Mon May 18 08:00:15 2026 -1000
Switch getXMLSchema() resolver from blocklist to allowlist
Gemini review identified that blocking specific schemes (http, https,
ftp, jar) while allowing file:// leaves a Local File Inclusion (LFI)
vector. Switch to allowlist approach: block ALL absolute URIs in the
default restrictive resolver. Only relative paths (resolved against
the document base URI) are permitted. This prevents SSRF, LFI, and
any future bypass via exotic URI schemes.
Co-packaged schemas in .aar/.war deployments use relative paths and
are unaffected. Applications needing absolute URI resolution can
supply their own resolver via setCustomResolver().
404 kernel tests pass.
Co-Authored-By: Claude Opus 4.6 (1M context) <no...@anthropic.com>
---
.../description/WSDLToAxisServiceBuilder.java | 37 +++++++++++++---------
1 file changed, 22 insertions(+), 15 deletions(-)
diff --git a/modules/kernel/src/org/apache/axis2/description/WSDLToAxisServiceBuilder.java b/modules/kernel/src/org/apache/axis2/description/WSDLToAxisServiceBuilder.java
index b271b069ce..cc38f14f87 100644
--- a/modules/kernel/src/org/apache/axis2/description/WSDLToAxisServiceBuilder.java
+++ b/modules/kernel/src/org/apache/axis2/description/WSDLToAxisServiceBuilder.java
@@ -154,24 +154,31 @@ public abstract class WSDLToAxisServiceBuilder {
delegate = new org.apache.ws.commons.schema.resolver.DefaultURIResolver();
public org.xml.sax.InputSource resolveEntity(
String ns, String loc, String base) {
- // Resolve against base URI before checking —
- // a relative loc with a remote base must be caught
- String resolved = loc;
- if (base != null && loc != null) {
+ // Allowlist: only permit relative paths resolved
+ // against the base URI. Block all absolute URIs
+ // (http, https, ftp, file, jar, etc.) to prevent
+ // both SSRF and LFI. Co-packaged schemas in .aar
+ // deployments use relative paths and are safe.
+ if (loc != null) {
+ String resolved = loc;
+ if (base != null) {
+ try {
+ resolved = java.net.URI.create(base)
+ .resolve(loc).toString();
+ } catch (IllegalArgumentException ignored) {
+ }
+ }
try {
- resolved = java.net.URI.create(base).resolve(loc).toString();
- } catch (IllegalArgumentException ignored) {
+ java.net.URI uri = new java.net.URI(resolved);
+ if (uri.isAbsolute()) {
+ throw new RuntimeException(
+ "Absolute schemaLocation blocked: "
+ + resolved + " (use setCustomResolver"
+ + " to opt in)");
+ }
+ } catch (java.net.URISyntaxException ignored) {
}
}
- if (resolved != null
- && (resolved.regionMatches(true, 0, "http:", 0, 5)
- || resolved.regionMatches(true, 0, "https:", 0, 6)
- || resolved.regionMatches(true, 0, "ftp:", 0, 4)
- || resolved.regionMatches(true, 0, "jar:", 0, 4))) {
- throw new RuntimeException(
- "Remote schemaLocation blocked: " + resolved
- + " (use setCustomResolver to opt in)");
- }
return delegate.resolveEntity(ns, loc, base);
}
});
(axis-axis2-java-core) 02/05: Harden WSDL import parsing against XXE and SSRF
Posted by ro...@apache.org.
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 <ro...@gmail.com>
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) <no...@anthropic.com>
---
.../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 );
(axis-axis2-java-core) 04/05: Fix SSRF bypass via relative path + remote base URI, harden WSDL locators
Posted by ro...@apache.org.
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 0afbb74417b065412e11c99432b7d5a0fc02817c
Author: Robert Lazarski <ro...@gmail.com>
AuthorDate: Mon May 18 07:46:39 2026 -1000
Fix SSRF bypass via relative path + remote base URI, harden WSDL locators
Gemini review found that the SSRF check on raw schemaLocation/importLocation
can be bypassed when a relative path resolves to a remote URL via the base
URI (e.g., relative "evil.xsd" + base "http://attacker/wsdl/" = remote fetch).
Fixes:
1. WSDLToAxisServiceBuilder restrictive resolver: resolve loc against base
before checking scheme.
2. WarFileBasedURIResolver: resolve schemaLocation against baseUri before
the absolute/remote check (AARFileBasedURIResolver already resolves first).
3. AARBasedWSDLLocator: block remote URLs in getImportInputSource() for
<wsdl:import> targets (was only patched for <xsd:import>).
4. WarBasedWSDLLocator: same fix, check resolved URI not raw importLocation.
5. URIResolverTest: add relative-path-with-remote-base bypass test.
404 kernel tests pass.
Co-Authored-By: Claude Opus 4.6 (1M context) <no...@anthropic.com>
---
.../deployment/resolver/AARBasedWSDLLocator.java | 13 +++++++--
.../deployment/resolver/WarBasedWSDLLocator.java | 14 +++++++--
.../resolver/WarFileBasedURIResolver.java | 33 +++++++++++++---------
.../description/WSDLToAxisServiceBuilder.java | 21 ++++++++++----
.../apache/axis2/deployment/URIResolverTest.java | 16 +++++++++++
5 files changed, 73 insertions(+), 24 deletions(-)
diff --git a/modules/kernel/src/org/apache/axis2/deployment/resolver/AARBasedWSDLLocator.java b/modules/kernel/src/org/apache/axis2/deployment/resolver/AARBasedWSDLLocator.java
index 7177075972..c3e4b367ad 100644
--- a/modules/kernel/src/org/apache/axis2/deployment/resolver/AARBasedWSDLLocator.java
+++ b/modules/kernel/src/org/apache/axis2/deployment/resolver/AARBasedWSDLLocator.java
@@ -70,8 +70,17 @@ public class AARBasedWSDLLocator extends DefaultURIResolver implements WSDLLocat
*/
public InputSource getImportInputSource(String parentLocation, String importLocation) {
lastImportLocation = URI.create(parentLocation).resolve(importLocation);
-
- if (isAbsolute(lastImportLocation.toString())) {
+ String loc = lastImportLocation.toString();
+
+ if (isAbsolute(loc)) {
+ // Block remote URLs to prevent SSRF in WSDL imports
+ if (loc.regionMatches(true, 0, "http:", 0, 5)
+ || loc.regionMatches(true, 0, "https:", 0, 6)
+ || loc.regionMatches(true, 0, "ftp:", 0, 4)
+ || loc.regionMatches(true, 0, "jar:", 0, 4)) {
+ throw new RuntimeException(
+ "Remote WSDL import blocked: " + loc);
+ }
return super.resolveEntity(
null, importLocation, parentLocation);
} else {
diff --git a/modules/kernel/src/org/apache/axis2/deployment/resolver/WarBasedWSDLLocator.java b/modules/kernel/src/org/apache/axis2/deployment/resolver/WarBasedWSDLLocator.java
index 4b8e5803bf..fcc860f6b4 100644
--- a/modules/kernel/src/org/apache/axis2/deployment/resolver/WarBasedWSDLLocator.java
+++ b/modules/kernel/src/org/apache/axis2/deployment/resolver/WarBasedWSDLLocator.java
@@ -57,13 +57,21 @@ public class WarBasedWSDLLocator extends DefaultURIResolver implements WSDLLocat
*/
public InputSource getImportInputSource(String parentLocation, String importLocation) {
lastImportLocation = URI.create(parentLocation).resolve(importLocation);
+ String loc = lastImportLocation.toString();
- if (isAbsolute(importLocation)) {
+ if (isAbsolute(loc)) {
+ // Block remote URLs to prevent SSRF in WSDL imports
+ if (loc.regionMatches(true, 0, "http:", 0, 5)
+ || loc.regionMatches(true, 0, "https:", 0, 6)
+ || loc.regionMatches(true, 0, "ftp:", 0, 4)
+ || loc.regionMatches(true, 0, "jar:", 0, 4)) {
+ throw new RuntimeException(
+ "Remote WSDL import blocked: " + loc);
+ }
return super.resolveEntity(
null, importLocation, parentLocation);
} else {
- String searchingStr = lastImportLocation.toString();
- return new InputSource(classLoader.getResourceAsStream(searchingStr));
+ return new InputSource(classLoader.getResourceAsStream(loc));
}
}
diff --git a/modules/kernel/src/org/apache/axis2/deployment/resolver/WarFileBasedURIResolver.java b/modules/kernel/src/org/apache/axis2/deployment/resolver/WarFileBasedURIResolver.java
index d418b4b646..ec8fa2a79b 100644
--- a/modules/kernel/src/org/apache/axis2/deployment/resolver/WarFileBasedURIResolver.java
+++ b/modules/kernel/src/org/apache/axis2/deployment/resolver/WarFileBasedURIResolver.java
@@ -41,15 +41,25 @@ public class WarFileBasedURIResolver extends DefaultURIResolver {
String targetNamespace,
String schemaLocation,
String baseUri) {
- //no issue with
- if (isAbsolute(schemaLocation)) {
- // Block remote URLs to prevent SSRF. Only allow resolution
- // of absolute URIs that are local file paths.
- if (schemaLocation.regionMatches(true, 0, "http:", 0, 5)
- || schemaLocation.regionMatches(true, 0, "https:", 0, 6)
- || schemaLocation.regionMatches(true, 0, "ftp:", 0, 4)
- || schemaLocation.regionMatches(true, 0, "jar:", 0, 4)) {
- log.warn("Blocked remote schema resolution in WAR deployment: " + schemaLocation);
+ // Resolve against base URI first to catch relative + remote base bypass
+ URI resolvedURI;
+ try {
+ resolvedURI = (baseUri != null)
+ ? URI.create(baseUri).resolve(schemaLocation)
+ : URI.create(schemaLocation);
+ } catch (IllegalArgumentException e) {
+ log.warn("Invalid URI syntax for schema location: " + schemaLocation);
+ return new InputSource(new java.io.ByteArrayInputStream(new byte[0]));
+ }
+ String resolved = resolvedURI.toString();
+
+ if (isAbsolute(resolved)) {
+ // Block remote URLs to prevent SSRF
+ if (resolved.regionMatches(true, 0, "http:", 0, 5)
+ || resolved.regionMatches(true, 0, "https:", 0, 6)
+ || resolved.regionMatches(true, 0, "ftp:", 0, 4)
+ || resolved.regionMatches(true, 0, "jar:", 0, 4)) {
+ log.warn("Blocked remote schema resolution in WAR deployment: " + resolved);
return new InputSource(new java.io.ByteArrayInputStream(new byte[0]));
}
return super.resolveEntity(
@@ -60,10 +70,7 @@ public class WarFileBasedURIResolver extends DefaultURIResolver {
throw new RuntimeException(
"Unsupported schema location " + schemaLocation);
}
-
- URI lastImportLocation = URI.create(baseUri).resolve(schemaLocation);
- String searchingStr = lastImportLocation.toString();
- return new InputSource(classLoader.getResourceAsStream(searchingStr));
+ return new InputSource(classLoader.getResourceAsStream(resolved));
}
}
}
diff --git a/modules/kernel/src/org/apache/axis2/description/WSDLToAxisServiceBuilder.java b/modules/kernel/src/org/apache/axis2/description/WSDLToAxisServiceBuilder.java
index 5fdfe4f697..b271b069ce 100644
--- a/modules/kernel/src/org/apache/axis2/description/WSDLToAxisServiceBuilder.java
+++ b/modules/kernel/src/org/apache/axis2/description/WSDLToAxisServiceBuilder.java
@@ -154,13 +154,22 @@ public abstract class WSDLToAxisServiceBuilder {
delegate = new org.apache.ws.commons.schema.resolver.DefaultURIResolver();
public org.xml.sax.InputSource resolveEntity(
String ns, String loc, String base) {
- if (loc != null
- && (loc.regionMatches(true, 0, "http:", 0, 5)
- || loc.regionMatches(true, 0, "https:", 0, 6)
- || loc.regionMatches(true, 0, "ftp:", 0, 4)
- || loc.regionMatches(true, 0, "jar:", 0, 4))) {
+ // Resolve against base URI before checking —
+ // a relative loc with a remote base must be caught
+ String resolved = loc;
+ if (base != null && loc != null) {
+ try {
+ resolved = java.net.URI.create(base).resolve(loc).toString();
+ } catch (IllegalArgumentException ignored) {
+ }
+ }
+ if (resolved != null
+ && (resolved.regionMatches(true, 0, "http:", 0, 5)
+ || resolved.regionMatches(true, 0, "https:", 0, 6)
+ || resolved.regionMatches(true, 0, "ftp:", 0, 4)
+ || resolved.regionMatches(true, 0, "jar:", 0, 4))) {
throw new RuntimeException(
- "Remote schemaLocation blocked: " + loc
+ "Remote schemaLocation blocked: " + resolved
+ " (use setCustomResolver to opt in)");
}
return delegate.resolveEntity(ns, loc, base);
diff --git a/modules/kernel/test/org/apache/axis2/deployment/URIResolverTest.java b/modules/kernel/test/org/apache/axis2/deployment/URIResolverTest.java
index 92ef18d604..e74693dc2c 100644
--- a/modules/kernel/test/org/apache/axis2/deployment/URIResolverTest.java
+++ b/modules/kernel/test/org/apache/axis2/deployment/URIResolverTest.java
@@ -62,4 +62,20 @@ public class URIResolverTest extends TestCase {
assertNull("AAR resolver must block remote https URLs (SSRF)",
inputSource.getSystemId());
}
+
+ /**
+ * Verify that a relative schemaLocation with a remote baseUri is
+ * blocked — prevents the bypass where a relative path resolves to
+ * a remote URL via the base URI.
+ */
+ public void testRelativePathWithRemoteBaseBlocked() {
+ WarFileBasedURIResolver war = new WarFileBasedURIResolver(null);
+ InputSource inputSource = war.resolveEntity(null,
+ "evil.xsd",
+ "http://attacker.example.com/wsdl/");
+ assertNotNull(inputSource);
+ // Resolved URI is http://attacker.example.com/wsdl/evil.xsd — must be blocked
+ assertNull("WAR resolver must block relative path resolving to remote URL",
+ inputSource.getSystemId());
+ }
}