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/09/04 18:15:05 UTC
(axis-axis2-java-core) 05/11: Refuse a caller-supplied JNDI environment on a JMS reply
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 dc51d36ed2867d1c527f12f72dea814cd9a311b6
Author: Robert Lazarski <ro...@gmail.com>
AuthorDate: Thu Sep 3 02:23:15 2026 -1000
Refuse a caller-supplied JNDI environment on a JMS reply
JMSOutTransportInfo hands a jms: endpoint reference's whole query string to new
InitialContext(...), so on a decoupled response -- where that address came from
the caller -- a java.naming.* parameter chose which broker the server connected
to and what it did once connected. Refuse those parameters for that case only;
a client-side send still addresses whatever provider the application asked for.
The existing substring blocklist is left alone deliberately: it screens the
wrong thing, and widening it is not the fix.
Co-Authored-By: Claude Opus 5 (1M context) <no...@anthropic.com>
---
.../AddressingResponseEndpointPolicy.java | 8 ++
.../org/apache/axis2/transport/jms/JMSSender.java | 20 ++++
.../jms/JMSDecoupledResponseEnvironmentTest.java | 104 +++++++++++++++++++++
src/site/markdown/release-notes/2.0.2.md | 7 +-
4 files changed, 138 insertions(+), 1 deletion(-)
diff --git a/modules/kernel/src/org/apache/axis2/addressing/AddressingResponseEndpointPolicy.java b/modules/kernel/src/org/apache/axis2/addressing/AddressingResponseEndpointPolicy.java
index 654dc16ff1..4e4cbcc411 100644
--- a/modules/kernel/src/org/apache/axis2/addressing/AddressingResponseEndpointPolicy.java
+++ b/modules/kernel/src/org/apache/axis2/addressing/AddressingResponseEndpointPolicy.java
@@ -190,6 +190,14 @@ public final class AddressingResponseEndpointPolicy {
// 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.
+ //
+ // Returning true here screens nothing about the rest of the address,
+ // and for these schemes the query string is not inert: a jms: EPR
+ // carries the JNDI environment used to resolve the destination. That
+ // is screened where the meaning of those parameters is known, in
+ // JMSSender, which refuses a caller-supplied java.naming.* on a
+ // decoupled response. Do not read this early return as a statement
+ // that host-less addresses are harmless.
return true;
}
diff --git a/modules/transport/jms/src/main/java/org/apache/axis2/transport/jms/JMSSender.java b/modules/transport/jms/src/main/java/org/apache/axis2/transport/jms/JMSSender.java
index 19cefddad8..8dfad332a7 100644
--- a/modules/transport/jms/src/main/java/org/apache/axis2/transport/jms/JMSSender.java
+++ b/modules/transport/jms/src/main/java/org/apache/axis2/transport/jms/JMSSender.java
@@ -23,6 +23,7 @@ import org.apache.axiom.om.OMNode;
import org.apache.axis2.util.MessageProcessorSelector;
import org.apache.axis2.AxisFault;
import org.apache.axis2.Constants;
+import org.apache.axis2.addressing.AddressingResponseEndpointPolicy;
import org.apache.axis2.context.MessageContext;
import org.apache.axis2.context.ConfigurationContext;
import org.apache.axis2.description.TransportOutDescription;
@@ -48,6 +49,7 @@ import java.io.IOException;
import java.io.OutputStream;
import java.io.StringWriter;
import java.nio.charset.UnsupportedCharsetException;
+import java.util.Locale;
import java.util.Map;
/**
@@ -124,6 +126,24 @@ public class JMSSender extends AbstractTransportSender implements ManagementSupp
throw new AxisFault("targetAddress received by JMSSender is not supported by this method: " + targetAddress);
}
+ // A decoupled response goes to a destination the caller named, so this EPR's
+ // query string is attacker-supplied. JMSOutTransportInfo hands that query
+ // string wholesale to new InitialContext(...), where a java.naming.* entry
+ // chooses which JNDI provider -- which broker -- the server connects to, and
+ // what it does once connected. The destination name is fine to take from the
+ // caller; the environment used to resolve it is not. Refused rather than
+ // quietly stripped, so that a deployment genuinely replying through a foreign
+ // provider names it in its own transport configuration and a caller cannot
+ // choose it per message.
+ if (targetAddress != null && AddressingResponseEndpointPolicy.isDecoupledResponse(msgCtx)) {
+ for (String name : BaseUtils.getEPRProperties(targetAddress).keySet()) {
+ if (name.toLowerCase(Locale.ENGLISH).startsWith("java.naming.")) {
+ handleException("Refusing a JMS decoupled response whose endpoint "
+ + "reference supplies the JNDI environment parameter " + name);
+ }
+ }
+ }
+
if (targetAddress != null) {
jmsOut = new JMSOutTransportInfo(targetAddress);
diff --git a/modules/transport/jms/src/test/java/org/apache/axis2/transport/jms/JMSDecoupledResponseEnvironmentTest.java b/modules/transport/jms/src/test/java/org/apache/axis2/transport/jms/JMSDecoupledResponseEnvironmentTest.java
new file mode 100644
index 0000000000..72366cdd81
--- /dev/null
+++ b/modules/transport/jms/src/test/java/org/apache/axis2/transport/jms/JMSDecoupledResponseEnvironmentTest.java
@@ -0,0 +1,104 @@
+/*
+ * 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.transport.jms;
+
+import junit.framework.TestCase;
+
+import org.apache.axis2.AxisFault;
+import org.apache.axis2.addressing.EndpointReference;
+import org.apache.axis2.context.MessageContext;
+
+/**
+ * A JMS endpoint reference carries the JNDI environment used to resolve its
+ * destination, so where the endpoint reference came from the caller — a decoupled
+ * WS-Addressing response — a <code>java.naming.*</code> parameter would let that
+ * caller choose which broker the server connects to. These tests pin the refusal,
+ * and pin that it applies only to that case: a client-side send addresses whatever
+ * provider the application asked for.
+ *
+ * <p>No broker is needed; the refusal happens before any connection is attempted.
+ */
+public class JMSDecoupledResponseEnvironmentTest extends TestCase {
+
+ /** An attacker-chosen broker, smuggled in as a reply address. */
+ private static final String EPR_WITH_ENVIRONMENT =
+ "jms:/ReplyQueue"
+ + "?java.naming.factory.initial=org.apache.activemq.jndi.ActiveMQInitialContextFactory"
+ + "&java.naming.provider.url=tcp://attacker.example.com:61616";
+
+ private static final String REFUSAL = "supplies the JNDI environment parameter";
+
+ private MessageContext decoupledResponse() {
+ MessageContext msgCtx = new MessageContext();
+ msgCtx.setServerSide(true);
+ msgCtx.setTo(new EndpointReference("jms:/ReplyQueue"));
+ return msgCtx;
+ }
+
+ private MessageContext clientRequest() {
+ MessageContext msgCtx = new MessageContext();
+ msgCtx.setServerSide(false);
+ msgCtx.setTo(new EndpointReference("jms:/ReplyQueue"));
+ return msgCtx;
+ }
+
+ public void testDecoupledResponseRefusesCallerSuppliedJndiEnvironment() throws Exception {
+ try {
+ new JMSSender().sendMessage(decoupledResponse(), EPR_WITH_ENVIRONMENT, null);
+ fail("a caller-supplied JNDI environment must not be honoured on a reply");
+ } catch (AxisFault expected) {
+ assertTrue("refused for the right reason, was: " + expected.getMessage(),
+ expected.getMessage().contains(REFUSAL));
+ assertTrue("the offending parameter should be named, was: " + expected.getMessage(),
+ expected.getMessage().contains("java.naming.factory.initial")
+ || expected.getMessage().contains("java.naming.provider.url"));
+ }
+ }
+
+ /**
+ * The scope control. This send fails too — there is no such broker — but it must
+ * not fail as a refusal, or the restriction would be blanket rather than aimed at
+ * caller-nominated destinations.
+ */
+ public void testClientRequestIsNotRefusedForCarryingAnEnvironment() throws Exception {
+ try {
+ new JMSSender().sendMessage(clientRequest(), EPR_WITH_ENVIRONMENT, null);
+ } catch (Exception whateverItWas) {
+ // It will fail -- there is no broker and the sender is not initialised --
+ // but it must get far enough to fail for one of those reasons.
+ assertRefusalAbsent("a client-side send must not hit the decoupled-response "
+ + "refusal", whateverItWas);
+ }
+ }
+
+ /** A reply naming only a destination, with no environment, is untouched. */
+ public void testDecoupledResponseWithoutEnvironmentIsNotRefused() throws Exception {
+ try {
+ new JMSSender().sendMessage(decoupledResponse(), "jms:/ReplyQueue", null);
+ } catch (Exception whateverItWas) {
+ assertRefusalAbsent("naming a destination is allowed", whateverItWas);
+ }
+ }
+
+ private static void assertRefusalAbsent(String what, Exception thrown) {
+ String message = thrown.getMessage();
+ assertFalse(what + ", was: " + thrown.getClass().getSimpleName() + ": " + message,
+ message != null && message.contains(REFUSAL));
+ }
+}
diff --git a/src/site/markdown/release-notes/2.0.2.md b/src/site/markdown/release-notes/2.0.2.md
index 4e5a317b9c..f70bffd089 100644
--- a/src/site/markdown/release-notes/2.0.2.md
+++ b/src/site/markdown/release-notes/2.0.2.md
@@ -38,7 +38,12 @@ in `SECURITY.md`.
generated reply address is the real external URL. With the feature enabled,
`allowedResponseEndpointSchemes` permits HTTPS only, the destination is screened
at both the header-parsing and transport-selection layers, and redirects are not
- followed.
+ followed over either the HTTP or the HTTP/2 sender. Where `jms` is among the
+ permitted schemes, a reply endpoint may name a destination but no longer supply
+ the JNDI environment used to resolve it: a `java.naming.*` parameter on a reply
+ address is refused, since it would let the caller choose which broker the server
+ connects to. Deployments replying through a particular provider name it in their
+ own transport configuration.
- **Request bodies are bounded.** The `multipart/form-data` and
`application/x-www-form-urlencoded` builders read the transport stream directly,