This is an automated email from the ASF dual-hosted git repository.
dengliming pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/shenyu.git
The following commit(s) were added to refs/heads/master by this push:
new fef9dd0c7c fix: validate forwarded remote addresses before resolution
(#7065)
fef9dd0c7c is described below
commit fef9dd0c7c8279be3e5bfcc0d2cd62da777f1dee
Author: Southern <[email protected]>
AuthorDate: Fri Sep 18 14:03:03 2026 +0800
fix: validate forwarded remote addresses before resolution (#7065)
- Preserve valid single-value X-Forwarded-For headers and discard empty
values.
- Parse only literal IPv4 and IPv6 addresses to prevent blocking DNS
lookups.
- Fall back to the TCP remote address when the forwarded value is invalid.
- Add regression tests for valid, empty, invalid, IPv6, and multi-value
headers.
Co-authored-by: zhengpeng <[email protected]>
Co-authored-by: Liming Deng <[email protected]>
---
.../forward/ForwardedRemoteAddressResolver.java | 9 ++++++--
.../ForwardedRemoteAddressResolverTest.java | 27 ++++++++++++++++++----
2 files changed, 29 insertions(+), 7 deletions(-)
diff --git
a/shenyu-web/src/main/java/org/apache/shenyu/web/forward/ForwardedRemoteAddressResolver.java
b/shenyu-web/src/main/java/org/apache/shenyu/web/forward/ForwardedRemoteAddressResolver.java
index 0c84a7f064..72690e6bfd 100644
---
a/shenyu-web/src/main/java/org/apache/shenyu/web/forward/ForwardedRemoteAddressResolver.java
+++
b/shenyu-web/src/main/java/org/apache/shenyu/web/forward/ForwardedRemoteAddressResolver.java
@@ -17,6 +17,7 @@
package org.apache.shenyu.web.forward;
+import com.google.common.net.InetAddresses;
import org.apache.commons.collections4.CollectionUtils;
import org.apache.commons.lang3.StringUtils;
import org.apache.shenyu.plugin.api.RemoteAddressResolver;
@@ -87,7 +88,11 @@ public class ForwardedRemoteAddressResolver implements
RemoteAddressResolver {
List<String> xForwardedValues = extractXForwardedValues(exchange);
if (CollectionUtils.isNotEmpty(xForwardedValues)) {
int index = Math.min(xForwardedValues.size(), maxTrustedIndex) - 1;
- return new InetSocketAddress(xForwardedValues.get(index), 0);
+ try {
+ return new
InetSocketAddress(InetAddresses.forString(xForwardedValues.get(index)), 0);
+ } catch (IllegalArgumentException ex) {
+ LOG.warn("Invalid IP address in X-Forwarded-For header,
falling back to remote address");
+ }
}
return defaultRemoteIpResolver.resolve(exchange);
}
@@ -103,7 +108,7 @@ public class ForwardedRemoteAddressResolver implements
RemoteAddressResolver {
return Collections.emptyList();
}
List<String> values = Arrays.asList(xForwardedValues.get(0).split(",
"));
- if (values.size() == 1 && StringUtils.isNotEmpty(values.get(0))) {
+ if (values.size() == 1 && StringUtils.isEmpty(values.get(0))) {
return Collections.emptyList();
}
return values;
diff --git
a/shenyu-web/src/test/java/org/apache/shenyu/web/forward/ForwardedRemoteAddressResolverTest.java
b/shenyu-web/src/test/java/org/apache/shenyu/web/forward/ForwardedRemoteAddressResolverTest.java
index d790d67128..5ca21932ab 100644
---
a/shenyu-web/src/test/java/org/apache/shenyu/web/forward/ForwardedRemoteAddressResolverTest.java
+++
b/shenyu-web/src/test/java/org/apache/shenyu/web/forward/ForwardedRemoteAddressResolverTest.java
@@ -27,6 +27,7 @@ import
org.springframework.mock.web.server.MockServerWebExchange;
import org.springframework.test.util.ReflectionTestUtils;
import org.springframework.web.server.ServerWebExchange;
+import java.net.InetSocketAddress;
import java.util.Collections;
import static
org.apache.shenyu.web.forward.ForwardedRemoteAddressResolver.X_FORWARDED_FOR;
@@ -61,26 +62,42 @@ public final class ForwardedRemoteAddressResolverTest {
@Test
public void testResolver() {
ForwardedRemoteAddressResolver instance =
ForwardedRemoteAddressResolver.maxTrustedIndex(1);
+ InetSocketAddress remoteAddress = new InetSocketAddress("192.0.2.10",
8080);
final ServerWebExchange exchange =
MockServerWebExchange.from(MockServerHttpRequest.post("localhost")
+ .remoteAddress(remoteAddress)
.build());
final ServerWebExchange emptyForwardExchange =
MockServerWebExchange.from(MockServerHttpRequest.post("localhost")
.header("X-Forwarded-For", "")
+ .remoteAddress(remoteAddress)
.build());
final ServerWebExchange forwardExchange =
MockServerWebExchange.from(MockServerHttpRequest.post("localhost")
.header("X-Forwarded-For", "127.0.0.1")
+ .remoteAddress(remoteAddress)
.build());
final ServerWebExchange multiForwardExchangeError =
MockServerWebExchange.from(MockServerHttpRequest.post("localhost")
.header("X-Forwarded-For", "127.0.0.1", "127.0.0.2")
+ .remoteAddress(remoteAddress)
.build());
final ServerWebExchange multiForwardExchange =
MockServerWebExchange.from(MockServerHttpRequest.post("localhost")
.header("X-Forwarded-For", "127.0.0.1, 127.0.0.2")
+ .remoteAddress(remoteAddress)
+ .build());
+ final ServerWebExchange ipv6ForwardExchange =
MockServerWebExchange.from(MockServerHttpRequest.post("localhost")
+ .header("X-Forwarded-For", "2001:db8::1")
+ .remoteAddress(remoteAddress)
+ .build());
+ final ServerWebExchange hostnameForwardExchange =
MockServerWebExchange.from(MockServerHttpRequest.post("localhost")
+ .header("X-Forwarded-For", "attacker.example")
+ .remoteAddress(remoteAddress)
.build());
- instance.resolve(exchange);
- instance.resolve(emptyForwardExchange);
- instance.resolve(forwardExchange);
- instance.resolve(multiForwardExchangeError);
- instance.resolve(multiForwardExchange);
+ assertEquals(remoteAddress, instance.resolve(exchange));
+ assertEquals(remoteAddress, instance.resolve(emptyForwardExchange));
+ assertEquals("127.0.0.1",
instance.resolve(forwardExchange).getAddress().getHostAddress());
+ assertEquals(remoteAddress,
instance.resolve(multiForwardExchangeError));
+ assertEquals("127.0.0.1",
instance.resolve(multiForwardExchange).getAddress().getHostAddress());
+ assertEquals("2001:db8:0:0:0:0:0:1",
instance.resolve(ipv6ForwardExchange).getAddress().getHostAddress());
+ assertEquals(remoteAddress, instance.resolve(hostnameForwardExchange));
ServerWebExchange headerEmptyExchange = mock(ServerWebExchange.class);
ServerHttpRequest headerEmptyServerHttpRequest =
mock(ServerHttpRequest.class);