Description
ForwardedRemoteAddressResolver (the production default, wired as new ForwardedRemoteAddressResolver(1) in ShenyuConfiguration) has an inverted guard in extractXForwardedValues: after splitting X-Forwarded-For by ", ", it does
if (values.size() == 1 && StringUtils.isNotEmpty(values.get(0))) {
return Collections.emptyList();
}
isNotEmpty is the opposite of the intended check. The intent is to discard a single empty value (blank header), but the code discards a single non-empty value — i.e. a valid single client IP. resolve() then falls through to the TCP peer address (the proxy's IP), discarding the real client IP. Conversely, an empty-value header is not discarded and produces new InetSocketAddress("", 0) → wildcard 0.0.0.0.
Additionally resolve() constructs new InetSocketAddress(xForwardedValues.get(index), 0); the InetSocketAddress(String,int) ctor performs a blocking DNS lookup when the value is not a literal IP. X-Forwarded-For is unvalidated client input; with the default maxTrustedIndex=1 the leftmost (most spoofable) value is selected. When shenyu.scheduler.enabled=false (default), HostAddressUtils.acquireIp runs on the Netty event loop → blocking DNS stalls all connections on that thread (DoS) and enables DNS exfiltration/reconnaissance.
Location
shenyu-web/src/main/java/org/apache/shenyu/web/forward/ForwardedRemoteAddressResolver.java:89-108 (inverted guard :106-108; blocking DNS :89-90)
shenyu-spring-boot-starter/shenyu-spring-boot-starter-gateway/src/main/java/org/apache/shenyu/springboot/starter/gateway/ShenyuConfiguration.java:158-160 (production default)
Impact
- Default deployment (single-hop trusted proxy appending one client IP — the most common case) silently ignores the client IP.
HostAddressUtils.acquireIp(exchange) (consumed by AbstractLoggingPlugin for clientIp, and by any IP-based allow/deny/rate-limit feature) returns the proxy's IP. Access logs, rate limiting, and IP-based security all see the wrong IP.
- DoS: crafted
X-Forwarded-For hostnames trigger blocking DNS (up to system DNS timeout, 5–15s) on the event loop.
- DNS exfiltration: gateway resolves attacker-controlled hostnames, leaking data to attacker's DNS server.
Suggested fix
- Change
StringUtils.isNotEmpty(values.get(0)) → StringUtils.isEmpty(values.get(0)) so only empty single values are discarded.
- Validate the selected value is a literal IP (
InetAddressUtils.isIPv4/IPv6 or try InetAddress.getByAddress) before constructing InetSocketAddress; otherwise fall back to the TCP remote address. Never call new InetSocketAddress(String,int) with unvalidated input on a reactive thread.
Related existing
None. Distinct from #6556 (WebSocket Upgrade header case-sensitivity in DefaultShenyuContextBuilder).
Description
ForwardedRemoteAddressResolver(the production default, wired asnew ForwardedRemoteAddressResolver(1)inShenyuConfiguration) has an inverted guard inextractXForwardedValues: after splittingX-Forwarded-Forby", ", it doesisNotEmptyis the opposite of the intended check. The intent is to discard a single empty value (blank header), but the code discards a single non-empty value — i.e. a valid single client IP.resolve()then falls through to the TCP peer address (the proxy's IP), discarding the real client IP. Conversely, an empty-value header is not discarded and producesnew InetSocketAddress("", 0)→ wildcard0.0.0.0.Additionally
resolve()constructsnew InetSocketAddress(xForwardedValues.get(index), 0); theInetSocketAddress(String,int)ctor performs a blocking DNS lookup when the value is not a literal IP.X-Forwarded-Foris unvalidated client input; with the defaultmaxTrustedIndex=1the leftmost (most spoofable) value is selected. Whenshenyu.scheduler.enabled=false(default),HostAddressUtils.acquireIpruns on the Netty event loop → blocking DNS stalls all connections on that thread (DoS) and enables DNS exfiltration/reconnaissance.Location
shenyu-web/src/main/java/org/apache/shenyu/web/forward/ForwardedRemoteAddressResolver.java:89-108(inverted guard :106-108; blocking DNS :89-90)shenyu-spring-boot-starter/shenyu-spring-boot-starter-gateway/src/main/java/org/apache/shenyu/springboot/starter/gateway/ShenyuConfiguration.java:158-160(production default)Impact
HostAddressUtils.acquireIp(exchange)(consumed byAbstractLoggingPluginforclientIp, and by any IP-based allow/deny/rate-limit feature) returns the proxy's IP. Access logs, rate limiting, and IP-based security all see the wrong IP.X-Forwarded-Forhostnames trigger blocking DNS (up to system DNS timeout, 5–15s) on the event loop.Suggested fix
StringUtils.isNotEmpty(values.get(0))→StringUtils.isEmpty(values.get(0))so only empty single values are discarded.InetAddressUtils.isIPv4/IPv6or tryInetAddress.getByAddress) before constructingInetSocketAddress; otherwise fall back to the TCP remote address. Never callnew InetSocketAddress(String,int)with unvalidated input on a reactive thread.Related existing
None. Distinct from #6556 (WebSocket Upgrade header case-sensitivity in
DefaultShenyuContextBuilder).