This is an automated email from the ASF dual-hosted git repository.

markt-asf pushed a commit to branch 9.0.x
in repository https://gitbox.apache.org/repos/asf/tomcat.git

commit e992113d2cd83a85c0d1ea4628848c1baf2f5f72
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 672b2844a8..aac235dc53 100644
--- a/java/org/apache/catalina/filters/RemoteIpFilter.java
+++ b/java/org/apache/catalina/filters/RemoteIpFilter.java
@@ -1014,7 +1014,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 b418e33225..600883c0f7 100644
--- a/java/org/apache/catalina/util/LocalStrings.properties
+++ b/java/org/apache/catalina/util/LocalStrings.properties
@@ -49,6 +49,8 @@ netmask.invalidPort=The port part in the pattern [{0}] is not 
valid
 
 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 f5047715ca..ac944d4369 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 javax.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 5b8053a6a4..f50271463a 100644
--- a/java/org/apache/catalina/valves/LocalStrings.properties
+++ b/java/org/apache/catalina/valves/LocalStrings.properties
@@ -163,6 +163,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 5985bb4cbb..a67ee5cd05 100644
--- a/java/org/apache/catalina/valves/RemoteIpValve.java
+++ b/java/org/apache/catalina/valves/RemoteIpValve.java
@@ -28,11 +28,13 @@ import java.util.List;
 import java.util.regex.Pattern;
 
 import javax.servlet.ServletException;
+import javax.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.RequestUtil;
 import org.apache.juli.logging.Log;
 import org.apache.juli.logging.LogFactory;
 import org.apache.tomcat.util.buf.StringUtils;
@@ -669,6 +671,20 @@ public class RemoteIpValve extends ValveBase {
         }
 
         if (isInternal || (trustedProxies != null && 
trustedProxies.matcher(originalRemoteAddr).matches())) {
+            // 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();
@@ -743,20 +759,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 b5d9423779..cae446ad50 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]

Reply via email to