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

rmaucher pushed a commit to branch 10.1.x
in repository https://gitbox.apache.org/repos/asf/tomcat.git

commit f49637821411fc0a7e4c9aec8d7e30f32578c767
Author: opencode <[email protected]>
AuthorDate: Mon Oct 12 11:23:08 2026 +0200

    Ignore empty entries in the remote IP header
    
    Entries of the comma-separated remote IP header were used as they
    came from the splitting, which trims whitespace but does not drop
    empty elements. A header value consisting only of whitespace or
    commas, or containing empty entries, ended up assigning an empty
    string as the remote address and remote host of the request, since
    an empty value is neither an internal nor a trusted proxy yet is not
    null. Downstream code then saw a blank client address in the access
    log, in access control valves and in the application. Depending on
    the internal proxy configuration, an empty entry could also be
    resolved as the loopback address and be classified as an internal
    proxy, which is another way in which an empty value should simply
    carry no meaning.
    
    Empty entries are now skipped in the loop that walks the header from
    right to left, and in the loop that rebuilds the header value, so an
    all-empty header leaves the request untouched as an absent header
    does, and a mixed header still yields the right-most real client
    address without echoing empty entries back. The valve and the filter
    that mirrors it gained a test for a whitespace-only header value.
---
 .../apache/catalina/filters/RemoteIpFilter.java    |  9 +++++
 java/org/apache/catalina/valves/RemoteIpValve.java |  9 +++++
 .../catalina/filters/TestRemoteIpFilter.java       | 32 +++++++++++++++++
 .../apache/catalina/valves/TestRemoteIpValve.java  | 40 ++++++++++++++++++++++
 4 files changed, 90 insertions(+)

diff --git a/java/org/apache/catalina/filters/RemoteIpFilter.java 
b/java/org/apache/catalina/filters/RemoteIpFilter.java
index fbec035d46..8a6f2796e1 100644
--- a/java/org/apache/catalina/filters/RemoteIpFilter.java
+++ b/java/org/apache/catalina/filters/RemoteIpFilter.java
@@ -960,6 +960,12 @@ public class RemoteIpFilter extends GenericFilter {
             // loop on remoteIpHeaderValue to find the first trusted remote ip 
and to build the proxies chain
             for (idx = remoteIpHeaderValue.length - 1; idx >= 0; idx--) {
                 String currentRemoteIp = remoteIpHeaderValue[idx];
+                if (currentRemoteIp.isEmpty()) {
+                    // Skip empty entries, such as those resulting from a
+                    // whitespace-only header value, rather than ending up
+                    // setting an empty remote address
+                    continue;
+                }
                 remoteIp = currentRemoteIp;
                 if (internalProxies != null && 
internalProxies.matcher(currentRemoteIp).matches()) {
                     // do nothing, internalProxies IPs are not appended to the
@@ -974,6 +980,9 @@ public class RemoteIpFilter extends GenericFilter {
             LinkedList<String> newRemoteIpHeaderValue = new LinkedList<>();
             for (; idx >= 0; idx--) {
                 String currentRemoteIp = remoteIpHeaderValue[idx];
+                if (currentRemoteIp.isEmpty()) {
+                    continue;
+                }
                 newRemoteIpHeaderValue.addFirst(currentRemoteIp);
             }
 
diff --git a/java/org/apache/catalina/valves/RemoteIpValve.java 
b/java/org/apache/catalina/valves/RemoteIpValve.java
index 3018fca4a7..f385825817 100644
--- a/java/org/apache/catalina/valves/RemoteIpValve.java
+++ b/java/org/apache/catalina/valves/RemoteIpValve.java
@@ -661,6 +661,12 @@ public class RemoteIpValve extends ValveBase {
             // loop on remoteIpHeaderValue to find the first trusted remote ip 
and to build the proxies chain
             for (idx = remoteIpHeaderValue.length - 1; idx >= 0; idx--) {
                 String currentRemoteIp = remoteIpHeaderValue[idx];
+                if (currentRemoteIp.isEmpty()) {
+                    // Skip empty entries, such as those resulting from a
+                    // whitespace-only header value, rather than ending up
+                    // setting an empty remote address
+                    continue;
+                }
                 remoteIp = currentRemoteIp;
                 if (internalProxies != null && 
internalProxies.matcher(currentRemoteIp).matches()) {
                     // do nothing, internalProxies IPs are not appended to the
@@ -675,6 +681,9 @@ public class RemoteIpValve extends ValveBase {
             Deque<String> newRemoteIpHeaderValue = new ArrayDeque<>();
             for (; idx >= 0; idx--) {
                 String currentRemoteIp = remoteIpHeaderValue[idx];
+                if (currentRemoteIp.isEmpty()) {
+                    continue;
+                }
                 newRemoteIpHeaderValue.addFirst(currentRemoteIp);
             }
             if (remoteIp != null) {
diff --git a/test/org/apache/catalina/filters/TestRemoteIpFilter.java 
b/test/org/apache/catalina/filters/TestRemoteIpFilter.java
index 7b39045ed6..e74cf01ed3 100644
--- a/test/org/apache/catalina/filters/TestRemoteIpFilter.java
+++ b/test/org/apache/catalina/filters/TestRemoteIpFilter.java
@@ -284,6 +284,38 @@ public class TestRemoteIpFilter extends TomcatBaseTest {
         Assert.assertEquals("remoteHost", "remote-host-original-value", 
actualRemoteHost);
     }
 
+    /*
+     * A remote IP header value that contains nothing but whitespace (or 
commas) must leave the request untouched,
+     * just like an absent header, rather than setting an empty remote address.
+     */
+    @Test
+    public void testInvokeAllowedRemoteAddrWithWhitespaceOnlyRemoteIpHeader() 
throws Exception {
+        // PREPARE
+        FilterDef filterDef = new FilterDef();
+        filterDef.addInitParameter("internalProxies", 
"192\\.168\\.0\\.10|192\\.168\\.0\\.11");
+        filterDef.addInitParameter("trustedProxies", 
"200.0.0.1,200.0.0.2,200.0.0.3");
+        filterDef.addInitParameter("remoteIpHeader", "x-forwarded-for");
+        filterDef.addInitParameter("proxiesHeader", "x-forwarded-by");
+
+        MockHttpServletRequest request = new MockHttpServletRequest();
+        request.setRemoteAddr("192.168.0.10");
+        request.setRemoteHost("remote-host-original-value");
+        request.addHeader("x-forwarded-for", " ");
+
+        // TEST
+        HttpServletRequest actualRequest = testRemoteIpFilter(filterDef, 
request).getRequest();
+
+        // VERIFY
+        String actualXForwardedBy = actualRequest.getHeader("x-forwarded-by");
+        Assert.assertNull("x-forwarded-by must not be added", 
actualXForwardedBy);
+
+        String actualRemoteAddr = actualRequest.getRemoteAddr();
+        Assert.assertEquals("remoteAddr", "192.168.0.10", actualRemoteAddr);
+
+        String actualRemoteHost = actualRequest.getRemoteHost();
+        Assert.assertEquals("remoteHost", "remote-host-original-value", 
actualRemoteHost);
+    }
+
     @Test
     public void testInvokeAllProxiesAreInternal() throws Exception {
 
diff --git a/test/org/apache/catalina/valves/TestRemoteIpValve.java 
b/test/org/apache/catalina/valves/TestRemoteIpValve.java
index c70b7f2ea6..30338efae4 100644
--- a/test/org/apache/catalina/valves/TestRemoteIpValve.java
+++ b/test/org/apache/catalina/valves/TestRemoteIpValve.java
@@ -151,6 +151,46 @@ public class TestRemoteIpValve {
 
     }
 
+    /*
+     * A remote IP header value that contains nothing but whitespace (or 
commas) must leave the request untouched,
+     * just like an absent header, rather than setting an empty remote address.
+     */
+    @Test
+    public void testInvokeAllowedRemoteAddrWithWhitespaceOnlyRemoteIpHeader() 
throws Exception {
+        // PREPARE
+        RemoteIpValve remoteIpValve = new RemoteIpValve();
+        remoteIpValve.setInternalProxies("192.168.0.10/31");
+        remoteIpValve.setTrustedProxies("200.0.0.1, 200.0.0.2, 200.0.0.3");
+        remoteIpValve.setRemoteIpHeader("x-forwarded-for");
+        remoteIpValve.setProxiesHeader("x-forwarded-by");
+        RemoteAddrAndHostTrackerValve remoteAddrAndHostTrackerValve = new 
RemoteAddrAndHostTrackerValve();
+        remoteIpValve.setNext(remoteAddrAndHostTrackerValve);
+
+        Request request = new MockRequest(new org.apache.coyote.Request());
+        request.setRemoteAddr("192.168.0.10");
+        request.setRemoteHost("remote-host-original-value");
+        
request.getCoyoteRequest().getMimeHeaders().addValue("x-forwarded-for").setString("
 ");
+
+        // TEST
+        remoteIpValve.invoke(request, null);
+
+        // VERIFY
+        String actualXForwardedBy = 
remoteAddrAndHostTrackerValve.getForwardedBy();
+        Assert.assertNull("x-forwarded-by must not be added", 
actualXForwardedBy);
+
+        String actualRemoteAddr = 
remoteAddrAndHostTrackerValve.getRemoteAddr();
+        Assert.assertEquals("remoteAddr", "192.168.0.10", actualRemoteAddr);
+
+        String actualRemoteHost = 
remoteAddrAndHostTrackerValve.getRemoteHost();
+        Assert.assertEquals("remoteHost", "remote-host-original-value", 
actualRemoteHost);
+
+        String actualPostInvokeRemoteAddr = request.getRemoteAddr();
+        Assert.assertEquals("postInvoke remoteAddr", "192.168.0.10", 
actualPostInvokeRemoteAddr);
+
+        String actualPostInvokeRemoteHost = request.getRemoteHost();
+        Assert.assertEquals("postInvoke remoteAddr", 
"remote-host-original-value", actualPostInvokeRemoteHost);
+    }
+
     @Test
     public void testInvokeAllProxiesAreTrusted() throws Exception {
 


---------------------------------------------------------------------
To unsubscribe, e-mail: [email protected]
For additional commands, e-mail: [email protected]

Reply via email to