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 cfbaf6a6484f634d4937477f2e1391c57f733635
Author: Robert Lazarski <[email protected]>
AuthorDate: Tue Aug 4 08:27:04 2026 -1000

    Cover the attack path an inbound wsa:ReplyTo actually takes
    
    The proof-of-concept supplied with the report drives the transport sender
    after setting ReplyTo
    programmatically, which no remote caller can do, and the policy unit tests 
call
    the check directly. Neither exercises the only route in from outside: a
    wsa:ReplyTo or wsa:FaultTo header on an inbound message, parsed by
    AddressingInHandler.
    
    These tests send that header and assert the handler faults on a link-local
    address, accepts an ordinary external one, and follows
    blockPrivateNetworkResponseEndpoints for a private one.
    
    Co-Authored-By: Claude Fable 5 <[email protected]>
---
 .../ResponseEndpointPolicyHandlerTest.java         | 146 +++++++++++++++++++++
 1 file changed, 146 insertions(+)

diff --git 
a/modules/addressing/test/org/apache/axis2/handlers/addressing/ResponseEndpointPolicyHandlerTest.java
 
b/modules/addressing/test/org/apache/axis2/handlers/addressing/ResponseEndpointPolicyHandlerTest.java
new file mode 100644
index 0000000000..82273ccaaf
--- /dev/null
+++ 
b/modules/addressing/test/org/apache/axis2/handlers/addressing/ResponseEndpointPolicyHandlerTest.java
@@ -0,0 +1,146 @@
+/*
+ * 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.handlers.addressing;
+
+import junit.framework.TestCase;
+import org.apache.axiom.om.OMAbstractFactory;
+import org.apache.axiom.soap.SOAPEnvelope;
+import org.apache.axiom.soap.SOAPFactory;
+import org.apache.axiom.soap.SOAPHeaderBlock;
+import org.apache.axis2.AxisFault;
+import org.apache.axis2.addressing.AddressingConstants;
+import org.apache.axis2.context.ConfigurationContext;
+import org.apache.axis2.context.ConfigurationContextFactory;
+import org.apache.axis2.context.MessageContext;
+import org.apache.axis2.description.Parameter;
+
+/**
+ * End-to-end coverage of the response-endpoint policy through the handler that
+ * actually parses inbound WS-Addressing headers.
+ *
+ * <p>This exists because neither the unit tests for the policy itself nor the
+ * reporter's proof-of-concept exercise the path an attacker actually has. The
+ * PoC builds a MessageContext and calls {@code setReplyTo} in Java before
+ * driving the transport sender, which no remote caller can do; the only way in
+ * from outside is a {@code wsa:ReplyTo} header on an inbound message, which is
+ * what these tests send.
+ */
+public class ResponseEndpointPolicyHandlerTest extends TestCase {
+
+    private AddressingInHandler handler;
+    private ConfigurationContext configurationContext;
+
+    protected void setUp() throws Exception {
+        super.setUp();
+        handler = new AddressingInHandler();
+        configurationContext = 
ConfigurationContextFactory.createEmptyConfigurationContext();
+    }
+
+    /**
+     * The cloud instance-metadata address is refused straight out of the box.
+     */
+    public void testMetadataReplyToIsRejected() throws Exception {
+        try {
+            invokeWith("ReplyTo", "http://169.254.169.254/latest/meta-data/";);
+            fail("A link-local ReplyTo should have faulted");
+        } catch (AxisFault expected) {
+            // expected
+        }
+    }
+
+    /** The same applies to FaultTo, which fires on any request that faults. */
+    public void testMetadataFaultToIsRejected() throws Exception {
+        try {
+            invokeWith("FaultTo", "http://169.254.169.254/latest/meta-data/";);
+            fail("A link-local FaultTo should have faulted");
+        } catch (AxisFault expected) {
+            // expected
+        }
+    }
+
+    /** A genuine external reply endpoint still passes through untouched. */
+    public void testOrdinaryReplyToIsAccepted() throws Exception {
+        MessageContext mc = invokeWith("ReplyTo", "http://192.0.2.25/replies";);
+        assertEquals("http://192.0.2.25/replies";, 
mc.getReplyTo().getAddress());
+    }
+
+    /**
+     * With the strict posture enabled a private callback address is refused 
too,
+     * which is the setting a deployment exposed to untrusted callers wants.
+     */
+    public void testPrivateReplyToRejectedUnderStrictPolicy() throws Exception 
{
+        configurationContext.getAxisConfiguration().addParameter(
+                new Parameter(ResponseEndpointPolicy.BLOCK_PRIVATE_NETWORKS, 
"true"));
+        try {
+            invokeWith("ReplyTo", "http://10.1.2.3/internal";);
+            fail("A private ReplyTo should have faulted under the strict 
policy");
+        } catch (AxisFault expected) {
+            // expected
+        }
+    }
+
+    /** Under the default policy that same private callback is allowed. */
+    public void testPrivateReplyToAcceptedByDefault() throws Exception {
+        MessageContext mc = invokeWith("ReplyTo", "http://10.1.2.3/internal";);
+        assertEquals("http://10.1.2.3/internal";, mc.getReplyTo().getAddress());
+    }
+
+    /**
+     * Build an inbound message carrying the given addressing header and run 
the
+     * in-handler over it, exactly as the engine would for a remote request.
+     */
+    private MessageContext invokeWith(String headerName, String address) 
throws Exception {
+        SOAPFactory factory = OMAbstractFactory.getSOAP11Factory();
+        SOAPEnvelope envelope = factory.getDefaultEnvelope();
+
+        addAddressingHeader(factory, envelope, AddressingConstants.WSA_TO,
+                "http://localhost/axis2/services/Target";, false);
+        addAddressingHeader(factory, envelope, AddressingConstants.WSA_ACTION,
+                "urn:someAction", false);
+        addAddressingHeader(factory, envelope, 
AddressingConstants.WSA_MESSAGE_ID,
+                "urn:uuid:test-message-id", false);
+        addAddressingHeader(factory, envelope, headerName, address, true);
+
+        MessageContext mc = new MessageContext();
+        mc.setConfigurationContext(configurationContext);
+        mc.setServerSide(true);
+        mc.setEnvelope(envelope);
+
+        handler.invoke(mc);
+        return mc;
+    }
+
+    /**
+     * @param nested true for the EPR-valued headers, whose address sits in a
+     *               child wsa:Address element rather than the header text
+     */
+    private void addAddressingHeader(SOAPFactory factory, SOAPEnvelope 
envelope,
+                                     String name, String value, boolean 
nested) {
+        SOAPHeaderBlock header = envelope.getHeader().addHeaderBlock(name,
+                
factory.createOMNamespace(AddressingConstants.Final.WSA_NAMESPACE, "wsa"));
+        if (nested) {
+            factory.createOMElement(AddressingConstants.EPR_ADDRESS,
+                    
factory.createOMNamespace(AddressingConstants.Final.WSA_NAMESPACE, "wsa"),
+                    header).setText(value);
+        } else {
+            header.setText(value);
+        }
+    }
+}

Reply via email to