apache / apache/shenyu

[BUG] ForwardedRemoteAddressResolver inverted guard discards client IP + blocking DNS on unvalidated X-Forwarded-For

Open
#6,824 1 comment 0 reactions 0 assignees View on GitHub
feature: http-proxy priority: high type: bug
Dominant language
Java
Stars
8.8k
Forks
3.1k
Avg merge
7d 1h
Merged PRs (30d)
85

Description

## 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
```java
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`).

Contributor guide

No contributing guide indexed for this repository

Research direction

Start with shenyu-web/src/main/java/org/apache/shenyu/web/forward/ForwardedRemoteAddressResolver.java, tracing extractXForwardedValues and resolve(), then check the default wiring in ShenyuConfiguration.java. Done means a single valid client IP is retained, blank or non-literal values fall back safely, and unvalidated X-Forwarded-For input cannot trigger blocking DNS on the reactive thread.

Written by the indexing model from the issue text.

Assessment

Tech stack
java
Domain
backend-api-design, networking, security
Issue type
Bug
Difficulty
4/5
Estimated time
3-5 days
Activity status
Quiet
Clarity
Clearly specified
Newbie friendliness
55/100

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.