highJun 25, 2026

chi RealIP Middleware IP Spoofing via Unvalidated X-Forwarded-For Header

Shubham Kandhare
Security Engagement Manager, SecureLayer7

chi's built-in RealIP middleware blindly trusts client-supplied headers like X-Forwarded-For and X-Real-IP without verifying the request came from a trusted proxy, letting any attacker fake their IP…

Packagegithub.com/go-chi/chi/middleware
Ecosystemgo
Affected<= 1.5.5
chi RealIP Middleware IP Spoofing via Unvalidated X-Forwarded-For Header

The problem

The middleware.RealIP function in go-chi/chi reads True-Client-IP, X-Real-IP, and X-Forwarded-For headers in that order and unconditionally overwrites r.RemoteAddr with whatever value the client supplied. No check is made to confirm the request actually arrived from a known, trusted proxy.

Any attacker who can reach the chi server directly, or whose upstream proxy does not sanitize those headers, can set an arbitrary IP. This breaks IP-based access controls, rate limiting, geo-fencing, and produces falsified audit logs.

Proof of concept

A working proof-of-concept for this issue in github.com/go-chi/chi/middleware, with the exact payload below.

bash
curl -H "X-Forwarded-For: 127.0.0.1" http://target:8080/admin

The old realIP() function called r.Header.Get(xForwardedFor) and took the leftmost comma-separated value, then assigned it directly to r.RemoteAddr. HTTP headers are fully attacker-controlled, so the leftmost XFF entry is always the one the client wrote.

The patch (v5.3.0, PR #967) removes RealIP as a safe default and replaces it with four new middlewares (ClientIPFromHeader, ClientIPFromXFF, ClientIPFromXFFTrustedProxies, ClientIPFromRemoteAddr) that require the operator to explicitly declare trusted proxy CIDRs or a trusted header name. ClientIPFromXFF walks the XFF chain right-to-left, stopping at the first IP not covered by a declared trusted CIDR, so attacker-appended leftmost entries are never reached.

The result is stored in request context via GetClientIP/GetClientIPAddr and r.RemoteAddr is never mutated.

The fix

Upgrade to go-chi/chi v5.3.0 or later. Replace middleware.RealIP with exactly one of the new ClientIPFrom* middlewares that matches your deployment topology. For a server behind Cloudflare use middleware.ClientIPFromHeader("CF-Connecting-IP"); for a proxy fleet with known CIDRs use middleware.ClientIPFromXFF("<cidr>", ...).

For direct internet exposure with no proxy use middleware.ClientIPFromRemoteAddr. Read the resolved IP with middleware.GetClientIP(ctx) instead of r.RemoteAddr.

Reported by rezmoss.

References: [1][2][3]

Related research