This is an automated email from the ASF dual-hosted git repository.
markt-asf pushed a commit to branch 11.0.x
in repository https://gitbox.apache.org/repos/asf/tomcat.git
The following commit(s) were added to refs/heads/11.0.x by this push:
new 69d2f8dff9 Add some additional validation in RemoteIp[Filter|Valve]
69d2f8dff9 is described below
commit 69d2f8dff9382e3ec76a7451d027ff72eed02471
Author: Mark Thomas <[email protected]>
AuthorDate: Wed Aug 19 17:45:29 2026 +0100
Add some additional validation in RemoteIp[Filter|Valve]
---
.../catalina/filters/LocalStrings.properties | 1 +
.../apache/catalina/filters/RemoteIpFilter.java | 12 ++++++-
.../apache/catalina/util/LocalStrings.properties | 2 ++
java/org/apache/catalina/util/RequestUtil.java | 29 +++++++++++++++
.../apache/catalina/valves/LocalStrings.properties | 1 +
java/org/apache/catalina/valves/RemoteIpValve.java | 41 ++++++++++++++--------
webapps/docs/changelog.xml | 4 +++
7 files changed, 75 insertions(+), 15 deletions(-)
diff --git a/java/org/apache/catalina/filters/LocalStrings.properties
b/java/org/apache/catalina/filters/LocalStrings.properties
index 18b468dd74..26ef3f72ea 100644
--- a/java/org/apache/catalina/filters/LocalStrings.properties
+++ b/java/org/apache/catalina/filters/LocalStrings.properties
@@ -78,6 +78,7 @@ remoteIpFilter.invalidHostWithPort=Host value [{0}] in HTTP
header [{1}] include
remoteIpFilter.invalidNumber=Illegal number for parameter [{0}]: [{1}]
remoteIpFilter.invalidPort=Port [{0}] in HTTP header [{1}] included a port
number which will be ignored
remoteIpFilter.invalidRemoteAddress=Unable to determine the remote host
because the reported remote address [{0}] is not valid
+remoteIpFilter.multipleHeaders=Multiple [{0}] headers found in HTTP headers
when only one is expected
requestFilter.deny=Denied request for [{0}] based on property [{1}]
diff --git a/java/org/apache/catalina/filters/RemoteIpFilter.java
b/java/org/apache/catalina/filters/RemoteIpFilter.java
index 48be3b09c4..6ac1d1c087 100644
--- a/java/org/apache/catalina/filters/RemoteIpFilter.java
+++ b/java/org/apache/catalina/filters/RemoteIpFilter.java
@@ -943,7 +943,17 @@ public class RemoteIpFilter extends GenericFilter {
}
if (protocolHeader != null) {
- String protocolHeaderValue = request.getHeader(protocolHeader);
+ String protocolHeaderValue;
+ try {
+ protocolHeaderValue = RequestUtil.getUniqueHeader(request,
protocolHeader);
+ } catch (IllegalArgumentException iae) {
+ if (log.isDebugEnabled()) {
+
log.debug(sm.getString("remoteIpFilter.multipleHeaders", protocolHeader));
+ }
+ response.sendError(HttpServletResponse.SC_BAD_REQUEST);
+ return;
+ }
+
if (protocolHeaderValue == null) {
// Don't modify the secure, scheme and serverPort
attributes
// of the request
diff --git a/java/org/apache/catalina/util/LocalStrings.properties
b/java/org/apache/catalina/util/LocalStrings.properties
index 971b6ca4aa..6f8780eb5a 100644
--- a/java/org/apache/catalina/util/LocalStrings.properties
+++ b/java/org/apache/catalina/util/LocalStrings.properties
@@ -46,6 +46,8 @@ netmaskSet.invalidNetMask=One or more netmasks provided are
invalid: {0}
parameterMap.locked=No modifications are allowed to a locked ParameterMap
+requestUtil.multipleHeaders=Multiple [{0}] headers found in HTTP headers when
only one is expected
+
resourceSet.locked=No modifications are allowed to a locked ResourceSet
sessionIdGeneratorBase.createRandom=Creation of SecureRandom instance for
session ID generation using [{0}] took [{1}] milliseconds.
diff --git a/java/org/apache/catalina/util/RequestUtil.java
b/java/org/apache/catalina/util/RequestUtil.java
index f64f7e397c..2be4f68844 100644
--- a/java/org/apache/catalina/util/RequestUtil.java
+++ b/java/org/apache/catalina/util/RequestUtil.java
@@ -17,15 +17,20 @@
package org.apache.catalina.util;
import java.net.URL;
+import java.util.Enumeration;
import jakarta.servlet.http.HttpServletRequest;
import org.apache.catalina.connector.Request;
+import org.apache.tomcat.util.res.StringManager;
/**
* General purpose request parsing and encoding utility methods.
*/
public final class RequestUtil {
+
+ private static final StringManager sm =
StringManager.getManager(RequestUtil.class);
+
/**
* Default constructor.
*/
@@ -156,4 +161,28 @@ public final class RequestUtil {
return true;
}
+
+
+ /**
+ * Obtains an HTTP value, ensuring that there is no more than one instance
of the header.
+ *
+ * @param request The request from which to obtain the HTTP headers
+ * @param headerName The name of the required HTTP header
+ *
+ * @return The value for the HTTP header of there is exactly one instance
of the header in the request. {@code null}
+ * if there are zero instances of the header
+ *
+ * @throws IllegalArgumentException if there is more than one instance of
the header in the request
+ */
+ public static String getUniqueHeader(HttpServletRequest request, String
headerName) {
+ Enumeration<String> headerValues = request.getHeaders(headerName);
+ String value = null;
+ if (headerValues.hasMoreElements()) {
+ value = headerValues.nextElement();
+ if (headerValues.hasMoreElements()) {
+ throw new
IllegalArgumentException(sm.getString("requestUtil.multipleHeaders",
headerName));
+ }
+ }
+ return value;
+ }
}
diff --git a/java/org/apache/catalina/valves/LocalStrings.properties
b/java/org/apache/catalina/valves/LocalStrings.properties
index 7ccf267b1a..c3f2870b49 100644
--- a/java/org/apache/catalina/valves/LocalStrings.properties
+++ b/java/org/apache/catalina/valves/LocalStrings.properties
@@ -169,6 +169,7 @@ remoteIpValve.invalidHostHeader=Invalid value [{0}] found
for Host in HTTP heade
remoteIpValve.invalidHostWithPort=Host value [{0}] in HTTP header [{1}]
included a port number which will be ignored
remoteIpValve.invalidPortHeader=Invalid value [{0}] found for port in HTTP
header [{1}]
remoteIpValve.invalidRemoteAddress=Unable to determine the remote host because
the reported remote address [{0}] is not valid
+remoteIpValve.multipleHeaders=Multiple [{0}] headers found in HTTP headers
when only one is expected
requestFilterValve.configInvalid=One or more invalid configuration settings
were provided for the Remote[Addr|Host]Valve which prevented the Valve and its
parent containers from starting
requestFilterValve.deny=Denied request for [{0}] based on property [{1}]
diff --git a/java/org/apache/catalina/valves/RemoteIpValve.java
b/java/org/apache/catalina/valves/RemoteIpValve.java
index ebf06838c3..a73adf7bc5 100644
--- a/java/org/apache/catalina/valves/RemoteIpValve.java
+++ b/java/org/apache/catalina/valves/RemoteIpValve.java
@@ -27,12 +27,14 @@ import java.util.List;
import java.util.regex.Pattern;
import jakarta.servlet.ServletException;
+import jakarta.servlet.http.HttpServletResponse;
import org.apache.catalina.AccessLog;
import org.apache.catalina.Globals;
import org.apache.catalina.connector.Request;
import org.apache.catalina.connector.Response;
import org.apache.catalina.util.NetMaskSet;
+import org.apache.catalina.util.RequestUtil;
import org.apache.juli.logging.Log;
import org.apache.juli.logging.LogFactory;
import org.apache.tomcat.util.buf.StringUtils;
@@ -592,6 +594,20 @@ public class RemoteIpValve extends ValveBase {
boolean isInternal = isInternalProxy(originalRemoteAddr);
if (isInternal || isTrustedProxy(originalRemoteAddr)) {
+ // Validate before request modifications
+ String protocolHeaderValue = null;
+ if (protocolHeader != null) {
+ try {
+ protocolHeaderValue = RequestUtil.getUniqueHeader(request,
protocolHeader);
+ } catch (IllegalArgumentException iae) {
+ if (log.isDebugEnabled()) {
+
log.debug(sm.getString("remoteIpValve.multipleHeaders", protocolHeader), iae);
+ }
+ response.sendError(HttpServletResponse.SC_BAD_REQUEST);
+ return;
+ }
+ }
+
String remoteIp = null;
Deque<String> proxiesHeaderValue = new ArrayDeque<>();
StringBuilder concatRemoteIpHeaderValue = new StringBuilder();
@@ -666,20 +682,17 @@ public class RemoteIpValve extends ValveBase {
}
}
- if (protocolHeader != null) {
- String protocolHeaderValue = request.getHeader(protocolHeader);
- if (protocolHeaderValue == null) {
- // Don't modify the secure, scheme and serverPort
attributes
- // of the request
- } else if
(isForwardedProtoHeaderValueSecure(protocolHeaderValue)) {
- request.setSecure(true);
- request.getCoyoteRequest().scheme().setString("https");
- setPorts(request, httpsServerPort);
- } else {
- request.setSecure(false);
- request.getCoyoteRequest().scheme().setString("http");
- setPorts(request, httpServerPort);
- }
+ if (protocolHeaderValue == null) {
+ // Don't modify the secure, scheme and serverPort attributes
+ // of the request
+ } else if (isForwardedProtoHeaderValueSecure(protocolHeaderValue))
{
+ request.setSecure(true);
+ request.getCoyoteRequest().scheme().setString("https");
+ setPorts(request, httpsServerPort);
+ } else {
+ request.setSecure(false);
+ request.getCoyoteRequest().scheme().setString("http");
+ setPorts(request, httpServerPort);
}
if (hostHeader != null) {
diff --git a/webapps/docs/changelog.xml b/webapps/docs/changelog.xml
index 8634968bf5..71ac634e5b 100644
--- a/webapps/docs/changelog.xml
+++ b/webapps/docs/changelog.xml
@@ -135,6 +135,10 @@
Improve robustness of DIGEST authentication to system clock jumps.
(markt)
</fix>
+ <add>
+ Reject requests containing multiple protocol header values in the
+ <code>RemoteIpFilter</code> and <code>RemoteIpValve</code>. (markt)
+ </add>
</changelog>
</subsection>
<subsection name="Coyote">
---------------------------------------------------------------------
To unsubscribe, e-mail: [email protected]
For additional commands, e-mail: [email protected]