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 1d325f4deb20f2ee5d2be97e3c618339009d259f Author: Robert Lazarski <[email protected]> AuthorDate: Wed Aug 5 08:30:30 2026 -1000 Permit only https as a reply scheme unless told otherwise The previous default list -- http, https, jms, mailto, tcp -- was wider than any real deployment needs. Dropping to https alone matters most for the http exclusion, which is not merely tidying: the cloud instance-metadata services this class of SSRF is aimed at are HTTP-only. AWS and Azure on 169.254.169.254 and GCP on metadata.google.internal offer no HTTPS listener, so refusing the scheme removes that target without relying on the address checks at all. jms, mailto and tcp keep their senders but become opt-in; a deployment naming one of them, or genuinely replying over plain HTTP inside a trusted network, lists it in allowedResponseEndpointSchemes. Worth being clear about the size of the win: this list is only consulted once allowNonAnonymousResponseEndpoints is on, and that now defaults to false, so this is defence in depth inside an already opt-in path rather than a first-line control. Also renames the class to AddressingResponseEndpointPolicy. It is reached from two places, both WS-Addressing: the header parsing in AddressingInHandler, and MessageContextBuilder where a non-anonymous response endpoint acquires its transport. Now that it sits in the kernel and is called from generic code, the bare name under-specified what it governs; the new one matches the AddressingHelper / AddressingFaultsHelper / AddressingConstants family beside it in the same package. Co-Authored-By: Claude Fable 5 <[email protected]> --- .../handlers/addressing/AddressingInHandler.java | 4 +- .../addressing/AddressingFinalInHandlerTest.java | 3 + ...dressingResponseEndpointPolicyHandlerTest.java} | 11 +- modules/integration/pom.xml | 18 +++ .../test-resources/mtom/MTOM-enabled-axis2.xml | 1 + .../mtom/MTOM-fileCache-enabled-axis2.xml | 1 + modules/kernel/conf/axis2.xml | 14 ++- ....java => AddressingResponseEndpointPolicy.java} | 36 ++++-- .../org/apache/axis2/engine/AxisConfiguration.java | 2 +- .../apache/axis2/util/MessageContextBuilder.java | 6 +- ...a => AddressingResponseEndpointPolicyTest.java} | 122 +++++++++++++-------- modules/webapp/conf/axis2.xml | 14 ++- 12 files changed, 155 insertions(+), 77 deletions(-) diff --git a/modules/addressing/src/org/apache/axis2/handlers/addressing/AddressingInHandler.java b/modules/addressing/src/org/apache/axis2/handlers/addressing/AddressingInHandler.java index 8b64481476..c7266d8b51 100644 --- a/modules/addressing/src/org/apache/axis2/handlers/addressing/AddressingInHandler.java +++ b/modules/addressing/src/org/apache/axis2/handlers/addressing/AddressingInHandler.java @@ -32,7 +32,7 @@ import org.apache.axis2.addressing.AddressingFaultsHelper; import org.apache.axis2.addressing.EndpointReference; import org.apache.axis2.addressing.EndpointReferenceHelper; import org.apache.axis2.addressing.RelatesTo; -import org.apache.axis2.addressing.ResponseEndpointPolicy; +import org.apache.axis2.addressing.AddressingResponseEndpointPolicy; import org.apache.axis2.client.Options; import org.apache.axis2.context.MessageContext; import org.apache.axis2.description.HandlerDescription; @@ -451,7 +451,7 @@ public class AddressingInHandler extends AbstractTemplatedHandler implements Add if (!messageContext.isServerSide()) { return; } - if (!ResponseEndpointPolicy.isAllowed(epr, messageContext)) { + if (!AddressingResponseEndpointPolicy.isAllowed(epr, messageContext)) { AddressingFaultsHelper .triggerInvalidEPRFault(messageContext, soapHeaderBlock.getLocalName()); } diff --git a/modules/addressing/test/org/apache/axis2/handlers/addressing/AddressingFinalInHandlerTest.java b/modules/addressing/test/org/apache/axis2/handlers/addressing/AddressingFinalInHandlerTest.java index 0015569bc9..4ce651ad97 100644 --- a/modules/addressing/test/org/apache/axis2/handlers/addressing/AddressingFinalInHandlerTest.java +++ b/modules/addressing/test/org/apache/axis2/handlers/addressing/AddressingFinalInHandlerTest.java @@ -223,6 +223,9 @@ public class AddressingFinalInHandlerTest extends AddressingInHandlerTestBase { mc.getConfigurationContext().getAxisConfiguration().addParameter( new org.apache.axis2.description.Parameter( "allowNonAnonymousResponseEndpoints", "true")); + mc.getConfigurationContext().getAxisConfiguration().addParameter( + new org.apache.axis2.description.Parameter( + "allowedResponseEndpointSchemes", "http,https")); try { mc.setSoapAction("http://ws.apache.org/tests/action"); basicExtractAddressingInformationFromHeaders(testfile, mc); diff --git a/modules/addressing/test/org/apache/axis2/handlers/addressing/ResponseEndpointPolicyHandlerTest.java b/modules/addressing/test/org/apache/axis2/handlers/addressing/AddressingResponseEndpointPolicyHandlerTest.java similarity index 92% rename from modules/addressing/test/org/apache/axis2/handlers/addressing/ResponseEndpointPolicyHandlerTest.java rename to modules/addressing/test/org/apache/axis2/handlers/addressing/AddressingResponseEndpointPolicyHandlerTest.java index 8a2cbe3810..02ceaa84e2 100644 --- a/modules/addressing/test/org/apache/axis2/handlers/addressing/ResponseEndpointPolicyHandlerTest.java +++ b/modules/addressing/test/org/apache/axis2/handlers/addressing/AddressingResponseEndpointPolicyHandlerTest.java @@ -29,7 +29,7 @@ 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.addressing.ResponseEndpointPolicy; +import org.apache.axis2.addressing.AddressingResponseEndpointPolicy; import org.apache.axis2.description.Parameter; /** @@ -43,7 +43,7 @@ import org.apache.axis2.description.Parameter; * from outside is a {@code wsa:ReplyTo} header on an inbound message, which is * what these tests send. */ -public class ResponseEndpointPolicyHandlerTest extends TestCase { +public class AddressingResponseEndpointPolicyHandlerTest extends TestCase { private AddressingInHandler handler; private ConfigurationContext configurationContext; @@ -55,7 +55,10 @@ public class ResponseEndpointPolicyHandlerTest extends TestCase { // Decoupled responses are off by default; opt in so these tests reach the // address checks. testDecoupledReplyToRefusedByDefault covers the default. configurationContext.getAxisConfiguration().addParameter( - new Parameter(ResponseEndpointPolicy.ALLOW_NON_ANONYMOUS, "true")); + new Parameter(AddressingResponseEndpointPolicy.ALLOW_NON_ANONYMOUS, "true")); + // Only https ships as permitted; these cases use http addresses. + configurationContext.getAxisConfiguration().addParameter( + new Parameter(AddressingResponseEndpointPolicy.ALLOWED_SCHEMES_PARAMETER, "http,https")); } /** @@ -114,7 +117,7 @@ public class ResponseEndpointPolicyHandlerTest extends TestCase { */ public void testPrivateReplyToRejectedUnderStrictPolicy() throws Exception { configurationContext.getAxisConfiguration().addParameter( - new Parameter(ResponseEndpointPolicy.BLOCK_PRIVATE_NETWORKS, "true")); + new Parameter(AddressingResponseEndpointPolicy.BLOCK_PRIVATE_NETWORKS, "true")); try { invokeWith("ReplyTo", "http://10.1.2.3/internal"); fail("A private ReplyTo should have faulted under the strict policy"); diff --git a/modules/integration/pom.xml b/modules/integration/pom.xml index 2ad66dba7b..5647538334 100644 --- a/modules/integration/pom.xml +++ b/modules/integration/pom.xml @@ -267,6 +267,12 @@ <name>allowNonAnonymousResponseEndpoints</name> <value>true</value> </parameter> + <!-- Only https is permitted by default; these + callbacks are plain http on loopback. --> + <parameter> + <name>allowedResponseEndpointSchemes</name> + <value>http,https</value> + </parameter> </parameters> <handlers> <handler> @@ -305,6 +311,12 @@ <name>allowNonAnonymousResponseEndpoints</name> <value>true</value> </parameter> + <!-- Only https is permitted by default; these + callbacks are plain http on loopback. --> + <parameter> + <name>allowedResponseEndpointSchemes</name> + <value>http,https</value> + </parameter> </parameters> </generatedAxis2xml> <modules>addressing</modules> @@ -370,6 +382,12 @@ <name>allowNonAnonymousResponseEndpoints</name> <value>true</value> </parameter> + <!-- Only https is permitted by default; these + callbacks are plain http on loopback. --> + <parameter> + <name>allowedResponseEndpointSchemes</name> + <value>http,https</value> + </parameter> </parameters> </generatedAxis2xml> <modules>addressing</modules> diff --git a/modules/integration/test-resources/mtom/MTOM-enabled-axis2.xml b/modules/integration/test-resources/mtom/MTOM-enabled-axis2.xml index 2c55ca73d5..d97cf59ff3 100644 --- a/modules/integration/test-resources/mtom/MTOM-enabled-axis2.xml +++ b/modules/integration/test-resources/mtom/MTOM-enabled-axis2.xml @@ -21,6 +21,7 @@ <!-- The separate-listener MTOM tests use a decoupled wsa:ReplyTo, which is off by default; opt in so they exercise that path. --> <parameter name="allowNonAnonymousResponseEndpoints">true</parameter> + <parameter name="allowedResponseEndpointSchemes">http,https</parameter> <!-- ================================================= --> <!-- Parameters --> diff --git a/modules/integration/test-resources/mtom/MTOM-fileCache-enabled-axis2.xml b/modules/integration/test-resources/mtom/MTOM-fileCache-enabled-axis2.xml index 0a86f27802..e3fbf0dec6 100644 --- a/modules/integration/test-resources/mtom/MTOM-fileCache-enabled-axis2.xml +++ b/modules/integration/test-resources/mtom/MTOM-fileCache-enabled-axis2.xml @@ -21,6 +21,7 @@ <!-- The separate-listener MTOM tests use a decoupled wsa:ReplyTo, which is off by default; opt in so they exercise that path. --> <parameter name="allowNonAnonymousResponseEndpoints">true</parameter> + <parameter name="allowedResponseEndpointSchemes">http,https</parameter> <!-- ================================================= --> <!-- Parameters --> diff --git a/modules/kernel/conf/axis2.xml b/modules/kernel/conf/axis2.xml index 8bc34b9091..68614835b0 100644 --- a/modules/kernel/conf/axis2.xml +++ b/modules/kernel/conf/axis2.xml @@ -92,15 +92,19 @@ used by the checks above, so a slow resolver cannot hold a request thread. Literal addresses are never looked up. - 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. + allowedResponseEndpointSchemes defaults to https ALONE. Plain http is + excluded on purpose: the cloud instance-metadata services this class of SSRF + targets (169.254.169.254 on AWS and Azure, metadata.google.internal on GCP) + are HTTP-only, so refusing the scheme removes that target outright. jms, + mailto and tcp have senders in the tree but are vanishingly rare as reply + destinations, so they are opt-in too. Name whatever a deployment genuinely + replies over, e.g. http,https for plain-HTTP callbacks inside a trusted + network. --> <parameter name="allowNonAnonymousResponseEndpoints">false</parameter> <parameter name="blockPrivateNetworkResponseEndpoints">false</parameter> <!--<parameter name="allowedResponseEndpointHosts">replies.example.com</parameter>--> - <!--<parameter name="allowedResponseEndpointSchemes">http,https</parameter>--> + <!--<parameter name="allowedResponseEndpointSchemes">https</parameter>--> <!--Uncomment if you want to plugin your own attachments lifecycle implementation --> diff --git a/modules/kernel/src/org/apache/axis2/addressing/ResponseEndpointPolicy.java b/modules/kernel/src/org/apache/axis2/addressing/AddressingResponseEndpointPolicy.java similarity index 92% rename from modules/kernel/src/org/apache/axis2/addressing/ResponseEndpointPolicy.java rename to modules/kernel/src/org/apache/axis2/addressing/AddressingResponseEndpointPolicy.java index 6aab3227a8..c9b10946d3 100644 --- a/modules/kernel/src/org/apache/axis2/addressing/ResponseEndpointPolicy.java +++ b/modules/kernel/src/org/apache/axis2/addressing/AddressingResponseEndpointPolicy.java @@ -82,11 +82,16 @@ import java.util.concurrent.TimeoutException; * <dt>{@code allowedResponseEndpointHosts} (no default)</dt> * <dd>A comma-separated host allow-list. When set, a response endpoint host must * appear in it, which supersedes the network-range check.</dd> + * + * <dt>{@code allowedResponseEndpointSchemes} (default {@code https})</dt> + * <dd>A comma-separated scheme allow-list, applied once decoupled responses are + * enabled at all. See {@link #DEFAULT_ALLOWED_SCHEMES} for why the default is + * HTTPS alone.</dd> * </dl> */ -public final class ResponseEndpointPolicy { +public final class AddressingResponseEndpointPolicy { - private static final Log log = LogFactory.getLog(ResponseEndpointPolicy.class); + private static final Log log = LogFactory.getLog(AddressingResponseEndpointPolicy.class); public static final String ALLOW_NON_ANONYMOUS = "allowNonAnonymousResponseEndpoints"; public static final String BLOCK_PRIVATE_NETWORKS = "blockPrivateNetworkResponseEndpoints"; @@ -106,23 +111,30 @@ public final class ResponseEndpointPolicy { private static final int RESOLVER_QUEUE_DEPTH = 64; /** - * Schemes Axis2 ships a sender for that can carry a decoupled reply. + * Schemes a decoupled reply may use, unless {@link #ALLOWED_SCHEMES_PARAMETER} + * widens it. Just {@code https}. + * + * <p>Plain {@code http} is excluded deliberately, and it is the exclusion that + * carries the most weight: the cloud instance-metadata services this class of + * SSRF is usually aimed at — AWS and Azure on 169.254.169.254, GCP on + * metadata.google.internal — are reachable over HTTP only and offer no HTTPS + * listener. Refusing plain HTTP therefore takes that target away by scheme + * alone, without depending on the address checks below. * - * <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. + * <p>{@code jms}, {@code mailto} and {@code tcp} have senders in the tree but + * are vanishingly rare as reply destinations, so they are opt-in rather than + * default-on. A deployment that needs any of these, or that genuinely replies + * over plain HTTP inside a trusted network, names them in + * {@link #ALLOWED_SCHEMES_PARAMETER}. */ - private static final Set<String> DEFAULT_ALLOWED_SCHEMES = new HashSet<String>( - Arrays.asList("http", "https", "jms", "mailto", "tcp")); + private static final Set<String> DEFAULT_ALLOWED_SCHEMES = + new HashSet<String>(Arrays.asList("https")); /** 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() { + private AddressingResponseEndpointPolicy() { } /** diff --git a/modules/kernel/src/org/apache/axis2/engine/AxisConfiguration.java b/modules/kernel/src/org/apache/axis2/engine/AxisConfiguration.java index a0dc32ac12..9582287a62 100644 --- a/modules/kernel/src/org/apache/axis2/engine/AxisConfiguration.java +++ b/modules/kernel/src/org/apache/axis2/engine/AxisConfiguration.java @@ -1343,7 +1343,7 @@ public class AxisConfiguration extends AxisDescription { // redeployment and pin this web application's class loader. MultipartTempFileTracker.shutdown(); // Same reason: the WS-Addressing endpoint resolver runs a thread pool. - org.apache.axis2.addressing.ResponseEndpointPolicy.shutdown(); + org.apache.axis2.addressing.AddressingResponseEndpointPolicy.shutdown(); this.policySupportedModules.clear(); this.moduleConfigmap.clear(); this.allEndpoints.clear(); diff --git a/modules/kernel/src/org/apache/axis2/util/MessageContextBuilder.java b/modules/kernel/src/org/apache/axis2/util/MessageContextBuilder.java index 91a3f786f2..87f78f42f9 100644 --- a/modules/kernel/src/org/apache/axis2/util/MessageContextBuilder.java +++ b/modules/kernel/src/org/apache/axis2/util/MessageContextBuilder.java @@ -43,7 +43,7 @@ import org.apache.axis2.addressing.AddressingConstants; import org.apache.axis2.addressing.AddressingConstants.Final; import org.apache.axis2.addressing.AddressingHelper; import org.apache.axis2.addressing.EndpointReference; -import org.apache.axis2.addressing.ResponseEndpointPolicy; +import org.apache.axis2.addressing.AddressingResponseEndpointPolicy; import org.apache.axis2.addressing.RelatesTo; import org.apache.axis2.context.ConfigurationContext; import org.apache.axis2.context.MessageContext; @@ -390,10 +390,10 @@ public class MessageContextBuilder { // check cannot be walked around by reaching the sender another // way. Apache CXF places its equivalent check at the same // depth, in DecoupledDestination.getBackChannel. - if (!ResponseEndpointPolicy.isAllowed(responseEPR, context)) { + if (!AddressingResponseEndpointPolicy.isAllowed(responseEPR, context)) { throw new AxisFault("Refusing to send a response to the " + "endpoint reference named by this message; see the " - + ResponseEndpointPolicy.ALLOW_NON_ANONYMOUS + " parameter"); + + AddressingResponseEndpointPolicy.ALLOW_NON_ANONYMOUS + " parameter"); } URI uri = new URI(responseEPR.getAddress()); String scheme = uri.getScheme(); diff --git a/modules/kernel/test/org/apache/axis2/addressing/ResponseEndpointPolicyTest.java b/modules/kernel/test/org/apache/axis2/addressing/AddressingResponseEndpointPolicyTest.java similarity index 64% rename from modules/kernel/test/org/apache/axis2/addressing/ResponseEndpointPolicyTest.java rename to modules/kernel/test/org/apache/axis2/addressing/AddressingResponseEndpointPolicyTest.java index d672157d5c..67522a170a 100644 --- a/modules/kernel/test/org/apache/axis2/addressing/ResponseEndpointPolicyTest.java +++ b/modules/kernel/test/org/apache/axis2/addressing/AddressingResponseEndpointPolicyTest.java @@ -29,7 +29,7 @@ import org.apache.axis2.engine.AxisConfiguration; * Unit tests for the egress policy applied to inbound WS-Addressing ReplyTo and * FaultTo endpoint references. */ -public class ResponseEndpointPolicyTest extends TestCase { +public class AddressingResponseEndpointPolicyTest extends TestCase { private AxisConfiguration axisConfiguration; private MessageContext messageContext; @@ -43,7 +43,36 @@ public class ResponseEndpointPolicyTest extends TestCase { // Decoupled responses are off by default. Most tests here cover what the // policy does to an endpoint it is actually willing to consider, so they // opt in; testDecoupledResponsesAreOffByDefault covers the default. - setParameter(ResponseEndpointPolicy.ALLOW_NON_ANONYMOUS, "true"); + setParameter(AddressingResponseEndpointPolicy.ALLOW_NON_ANONYMOUS, "true"); + // Only https is permitted by default. These tests exercise the address + // checks over plain http for readability, so widen the list here; + // testOnlyHttpsIsAllowedByDefault covers the shipped default. + setParameter(AddressingResponseEndpointPolicy.ALLOWED_SCHEMES_PARAMETER, "http,https"); + } + + /** + * Plain HTTP is not permitted unless asked for. This matters beyond tidiness: + * the cloud instance-metadata endpoints are HTTP-only, so refusing the scheme + * removes that target without relying on the address checks at all. + */ + public void testOnlyHttpsIsAllowedByDefault() throws Exception { + AxisConfiguration config = new AxisConfiguration(); + config.addParameter( + new Parameter(AddressingResponseEndpointPolicy.ALLOW_NON_ANONYMOUS, "true")); + MessageContext mc = new ConfigurationContext(config).createMessageContext(); + mc.setServerSide(true); + + assertTrue("https must be allowed", AddressingResponseEndpointPolicy.isAllowed( + new EndpointReference("https://192.0.2.25/cb"), mc)); + assertFalse("plain http must be opt-in", AddressingResponseEndpointPolicy.isAllowed( + new EndpointReference("http://192.0.2.25/cb"), mc)); + assertFalse("the metadata endpoint is refused on scheme alone", + AddressingResponseEndpointPolicy.isAllowed( + new EndpointReference("http://169.254.169.254/latest/meta-data/"), mc)); + assertFalse("jms must be opt-in", AddressingResponseEndpointPolicy.isAllowed( + new EndpointReference("jms:/ReplyQueue"), mc)); + assertFalse("tcp must be opt-in", AddressingResponseEndpointPolicy.isAllowed( + new EndpointReference("tcp://192.0.2.25:6060/svc"), mc)); } /** A context without the opt-in, for testing the shipped default. */ @@ -65,8 +94,8 @@ public class ResponseEndpointPolicyTest extends TestCase { public void testAnonymousAddressIsAlwaysAllowed() { EndpointReference anonymous = new EndpointReference(AddressingConstants.Final.WSA_ANONYMOUS_URL); - assertTrue(ResponseEndpointPolicy.isAllowed(anonymous, messageContext)); - assertTrue(ResponseEndpointPolicy.isAllowed(null, messageContext)); + assertTrue(AddressingResponseEndpointPolicy.isAllowed(anonymous, messageContext)); + assertTrue(AddressingResponseEndpointPolicy.isAllowed(null, messageContext)); } /** @@ -76,7 +105,7 @@ public class ResponseEndpointPolicyTest extends TestCase { public void testInstanceMetadataAddressIsBlockedByDefault() { EndpointReference metadata = new EndpointReference("http://169.254.169.254/latest/meta-data/"); - assertFalse(ResponseEndpointPolicy.isAllowed(metadata, messageContext)); + assertFalse(AddressingResponseEndpointPolicy.isAllowed(metadata, messageContext)); } /** @@ -86,24 +115,24 @@ public class ResponseEndpointPolicyTest extends TestCase { * must not refuse them. */ public void testLoopbackAndPrivateAddressesAllowedWhenDecoupledEnabled() { - assertTrue(ResponseEndpointPolicy.isAllowed( + assertTrue(AddressingResponseEndpointPolicy.isAllowed( new EndpointReference("http://127.0.0.1:8080/sink"), messageContext)); - assertTrue(ResponseEndpointPolicy.isAllowed( + assertTrue(AddressingResponseEndpointPolicy.isAllowed( new EndpointReference("http://10.1.2.3/internal"), messageContext)); - assertTrue(ResponseEndpointPolicy.isAllowed( + assertTrue(AddressingResponseEndpointPolicy.isAllowed( new EndpointReference("http://192.168.1.10/admin"), messageContext)); } /** Opting in to the strict posture refuses them. */ public void testLoopbackAndPrivateAddressesBlockedWhenOptedIn() throws Exception { - setParameter(ResponseEndpointPolicy.BLOCK_PRIVATE_NETWORKS, "true"); - assertFalse(ResponseEndpointPolicy.isAllowed( + setParameter(AddressingResponseEndpointPolicy.BLOCK_PRIVATE_NETWORKS, "true"); + assertFalse(AddressingResponseEndpointPolicy.isAllowed( new EndpointReference("http://127.0.0.1:8080/sink"), messageContext)); - assertFalse(ResponseEndpointPolicy.isAllowed( + assertFalse(AddressingResponseEndpointPolicy.isAllowed( new EndpointReference("http://10.1.2.3/internal"), messageContext)); - assertFalse(ResponseEndpointPolicy.isAllowed( + assertFalse(AddressingResponseEndpointPolicy.isAllowed( new EndpointReference("http://192.168.1.10/admin"), messageContext)); - assertFalse(ResponseEndpointPolicy.isAllowed( + assertFalse(AddressingResponseEndpointPolicy.isAllowed( new EndpointReference("http://172.16.5.5/admin"), messageContext)); } @@ -112,9 +141,9 @@ public class ResponseEndpointPolicyTest extends TestCase { * are refused without opting in. */ public void testWildcardAndMulticastAlwaysBlocked() { - assertFalse(ResponseEndpointPolicy.isAllowed( + assertFalse(AddressingResponseEndpointPolicy.isAllowed( new EndpointReference("http://0.0.0.0/sink"), messageContext)); - assertFalse(ResponseEndpointPolicy.isAllowed( + assertFalse(AddressingResponseEndpointPolicy.isAllowed( new EndpointReference("http://239.1.2.3/sink"), messageContext)); } @@ -123,13 +152,13 @@ public class ResponseEndpointPolicyTest extends TestCase { * host check. */ public void testPivotSchemesAreRejected() { - assertFalse(ResponseEndpointPolicy.isAllowed( + assertFalse(AddressingResponseEndpointPolicy.isAllowed( new EndpointReference("file:///etc/passwd"), messageContext)); - assertFalse(ResponseEndpointPolicy.isAllowed( + assertFalse(AddressingResponseEndpointPolicy.isAllowed( new EndpointReference("gopher://example.com/1"), messageContext)); - assertFalse(ResponseEndpointPolicy.isAllowed( + assertFalse(AddressingResponseEndpointPolicy.isAllowed( new EndpointReference("jar:http://example.com/a.jar!/"), messageContext)); - assertFalse(ResponseEndpointPolicy.isAllowed( + assertFalse(AddressingResponseEndpointPolicy.isAllowed( new EndpointReference("ftp://example.com/drop"), messageContext)); } @@ -139,13 +168,15 @@ public class ResponseEndpointPolicyTest extends TestCase { * reference's scheme against whatever is registered — so the policy must not * restrict replies to HTTP. */ - public void testNonHttpTransportSchemesAreAllowed() { - assertTrue(ResponseEndpointPolicy.isAllowed( + public void testNonHttpTransportSchemesAreAllowedWhenNamed() throws Exception { + setParameter(AddressingResponseEndpointPolicy.ALLOWED_SCHEMES_PARAMETER, + "http,https,jms,mailto,tcp"); + assertTrue(AddressingResponseEndpointPolicy.isAllowed( new EndpointReference("jms:/ReplyQueue?transport.jms.ConnectionFactory=qcf"), messageContext)); - assertTrue(ResponseEndpointPolicy.isAllowed( + assertTrue(AddressingResponseEndpointPolicy.isAllowed( new EndpointReference("mailto:[email protected]"), messageContext)); - assertTrue(ResponseEndpointPolicy.isAllowed( + assertTrue(AddressingResponseEndpointPolicy.isAllowed( new EndpointReference("tcp://192.0.2.25:6060/svc"), messageContext)); } @@ -153,17 +184,18 @@ public class ResponseEndpointPolicyTest extends TestCase { * 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( + public void testNonHttpSchemeStillGetsTheRangeCheck() throws Exception { + setParameter(AddressingResponseEndpointPolicy.ALLOWED_SCHEMES_PARAMETER, "http,https,tcp"); + assertFalse(AddressingResponseEndpointPolicy.isAllowed( new EndpointReference("tcp://169.254.169.254:6060/svc"), messageContext)); } public void testSchemeListIsConfigurable() throws Exception { - setParameter(ResponseEndpointPolicy.ALLOWED_SCHEMES_PARAMETER, "http,https"); + setParameter(AddressingResponseEndpointPolicy.ALLOWED_SCHEMES_PARAMETER, "http,https"); assertFalse("A scheme outside the configured list should be refused", - ResponseEndpointPolicy.isAllowed( + AddressingResponseEndpointPolicy.isAllowed( new EndpointReference("mailto:[email protected]"), messageContext)); - assertTrue(ResponseEndpointPolicy.isAllowed( + assertTrue(AddressingResponseEndpointPolicy.isAllowed( new EndpointReference("http://192.0.2.25/replies"), messageContext)); } @@ -173,7 +205,7 @@ public class ResponseEndpointPolicyTest extends TestCase { */ public void testPublicAddressAllowedWhenDecoupledEnabled() { EndpointReference publicEpr = new EndpointReference("http://192.0.2.25/replies"); - assertTrue(ResponseEndpointPolicy.isAllowed(publicEpr, messageContext)); + assertTrue(AddressingResponseEndpointPolicy.isAllowed(publicEpr, messageContext)); } /** @@ -184,16 +216,16 @@ public class ResponseEndpointPolicyTest extends TestCase { public void testDecoupledResponsesAreOffByDefault() throws Exception { MessageContext defaults = defaultConfigured(); assertFalse("A public reply endpoint must be refused by default", - ResponseEndpointPolicy.isAllowed( + AddressingResponseEndpointPolicy.isAllowed( new EndpointReference("http://192.0.2.25/replies"), defaults)); assertFalse("A private reply endpoint must be refused by default", - ResponseEndpointPolicy.isAllowed( + AddressingResponseEndpointPolicy.isAllowed( new EndpointReference("http://10.1.2.3/internal"), defaults)); // Anonymous must still work by default, or ordinary in-out messaging breaks. - assertTrue(ResponseEndpointPolicy.isAllowed( + assertTrue(AddressingResponseEndpointPolicy.isAllowed( new EndpointReference(AddressingConstants.Final.WSA_ANONYMOUS_URL), defaults)); - assertTrue(ResponseEndpointPolicy.isAllowed(null, defaults)); + assertTrue(AddressingResponseEndpointPolicy.isAllowed(null, defaults)); } /** @@ -201,10 +233,10 @@ public class ResponseEndpointPolicyTest extends TestCase { * that really does reply into its own network can permit exactly that host. */ public void testAllowListPermitsAnOtherwiseBlockedHost() throws Exception { - setParameter(ResponseEndpointPolicy.ALLOWED_HOSTS, "replies.example.com, 127.0.0.1"); - assertTrue(ResponseEndpointPolicy.isAllowed( + setParameter(AddressingResponseEndpointPolicy.ALLOWED_HOSTS, "replies.example.com, 127.0.0.1"); + assertTrue(AddressingResponseEndpointPolicy.isAllowed( new EndpointReference("http://127.0.0.1:8080/sink"), messageContext)); - assertFalse(ResponseEndpointPolicy.isAllowed( + assertFalse(AddressingResponseEndpointPolicy.isAllowed( new EndpointReference("http://192.0.2.25/replies"), messageContext)); } @@ -213,10 +245,10 @@ public class ResponseEndpointPolicyTest extends TestCase { * explicitly off: link-local is refused unconditionally. */ public void testMetadataAddressStillBlockedWithPrivateRangeCheckOff() throws Exception { - setParameter(ResponseEndpointPolicy.BLOCK_PRIVATE_NETWORKS, "false"); - assertFalse(ResponseEndpointPolicy.isAllowed( + setParameter(AddressingResponseEndpointPolicy.BLOCK_PRIVATE_NETWORKS, "false"); + assertFalse(AddressingResponseEndpointPolicy.isAllowed( new EndpointReference("http://169.254.169.254/latest/meta-data/"), messageContext)); - assertTrue(ResponseEndpointPolicy.isAllowed( + assertTrue(AddressingResponseEndpointPolicy.isAllowed( new EndpointReference("http://127.0.0.1:8080/sink"), messageContext)); } @@ -226,7 +258,7 @@ public class ResponseEndpointPolicyTest extends TestCase { * .invalid TLD is reserved by RFC 2606 precisely so it never resolves. */ public void testUnresolvableHostIsRejected() { - assertFalse(ResponseEndpointPolicy.isAllowed( + assertFalse(AddressingResponseEndpointPolicy.isAllowed( new EndpointReference("http://no-such-host.invalid/replies"), messageContext)); } @@ -235,18 +267,18 @@ public class ResponseEndpointPolicyTest extends TestCase { * on the request path for the direct-IP cases. */ public void testLiteralAddressNeedsNoResolution() throws Exception { - setParameter(ResponseEndpointPolicy.RESOLVE_TIMEOUT, "1"); + setParameter(AddressingResponseEndpointPolicy.RESOLVE_TIMEOUT, "1"); // Would time out if this went to the resolver; it must not. - assertFalse(ResponseEndpointPolicy.isAllowed( + assertFalse(AddressingResponseEndpointPolicy.isAllowed( new EndpointReference("http://169.254.169.254/latest/meta-data/"), messageContext)); - assertTrue(ResponseEndpointPolicy.isAllowed( + assertTrue(AddressingResponseEndpointPolicy.isAllowed( new EndpointReference("http://192.0.2.25/replies"), messageContext)); } public void testMalformedAddressIsRejected() { - assertFalse(ResponseEndpointPolicy.isAllowed( + assertFalse(AddressingResponseEndpointPolicy.isAllowed( new EndpointReference("http://[not a uri"), messageContext)); - assertFalse(ResponseEndpointPolicy.isAllowed( + assertFalse(AddressingResponseEndpointPolicy.isAllowed( new EndpointReference("http:///no-host"), messageContext)); } } diff --git a/modules/webapp/conf/axis2.xml b/modules/webapp/conf/axis2.xml index 2e8aecbe9b..922f1cfacf 100644 --- a/modules/webapp/conf/axis2.xml +++ b/modules/webapp/conf/axis2.xml @@ -92,15 +92,19 @@ used by the checks above, so a slow resolver cannot hold a request thread. Literal addresses are never looked up. - 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. + allowedResponseEndpointSchemes defaults to https ALONE. Plain http is + excluded on purpose: the cloud instance-metadata services this class of SSRF + targets (169.254.169.254 on AWS and Azure, metadata.google.internal on GCP) + are HTTP-only, so refusing the scheme removes that target outright. jms, + mailto and tcp have senders in the tree but are vanishingly rare as reply + destinations, so they are opt-in too. Name whatever a deployment genuinely + replies over, e.g. http,https for plain-HTTP callbacks inside a trusted + network. --> <parameter name="allowNonAnonymousResponseEndpoints">false</parameter> <parameter name="blockPrivateNetworkResponseEndpoints">false</parameter> <!--<parameter name="allowedResponseEndpointHosts">replies.example.com</parameter>--> - <!--<parameter name="allowedResponseEndpointSchemes">http,https</parameter>--> + <!--<parameter name="allowedResponseEndpointSchemes">https</parameter>--> <!--Uncomment if you want to plugin your own attachments lifecycle implementation --> <!--<attachmentsLifecycleManager class="org.apache.axiom.attachments.lifecycle.impl.LifecycleManagerImpl"/>-->
