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 1b5b2fcac5336568e51dc893d24543c6c47d6d78
Author: Robert Lazarski <[email protected]>
AuthorDate: Tue Aug 4 06:38:37 2026 -1000

    Do not restrict WS-Addressing reply endpoints to HTTP
    
    The egress policy added in 1671a68660 allowed only http and https schemes,
    which was wrong. MessageContextBuilder.setupCorrectTransportOut resolves the
    response transport from the endpoint reference's scheme against whatever is
    registered, so a decoupled reply over JMS, mail or TCP is a legitimate Axis2
    configuration and the policy would have faulted it.
    
    Default the scheme list to http,https,jms,mailto,tcp and make it 
configurable
    via allowedResponseEndpointSchemes. The pivot-only schemes -- file, gopher,
    jar, ftp -- stay out, and the private-range check still applies to any 
address
    that names a host, so tcp://127.0.0.1 is refused the same as 
http://127.0.0.1.
    Schemes that name a destination rather than a network host, such as mailto: 
and
    JNDI-style jms:, have no address to range-check and pass.
    
    Co-Authored-By: Claude Fable 5 <[email protected]>
---
 .../addressing/ResponseEndpointPolicy.java         | 55 ++++++++++++++++++++--
 .../addressing/ResponseEndpointPolicyTest.java     | 42 +++++++++++++++--
 modules/kernel/conf/axis2.xml                      |  6 +++
 3 files changed, 95 insertions(+), 8 deletions(-)

diff --git 
a/modules/addressing/src/org/apache/axis2/handlers/addressing/ResponseEndpointPolicy.java
 
b/modules/addressing/src/org/apache/axis2/handlers/addressing/ResponseEndpointPolicy.java
index 81b330e802..f6a2b4219b 100644
--- 
a/modules/addressing/src/org/apache/axis2/handlers/addressing/ResponseEndpointPolicy.java
+++ 
b/modules/addressing/src/org/apache/axis2/handlers/addressing/ResponseEndpointPolicy.java
@@ -77,9 +77,23 @@ final class ResponseEndpointPolicy {
     static final String ALLOW_NON_ANONYMOUS = 
"allowNonAnonymousResponseEndpoints";
     static final String BLOCK_PRIVATE_NETWORKS = 
"blockPrivateNetworkResponseEndpoints";
     static final String ALLOWED_HOSTS = "allowedResponseEndpointHosts";
+    static final String ALLOWED_SCHEMES_PARAMETER = 
"allowedResponseEndpointSchemes";
 
-    /** Only transports that carry a genuine reply are worth honouring. */
-    private static final Set<String> ALLOWED_SCHEMES =
+    /**
+     * Schemes Axis2 ships a sender for that can carry a decoupled reply.
+     *
+     * <p>This is deliberately wider than http/https: {@code 
setupCorrectTransportOut}
+     * resolves the response transport from the endpoint reference's scheme
+     * against whatever is registered, so a JMS, mail or TCP reply address is a
+     * legitimate configuration and must not be refused here. What the list 
keeps
+     * out are the schemes that only ever serve as an SSRF pivot — file, 
gopher,
+     * jar, ftp and the like.
+     */
+    private static final Set<String> DEFAULT_ALLOWED_SCHEMES = new 
HashSet<String>(
+            Arrays.asList("http", "https", "jms", "mailto", "tcp"));
+
+    /** Schemes for which a missing host means the address is unusable. */
+    private static final Set<String> HOST_BEARING_SCHEMES =
             new HashSet<String>(Arrays.asList("http", "https"));
 
     private ResponseEndpointPolicy() {
@@ -119,15 +133,26 @@ final class ResponseEndpointPolicy {
         }
 
         String scheme = uri.getScheme();
-        if (scheme == null || 
!ALLOWED_SCHEMES.contains(scheme.toLowerCase(Locale.ENGLISH))) {
+        if (scheme == null) {
+            log.warn("Rejecting WS-Addressing response endpoint with no 
scheme");
+            return false;
+        }
+        scheme = scheme.toLowerCase(Locale.ENGLISH);
+        if (!allowedSchemes(messageContext).contains(scheme)) {
             log.warn("Rejecting WS-Addressing response endpoint with 
unsupported scheme: " + scheme);
             return false;
         }
 
         String host = uri.getHost();
         if (host == null || host.isEmpty()) {
-            log.warn("Rejecting WS-Addressing response endpoint with no host 
component");
-            return false;
+            if (HOST_BEARING_SCHEMES.contains(scheme)) {
+                log.warn("Rejecting WS-Addressing response endpoint with no 
host component");
+                return false;
+            }
+            // mailto: and JNDI-style jms: addresses name a destination rather
+            // than a network host, so there is no address to range-check. The
+            // transport behind them has to be enabled by an administrator.
+            return true;
         }
 
         String allowedHosts = stringParameter(messageContext, ALLOWED_HOSTS);
@@ -202,6 +227,26 @@ final class ResponseEndpointPolicy {
                 && (bytes[1] & 0xc0) == 0x40;
     }
 
+    /**
+     * The permitted scheme set, overridable so a deployment with a custom
+     * transport can name its scheme without having to disable the policy.
+     */
+    private static Set<String> allowedSchemes(MessageContext messageContext) {
+        String configured = stringParameter(messageContext, 
ALLOWED_SCHEMES_PARAMETER);
+        if (configured == null || configured.trim().isEmpty()) {
+            return DEFAULT_ALLOWED_SCHEMES;
+        }
+        Set<String> schemes = new HashSet<String>();
+        String[] parts = configured.split(",");
+        for (int i = 0; i < parts.length; i++) {
+            String scheme = parts[i].trim().toLowerCase(Locale.ENGLISH);
+            if (!scheme.isEmpty()) {
+                schemes.add(scheme);
+            }
+        }
+        return schemes;
+    }
+
     private static boolean booleanParameter(MessageContext messageContext, 
String name,
                                             boolean defaultValue) {
         if (messageContext == null) {
diff --git 
a/modules/addressing/test/org/apache/axis2/handlers/addressing/ResponseEndpointPolicyTest.java
 
b/modules/addressing/test/org/apache/axis2/handlers/addressing/ResponseEndpointPolicyTest.java
index fd1d26f06a..c4ec23e0a0 100644
--- 
a/modules/addressing/test/org/apache/axis2/handlers/addressing/ResponseEndpointPolicyTest.java
+++ 
b/modules/addressing/test/org/apache/axis2/handlers/addressing/ResponseEndpointPolicyTest.java
@@ -81,16 +81,52 @@ public class ResponseEndpointPolicyTest extends TestCase {
     }
 
     /**
-     * Only transports that can carry a genuine reply are honoured, so the
-     * scheme-based SSRF pivots are refused before any host check.
+     * The schemes that only ever serve as an SSRF pivot are refused before any
+     * host check.
      */
-    public void testNonHttpSchemesAreRejected() {
+    public void testPivotSchemesAreRejected() {
         assertFalse(ResponseEndpointPolicy.isAllowed(
                 new EndpointReference("file:///etc/passwd"), messageContext));
         assertFalse(ResponseEndpointPolicy.isAllowed(
                 new EndpointReference("gopher://example.com/1";), 
messageContext));
         assertFalse(ResponseEndpointPolicy.isAllowed(
                 new EndpointReference("jar:http://example.com/a.jar!/";), 
messageContext));
+        assertFalse(ResponseEndpointPolicy.isAllowed(
+                new EndpointReference("ftp://example.com/drop";), 
messageContext));
+    }
+
+    /**
+     * A decoupled reply over JMS, mail or TCP is a legitimate Axis2
+     * configuration — the response transport is resolved from the endpoint
+     * reference's scheme against whatever is registered — so the policy must 
not
+     * restrict replies to HTTP.
+     */
+    public void testNonHttpTransportSchemesAreAllowed() {
+        assertTrue(ResponseEndpointPolicy.isAllowed(
+                new 
EndpointReference("jms:/ReplyQueue?transport.jms.ConnectionFactory=qcf"),
+                messageContext));
+        assertTrue(ResponseEndpointPolicy.isAllowed(
+                new EndpointReference("mailto:[email protected]";), 
messageContext));
+        assertTrue(ResponseEndpointPolicy.isAllowed(
+                new EndpointReference("tcp://192.0.2.25:6060/svc"), 
messageContext));
+    }
+
+    /**
+     * The range check is about the destination address, not the scheme, so a
+     * non-HTTP address that does name a host is still screened.
+     */
+    public void testNonHttpSchemeStillGetsTheRangeCheck() {
+        assertFalse(ResponseEndpointPolicy.isAllowed(
+                new EndpointReference("tcp://127.0.0.1:6060/svc"), 
messageContext));
+    }
+
+    public void testSchemeListIsConfigurable() throws Exception {
+        setParameter(ResponseEndpointPolicy.ALLOWED_SCHEMES_PARAMETER, 
"http,https");
+        assertFalse("A scheme outside the configured list should be refused",
+                ResponseEndpointPolicy.isAllowed(
+                        new EndpointReference("mailto:[email protected]";), 
messageContext));
+        assertTrue(ResponseEndpointPolicy.isAllowed(
+                new EndpointReference("http://192.0.2.25/replies";), 
messageContext));
     }
 
     /**
diff --git a/modules/kernel/conf/axis2.xml b/modules/kernel/conf/axis2.xml
index 63c6a06f62..164b952683 100644
--- a/modules/kernel/conf/axis2.xml
+++ b/modules/kernel/conf/axis2.xml
@@ -74,10 +74,16 @@
 
     allowedResponseEndpointHosts, when set to a comma-separated host list,
     restricts response endpoints to exactly those hosts.
+
+    allowedResponseEndpointSchemes defaults to http,https,jms,mailto,tcp - a
+    decoupled reply over JMS, mail or TCP is a legitimate configuration, so the
+    list is not restricted to HTTP. Schemes outside it, such as file or gopher,
+    are refused. Add to this list if a custom transport is in use.
     -->
     <parameter name="allowNonAnonymousResponseEndpoints">true</parameter>
     <parameter name="blockPrivateNetworkResponseEndpoints">true</parameter>
     <!--<parameter 
name="allowedResponseEndpointHosts">replies.example.com</parameter>-->
+    <!--<parameter 
name="allowedResponseEndpointSchemes">http,https</parameter>-->
 
 
     <!--Uncomment if you want to plugin your own attachments lifecycle 
implementation -->

Reply via email to