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]
