This is an automated email from the ASF dual-hosted git repository.
markt-asf pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/tomcat.git
The following commit(s) were added to refs/heads/main by this push:
new 4ea7d6d509 Add some additional validation in RemoteIp[Filter|Valve]
4ea7d6d509 is described below
commit 4ea7d6d509f480c8c383116c066f53ca13c20d0a
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 43e4bf0b6f..ca8383dcbf 100644
--- a/java/org/apache/catalina/filters/LocalStrings.properties
+++ b/java/org/apache/catalina/filters/LocalStrings.properties
@@ -75,6 +75,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 aab4d8b918..492911075a 100644
--- a/java/org/apache/catalina/filters/RemoteIpFilter.java
+++ b/java/org/apache/catalina/filters/RemoteIpFilter.java
@@ -940,7 +940,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 63eaed8748..94eda8b675 100644
--- a/java/org/apache/catalina/util/LocalStrings.properties
+++ b/java/org/apache/catalina/util/LocalStrings.properties
@@ -43,6 +43,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 0cb4d4545b..29837d10eb 100644
--- a/java/org/apache/catalina/valves/LocalStrings.properties
+++ b/java/org/apache/catalina/valves/LocalStrings.properties
@@ -165,6 +165,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 b936f8c158..f472a1a7b7 100644
--- a/java/org/apache/catalina/valves/RemoteIpValve.java
+++ b/java/org/apache/catalina/valves/RemoteIpValve.java
@@ -26,12 +26,14 @@ import java.util.Enumeration;
import java.util.List;
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;
@@ -583,6 +585,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();
@@ -657,20 +673,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 af3d64da0f..9da313da4b 100644
--- a/webapps/docs/changelog.xml
+++ b/webapps/docs/changelog.xml
@@ -262,6 +262,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]