(tomcat) branch 11.0.x updated: Add some additional validation in RemoteIp[Filter|Valve]

[email protected]
Newsgroups gmane.comp.jakarta.tomcat.devel
Message-ID <[email protected]>
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">
lmpx.com only provides a reader for public news (NNTP) servers. It is not affiliated with the servers or forums shown here and is not responsible for the content of articles, which is written by their respective authors.