Skip to main content
VulnerabilityPublished

CVE-2026-118651: Echo · Reverse Proxy Forwards Attacker-Supplied X-Real-IP to Upstream by Default

Affects github.com/labstack/echo/v5Source GCVE Database

0
Authoritative record:CVE-2026-118651· Medium 5.3

Not known to be exploited · CVSS 5.3 Medium : upgrade to 5.4.0 when convenient.

Data as of 28 Oct 2026, 21:14 UTC

CVSS 3.1 base score
5.3Medium
How severe the flaw is on the 0–10 CVSS scale.
Patch
Available – fixed in 5.4.0
CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:N/I:L/A:N

Exploitation evidence

  1. Disclosed: evidence found.

    CVE published 28 Oct 2026

  2. Detectable: no evidence on record.

    No public nuclei template indexed

  3. Weaponised: no evidence on record.

    No public exploit code indexed in Metasploit or Exploit-DB

  4. Exploited in the wild: no evidence on record.

    Not in CISA KEV and no source reports exploitation

  5. Observed by OffSeq Mirage: no evidence on record.

    No attack traffic recorded by OffSeq Mirage sensors

A missing step is not evidence of safety. Sources: cve.org (opens in a new tab), the Metasploit, Exploit-DB and nuclei-templates index as of 26 Oct 2026.

What to do

Upgrade github.com/labstack/echo/v5 to 5.4.0.

Affected software

ProductAffectedFixed in
Gogithub.com/labstack/echo/v5Affected
  • <5.4.0
Fixed in
  • 5.4.0
Gogithub.com/labstack/echo/v4Affected
  • >=4.0.0 <=4.16.2
Fixed in–

Check your own hosts for these packages with threat-finder (opens in a new tab), our free open-source scanner.

Description

## Summary The Echo proxy middleware (`middleware/proxy.go`) preserves an incoming `X-Real-IP` header verbatim and forwards it to the upstream service when `Echo#IPExtractor` is not configured — which is the default. Because any HTTP client can supply an arbitrary `X-Real-IP` value, an attacker can cause Echo to relay a spoofed client IP address to every upstream service behind the proxy, undermining upstream IP-based access controls, rate limiting, audit logging, and geo-restriction. ## Affected Component - `middleware/proxy.go` — `ProxyConfig.ToMiddleware()`, header-rewriting block (lines 351–355) ## Description ### The header-preservation condition passes spoofed values to upstream `ProxyConfig.ToMiddleware()` (`proxy.go:338-405`) prepares forwarding headers before dispatching each request. The `X-Real-IP` rewrite logic at lines 351-355 reads: ```go // proxy.go:351-355 // Basically it's not good practice to unconditionally pass incoming x-real-ip header to upstream. // However, for backward compatibility, legacy behavior is preserved unless you configure Echo#IPExtractor. if req.Header.Get(echo.HeaderXRealIP) == "" || c.Echo().IPExtractor != nil { req.Header.Set(echo.HeaderXRealIP, c.RealIP()) } ``` The condition is `TRUE` (and therefore overwrites the header with a derived value) only when: - The header is absent — `req.Header.Get(echo.HeaderXRealIP) == ""`; **or** - `IPExtractor` is explicitly configured — `c.Echo().IPExtractor != nil` The condition is `FALSE` — and the header is left **unchanged** — when the request arrives with an `X-Real-IP` header already set **and** `IPExtractor` is `nil`. Because `Echo.New()` leaves `IPExtractor` as `nil`, this is the out-of-the-box behaviour for every application that uses `Proxy()` or `ProxyWithConfig()` without extra configuration. An attacker who sets `X-Real-IP` to any value causes Echo to forward exactly that value to the upstream, with no modification or validation. ### The comment in the source code confirms intent and the security gap The inline comment at line 351-352 explicitly states: > "Basically it's not good practice to unconditionally pass incoming x-real-ip header to upstream. However, for backward compatibility, legacy behavior is preserved unless you configure Echo#IPExtractor." The framework is aware that the default is insecure and retains it for backward compatibility. No warning appears in the `Proxy()` or `ProxyWithConfig()` documentation, so operators who follow the standard setup example receive a silently insecure proxy configuration. This is structurally identical to the documented insecurity in `ip.go:27`: > "Note: if you don't set `Echo#IPExtractor` explicitly, Echo fallback to legacy behavior, which is not a good choice." ### Full execution chain 1. Attacker sends `GET /api/admin` to the Echo proxy with header `X-Real-IP: 203.0.113.1` (a trusted corporate IP) 2. `ToMiddleware()` evaluates the guard at line 353: header is present (`!= ""`), `IPExtractor` is `nil` → condition is `FALSE` 3. `req.Header.Set` is **not** called; the upstream request carries the original, attacker-supplied `X-Real-IP: 203.0.113.1` 4. Upstream service checks `X-Real-IP` against its IP allowlist → finds `203.0.113.1` → grants access 5. Attacker reaches an endpoint that should be restricted to the corporate IP range Secondary impact path (audit poisoning): - All upstream log entries, rate-limit counters, and geo-restriction checks are evaluated against `203.0.113.1` rather than the real client IP, rendering audit trails and security analytics unreliable. ## Proof of Concept ```go package main import ( "fmt" "log" "net/http" echo "github.com/labstack/echo/v5" "github.com/labstack/echo/v5/middleware" ) func main() { // Upstream server: echoes back the X-Real-IP it received upstream := http.NewServeMux() upstream.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) { fmt.Fprintf(w, "upstream sees X-Real-IP: %s\n", r.Header.Get("X-Real-IP")) }) go http.ListenAndServe(":9000", upstream) // Echo proxy — IPExtractor deliberately NOT set (the default) e := echo.New() targets := []*middleware.ProxyTarget{{URL: mustParse("http://127.0.0.1:9000")}} e.Use(middleware.Proxy(middleware.NewRoundRobinBalancer(targets))) log.Fatal(e.Start(":1323")) } ``` ```bash # Without spoofed header — upstream sees actual peer address curl http://127.0.0.1:1323/ # upstream sees X-Real-IP: 127.0.0.1 # With spoofed header — upstream sees attacker-supplied value curl -H 'X-Real-IP: 198.51.100.77' http://127.0.0.1:1323/ # upstream sees X-Real-IP: 198.51.100.77 ``` The attacker-supplied IP `198.51.100.77` arrives at the upstream unchanged. ## Impact - Upstream IP-based access controls (allowlists, blocklists, geo-restrictions) can be bypassed by supplying the expected IP in `X-Real-IP` - Upstream rate limiters that key on `X-Real-IP` are bypassable (same class as the Echo rate-limiter fi

References

Community reviews

Loading reviews…