{"id":"CVE-2026-118651","aliases":["DEBIAN-CVE-2026-118651","EUVD-2026-112406","GHSA-99jh-6h7p-pp36","GO-2026-6875","UBUNTU-CVE-2026-118651"],"status":"published","title":"Echo: Reverse Proxy Forwards Attacker-Supplied X-Real-IP to Upstream by Default","description":"Echo is a Go web framework. From 4.0.0 until 5.4.0, the Proxy middleware can preserve an attacker-supplied X-Real-IP header and forward it to an upstream service when Echo.IPExtractor is not configured. A remote client can therefore make upstream services receive a spoofed client address, undermining IP-based access controls, rate limiting, audit logging, or geographic restrictions that trust Echo's forwarded identity. This issue is fixed in Echo 5.4.0.","severity":{"version":"3.1","score":5.3,"vector":"CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:N/I:L/A:N","level":"medium","assessor":"cna","from":"cve"},"epss":{"day":"2026-11-12","score":0.00207,"percentile":0.10893,"model":"v2026.06.15"},"facts":{"in_kev":false,"known_exploited":false,"public_exploit":false,"fix_available":true},"cwes":["CWE-346"],"published":"2026-10-28T19:30:01Z","modified":"2026-11-05T23:25:35Z","fixes":[{"ecosystem":"Go","package":"github.com/labstack/echo/v5","fixed_in":["5.4.0"],"sources":["ghsa","osv"]}],"affected":[{"source":"ghsa","ecosystem":"Go","package":"github.com/labstack/echo/v5","ranges":[{"type":"ECOSYSTEM","events":[{"introduced":"0"},{"fixed":"5.4.0"}]}]},{"source":"ghsa","ecosystem":"Go","package":"github.com/labstack/echo/v4","ranges":[{"type":"ECOSYSTEM","events":[{"introduced":"4.0.0"},{"last_affected":"4.16.2"}]}]},{"source":"osv","ecosystem":"Debian:12","package":"golang-github-labstack-echo","ranges":[{"type":"ECOSYSTEM","events":[{"introduced":"0"}]}],"versions":["4.11.1-1","4.11.1-2","4.12.0-1","4.2.1-3"]},{"source":"osv","ecosystem":"Debian:13","package":"golang-github-labstack-echo","ranges":[{"type":"ECOSYSTEM","events":[{"introduced":"0"}]}],"versions":["4.12.0-1"]},{"source":"osv","ecosystem":"Debian:14","package":"golang-github-labstack-echo","ranges":[{"type":"ECOSYSTEM","events":[{"introduced":"0"}]}],"versions":["4.12.0-1"]},{"source":"osv","ecosystem":"Go","package":"github.com/labstack/echo/v5","ranges":[{"type":"SEMVER","events":[{"introduced":"0"},{"fixed":"5.4.0"}]}]},{"source":"osv","ecosystem":"Go","package":"github.com/labstack/echo/v4","ranges":[{"type":"SEMVER","events":[{"introduced":"4.0.0"},{"last_affected":"4.16.2"}]}]},{"source":"osv","ecosystem":"Go","package":"github.com/labstack/echo/v4","ranges":[{"type":"SEMVER","events":[{"introduced":"4.0.0"}]}]},{"source":"osv","ecosystem":"Go","package":"github.com/labstack/echo/v5","ranges":[{"type":"SEMVER","events":[{"introduced":"0"},{"fixed":"5.4.0"}]}]},{"source":"osv","ecosystem":"Ubuntu:20.04:LTS","package":"golang-github-labstack-echo.v2","ranges":[{"type":"ECOSYSTEM","events":[{"introduced":"0"}]}],"versions":["2.2.0-1"]},{"source":"osv","ecosystem":"Ubuntu:20.04:LTS","package":"golang-github-labstack-echo.v3","ranges":[{"type":"ECOSYSTEM","events":[{"introduced":"0"}]}],"versions":["3.3.5-1"]},{"source":"osv","ecosystem":"Ubuntu:22.04:LTS","package":"golang-github-labstack-echo","ranges":[{"type":"ECOSYSTEM","events":[{"introduced":"0"}]}],"versions":["4.2.1-2"]},{"source":"osv","ecosystem":"Ubuntu:22.04:LTS","package":"golang-github-labstack-echo.v2","ranges":[{"type":"ECOSYSTEM","events":[{"introduced":"0"}]}],"versions":["2.2.0-2"]},{"source":"osv","ecosystem":"Ubuntu:22.04:LTS","package":"golang-github-labstack-echo.v3","ranges":[{"type":"ECOSYSTEM","events":[{"introduced":"0"}]}],"versions":["3.3.10-1"]},{"source":"osv","ecosystem":"Ubuntu:24.04:LTS","package":"golang-github-labstack-echo","ranges":[{"type":"ECOSYSTEM","events":[{"introduced":"0"}]}],"versions":["4.11.1-2"]},{"source":"osv","ecosystem":"Ubuntu:26.10","package":"golang-github-labstack-echo","ranges":[{"type":"ECOSYSTEM","events":[{"introduced":"0"}]}],"versions":["4.12.0-1"]},{"source":"osv","ecosystem":"Ubuntu:26.04:LTS","package":"golang-github-labstack-echo","ranges":[{"type":"ECOSYSTEM","events":[{"introduced":"0"}]}],"versions":["4.12.0-1"]}],"products":[{"vendor":"labstack","product":"echo"},{"vendor":"wikimedia foundation","product":"echo"}],"references":[{"url":"https://github.com/labstack/echo/security/advisories/GHSA-99jh-6h7p-pp36","tags":["x_refsource_CONFIRM","advisory","web","report"]},{"url":"https://github.com/labstack/echo/commit/fc410094b2f3415cf2bf5204082137b205bfd5af","tags":["x_refsource_MISC","fix","web","report"]},{"url":"https://github.com/labstack/echo/releases/tag/v5.4.0","tags":["x_refsource_MISC","web","report"]},{"url":"https://github.com/advisories/GHSA-99jh-6h7p-pp36"},{"url":"https://github.com/CVEProject/cvelistV5/tree/main/cves/2026/118xxx/CVE-2026-118651.json","tags":["advisory"]},{"url":"https://nvd.nist.gov/vuln/detail/CVE-2026-118651","tags":["advisory"]},{"url":"https://security-tracker.debian.org/tracker/CVE-2026-118651","tags":["advisory"]},{"url":"https://github.com/labstack/echo","tags":["package"]},{"url":"https://ubuntu.com/security/CVE-2026-118651","tags":["report"]},{"url":"https://www.cve.org/CVERecord?id=CVE-2026-118651","tags":["report"]}],"sources":{"cve":[{"id":"CVE-2026-118651","title":"Echo: Reverse Proxy Forwards Attacker-Supplied X-Real-IP to Upstream by Default","description":"Echo is a Go web framework. From 4.0.0 until 5.4.0, the Proxy middleware can preserve an attacker-supplied X-Real-IP header and forward it to an upstream service when Echo.IPExtractor is not configured. A remote client can therefore make upstream services receive a spoofed client address, undermining IP-based access controls, rate limiting, audit logging, or geographic restrictions that trust Echo's forwarded identity. This issue is fixed in Echo 5.4.0.","status":"published","published":"2026-10-28T19:30:01.32Z","modified":"2026-10-29T20:53:54.404Z","cvss":[{"version":"3.1","score":5.3,"vector":"CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:N/I:L/A:N","level":"medium","assessor":"cna"}],"cwes":["CWE-346"],"updated_at":"2026-11-02T14:20:03.827373Z"}],"euvd":[{"id":"EUVD-2026-112406","description":"Echo is a Go web framework. From 4.0.0 until 5.4.0, the Proxy middleware can preserve an attacker-supplied X-Real-IP header and forward it to an upstream service when Echo.IPExtractor is not configured. A remote client can therefore make upstream services receive a spoofed client address, undermining IP-based access controls, rate limiting, audit logging, or geographic restrictions that trust Echo's forwarded identity. This issue is fixed in Echo 5.4.0.","status":"published","published":"2026-10-28T19:30:01Z","modified":"2026-10-29T20:53:54Z","cvss":[{"version":"3.1","score":5.3,"vector":"CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:N/I:L/A:N","level":"medium"}],"updated_at":"2026-11-02T16:59:14.591479Z"}],"ghsa":[{"id":"GHSA-99jh-6h7p-pp36","title":"Echo: Reverse Proxy Forwards Attacker-Supplied X-Real-IP to Upstream by Default","description":"## Summary\n\nThe 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.\n\n## Affected Component\n\n- `middleware/proxy.go` — `ProxyConfig.ToMiddleware()`, header-rewriting block (lines 351–355)\n\n## Description\n\n### The header-preservation condition passes spoofed values to upstream\n\n`ProxyConfig.ToMiddleware()` (`proxy.go:338-405`) prepares forwarding headers before dispatching each request. The `X-Real-IP` rewrite logic at lines 351-355 reads:\n\n```go\n// proxy.go:351-355\n// Basically it's not good practice to unconditionally pass incoming x-real-ip header to upstream.\n// However, for backward compatibility, legacy behavior is preserved unless you configure Echo#IPExtractor.\nif req.Header.Get(echo.HeaderXRealIP) == \"\" || c.Echo().IPExtractor != nil {\n    req.Header.Set(echo.HeaderXRealIP, c.RealIP())\n}\n```\n\nThe condition is `TRUE` (and therefore overwrites the header with a derived value) only when:\n- The header is absent — `req.Header.Get(echo.HeaderXRealIP) == \"\"`; **or**\n- `IPExtractor` is explicitly configured — `c.Echo().IPExtractor != nil`\n\nThe 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.\n\nAn attacker who sets `X-Real-IP` to any value causes Echo to forward exactly that value to the upstream, with no modification or validation.\n\n### The comment in the source code confirms intent and the security gap\n\nThe inline comment at line 351-352 explicitly states:\n\n\u003e \"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.\"\n\nThe 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.\n\nThis is structurally identical to the documented insecurity in `ip.go:27`:\n\n\u003e \"Note: if you don't set `Echo#IPExtractor` explicitly, Echo fallback to legacy behavior, which is not a good choice.\"\n\n### Full execution chain\n\n1. Attacker sends `GET /api/admin` to the Echo proxy with header `X-Real-IP: 203.0.113.1` (a trusted corporate IP)\n2. `ToMiddleware()` evaluates the guard at line 353: header is present (`!= \"\"`), `IPExtractor` is `nil` → condition is `FALSE`\n3. `req.Header.Set` is **not** called; the upstream request carries the original, attacker-supplied `X-Real-IP: 203.0.113.1`\n4. Upstream service checks `X-Real-IP` against its IP allowlist → finds `203.0.113.1` → grants access\n5. Attacker reaches an endpoint that should be restricted to the corporate IP range\n\nSecondary impact path (audit poisoning):\n- 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.\n\n## Proof of Concept\n\n```go\npackage main\n\nimport (\n    \"fmt\"\n    \"log\"\n    \"net/http\"\n    echo \"github.com/labstack/echo/v5\"\n    \"github.com/labstack/echo/v5/middleware\"\n)\n\nfunc main() {\n    // Upstream server: echoes back the X-Real-IP it received\n    upstream := http.NewServeMux()\n    upstream.HandleFunc(\"/\", func(w http.ResponseWriter, r *http.Request) {\n        fmt.Fprintf(w, \"upstream sees X-Real-IP: %s\\n\", r.Header.Get(\"X-Real-IP\"))\n    })\n    go http.ListenAndServe(\":9000\", upstream)\n\n    // Echo proxy — IPExtractor deliberately NOT set (the default)\n    e := echo.New()\n    targets := []*middleware.ProxyTarget{{URL: mustParse(\"http://127.0.0.1:9000\")}}\n    e.Use(middleware.Proxy(middleware.NewRoundRobinBalancer(targets)))\n    log.Fatal(e.Start(\":1323\"))\n}\n```\n\n```bash\n# Without spoofed header — upstream sees actual peer address\ncurl http://127.0.0.1:1323/\n# upstream sees X-Real-IP: 127.0.0.1\n\n# With spoofed header — upstream sees attacker-supplied value\ncurl -H 'X-Real-IP: 198.51.100.77' http://127.0.0.1:1323/\n# upstream sees X-Real-IP: 198.51.100.77\n```\n\nThe attacker-supplied IP `198.51.100.77` arrives at the upstream unchanged.\n\n## Impact\n\n- Upstream IP-based access controls (allowlists, blocklists, geo-restrictions) can be bypassed by supplying the expected IP in `X-Real-IP`\n- Upstream rate limiters that key on `X-Real-IP` are bypassable (same class as the Echo rate-limiter finding, now on every upstream service)\n- Audit logs, intrusion detection systems, and SIEM tools that rely on `X-Real-IP` record the attacker-chosen IP, not the real source — forensic trails are poisoned\n- Any upstream admin endpoint restricted by IP allowlist is reachable by any attacker who knows or guesses a trusted IP address\n\n## Recommended Remediation\n\n### Option 1: Always overwrite `X-Real-IP` from an authoritative source (preferred)\n\nRemove the conditional that preserves the incoming header. Echo, as the edge proxy, is the only authoritative source for the real client IP:\n\n```go\n// proxy.go — always set X-Real-IP from an authoritative source\nreq.Header.Set(echo.HeaderXRealIP, c.RealIP())\n```\n\nFor this to be safe, operators using `Proxy()` should also configure `IPExtractor`:\n\n```go\ne.IPExtractor = echo.ExtractIPDirect() // no upstream proxy\n// or\ne.IPExtractor = echo.ExtractIPFromXFFHeader() // behind a trusted upstream LB\n```\n\n### Option 2: Document and warn in `Proxy()` godoc\n\nIf backward compatibility must be preserved, add a prominent security warning to `Proxy()` and `ProxyWithConfig()`:\n\n```go\n// Proxy returns a Proxy middleware.\n//\n// SECURITY: By default, an incoming X-Real-IP header supplied by the client\n// is forwarded to the upstream unchanged. This allows any client to spoof its\n// IP identity to upstream services. Configure Echo#IPExtractor before using\n// this middleware to ensure X-Real-IP is derived from a trusted source:\n//\n//   e.IPExtractor = echo.ExtractIPDirect()\n//\n// See https://echo.labstack.com/guide/ip-address for details.\nfunc Proxy(balancer ProxyBalancer) echo.MiddlewareFunc {\n```\n\n## Credit\n\nThis vulnerability was discovered and reported by [bugbunny.ai](https://bugbunny.ai).\n\n## Patches\n\nFixed in `github.com/labstack/echo/v5` v5.4.0: the Proxy middleware always sets `X-Real-IP` from `Context.RealIP()`, which is the address of the direct peer unless `Echo#IPExtractor` is configured.\n\n`github.com/labstack/echo/v4` is not changed, because v4's `Context.RealIP()` without `Echo#IPExtractor` trusts `X-Forwarded-For` and `X-Real-IP` by design, and changing that default would merge all clients behind a proxy into one rate-limit bucket.\n\n## Workarounds\n\nConfigure `Echo#IPExtractor` for your deployment, for example `e.IPExtractor = echo.ExtractIPDirect()` when there is no proxy in front of the application, or `echo.ExtractIPFromXFFHeader()` / `echo.ExtractIPFromRealIPHeader()` (with `TrustIPRange` for proxies on public addresses). With an extractor set, the Proxy middleware overwrites `X-Real-IP` in v4 too.","status":"published","published":"2026-10-28T19:53:24Z","modified":"2026-10-28T19:53:25Z","cvss":[{"version":"3.1","score":5.3,"vector":"CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:N/I:L/A:N","level":"medium"}],"cwes":["CWE-346"],"updated_at":"2026-11-02T14:41:39.926569Z"}],"nvd":[{"id":"CVE-2026-118651","description":"Echo is a Go web framework. From 4.0.0 until 5.4.0, the Proxy middleware can preserve an attacker-supplied X-Real-IP header and forward it to an upstream service when Echo.IPExtractor is not configured. A remote client can therefore make upstream services receive a spoofed client address, undermining IP-based access controls, rate limiting, audit logging, or geographic restrictions that trust Echo's forwarded identity. This issue is fixed in Echo 5.4.0.","status":"published","published":"2026-10-28T20:17:22.073Z","modified":"2026-10-29T21:17:16.26Z","cvss":[{"version":"3.1","score":5.3,"vector":"CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:N/I:L/A:N","level":"medium","assessor":"secondary"}],"cwes":["CWE-346"],"updated_at":"2026-11-02T14:13:04.6346Z"}],"osv":[{"id":"CVE-2026-118651","title":"Echo: Reverse Proxy Forwards Attacker-Supplied X-Real-IP to Upstream by Default","description":"Echo is a Go web framework. From 4.0.0 until 5.4.0, the Proxy middleware can preserve an attacker-supplied X-Real-IP header and forward it to an upstream service when Echo.IPExtractor is not configured. A remote client can therefore make upstream services receive a spoofed client address, undermining IP-based access controls, rate limiting, audit logging, or geographic restrictions that trust Echo's forwarded identity. This issue is fixed in Echo 5.4.0.","status":"published","published":"2026-10-28T19:30:01.32Z","modified":"2026-11-05T23:25:35.191121662Z","cvss":[{"version":"3.1","score":5.3,"vector":"CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:N/I:L/A:N","level":"medium"}],"cwes":["CWE-346"],"updated_at":"2026-11-06T00:02:13.631839Z"},{"id":"DEBIAN-CVE-2026-118651","description":"Echo is a Go web framework. From 4.0.0 until 5.4.0, the Proxy middleware can preserve an attacker-supplied X-Real-IP header and forward it to an upstream service when Echo.IPExtractor is not configured. A remote client can therefore make upstream services receive a spoofed client address, undermining IP-based access controls, rate limiting, audit logging, or geographic restrictions that trust Echo's forwarded identity. This issue is fixed in Echo 5.4.0.","status":"published","published":"2026-10-28T20:17:22.073Z","modified":"2026-10-30T09:49:06.484248724Z","cvss":[{"version":"3.1","score":5.3,"vector":"CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:N/I:L/A:N","level":"medium"}],"updated_at":"2026-11-02T15:45:51.374409Z"},{"id":"GHSA-99jh-6h7p-pp36","title":"Echo: Reverse Proxy Forwards Attacker-Supplied X-Real-IP to Upstream by Default","description":"## Summary\n\nThe 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.\n\n## Affected Component\n\n- `middleware/proxy.go` — `ProxyConfig.ToMiddleware()`, header-rewriting block (lines 351–355)\n\n## Description\n\n### The header-preservation condition passes spoofed values to upstream\n\n`ProxyConfig.ToMiddleware()` (`proxy.go:338-405`) prepares forwarding headers before dispatching each request. The `X-Real-IP` rewrite logic at lines 351-355 reads:\n\n```go\n// proxy.go:351-355\n// Basically it's not good practice to unconditionally pass incoming x-real-ip header to upstream.\n// However, for backward compatibility, legacy behavior is preserved unless you configure Echo#IPExtractor.\nif req.Header.Get(echo.HeaderXRealIP) == \"\" || c.Echo().IPExtractor != nil {\n    req.Header.Set(echo.HeaderXRealIP, c.RealIP())\n}\n```\n\nThe condition is `TRUE` (and therefore overwrites the header with a derived value) only when:\n- The header is absent — `req.Header.Get(echo.HeaderXRealIP) == \"\"`; **or**\n- `IPExtractor` is explicitly configured — `c.Echo().IPExtractor != nil`\n\nThe 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.\n\nAn attacker who sets `X-Real-IP` to any value causes Echo to forward exactly that value to the upstream, with no modification or validation.\n\n### The comment in the source code confirms intent and the security gap\n\nThe inline comment at line 351-352 explicitly states:\n\n\u003e \"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.\"\n\nThe 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.\n\nThis is structurally identical to the documented insecurity in `ip.go:27`:\n\n\u003e \"Note: if you don't set `Echo#IPExtractor` explicitly, Echo fallback to legacy behavior, which is not a good choice.\"\n\n### Full execution chain\n\n1. Attacker sends `GET /api/admin` to the Echo proxy with header `X-Real-IP: 203.0.113.1` (a trusted corporate IP)\n2. `ToMiddleware()` evaluates the guard at line 353: header is present (`!= \"\"`), `IPExtractor` is `nil` → condition is `FALSE`\n3. `req.Header.Set` is **not** called; the upstream request carries the original, attacker-supplied `X-Real-IP: 203.0.113.1`\n4. Upstream service checks `X-Real-IP` against its IP allowlist → finds `203.0.113.1` → grants access\n5. Attacker reaches an endpoint that should be restricted to the corporate IP range\n\nSecondary impact path (audit poisoning):\n- 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.\n\n## Proof of Concept\n\n```go\npackage main\n\nimport (\n    \"fmt\"\n    \"log\"\n    \"net/http\"\n    echo \"github.com/labstack/echo/v5\"\n    \"github.com/labstack/echo/v5/middleware\"\n)\n\nfunc main() {\n    // Upstream server: echoes back the X-Real-IP it received\n    upstream := http.NewServeMux()\n    upstream.HandleFunc(\"/\", func(w http.ResponseWriter, r *http.Request) {\n        fmt.Fprintf(w, \"upstream sees X-Real-IP: %s\\n\", r.Header.Get(\"X-Real-IP\"))\n    })\n    go http.ListenAndServe(\":9000\", upstream)\n\n    // Echo proxy — IPExtractor deliberately NOT set (the default)\n    e := echo.New()\n    targets := []*middleware.ProxyTarget{{URL: mustParse(\"http://127.0.0.1:9000\")}}\n    e.Use(middleware.Proxy(middleware.NewRoundRobinBalancer(targets)))\n    log.Fatal(e.Start(\":1323\"))\n}\n```\n\n```bash\n# Without spoofed header — upstream sees actual peer address\ncurl http://127.0.0.1:1323/\n# upstream sees X-Real-IP: 127.0.0.1\n\n# With spoofed header — upstream sees attacker-supplied value\ncurl -H 'X-Real-IP: 198.51.100.77' http://127.0.0.1:1323/\n# upstream sees X-Real-IP: 198.51.100.77\n```\n\nThe attacker-supplied IP `198.51.100.77` arrives at the upstream unchanged.\n\n## Impact\n\n- Upstream IP-based access controls (allowlists, blocklists, geo-restrictions) can be bypassed by supplying the expected IP in `X-Real-IP`\n- Upstream rate limiters that key on `X-Real-IP` are bypassable (same class as the Echo rate-limiter finding, now on every upstream service)\n- Audit logs, intrusion detection systems, and SIEM tools that rely on `X-Real-IP` record the attacker-chosen IP, not the real source — forensic trails are poisoned\n- Any upstream admin endpoint restricted by IP allowlist is reachable by any attacker who knows or guesses a trusted IP address\n\n## Recommended Remediation\n\n### Option 1: Always overwrite `X-Real-IP` from an authoritative source (preferred)\n\nRemove the conditional that preserves the incoming header. Echo, as the edge proxy, is the only authoritative source for the real client IP:\n\n```go\n// proxy.go — always set X-Real-IP from an authoritative source\nreq.Header.Set(echo.HeaderXRealIP, c.RealIP())\n```\n\nFor this to be safe, operators using `Proxy()` should also configure `IPExtractor`:\n\n```go\ne.IPExtractor = echo.ExtractIPDirect() // no upstream proxy\n// or\ne.IPExtractor = echo.ExtractIPFromXFFHeader() // behind a trusted upstream LB\n```\n\n### Option 2: Document and warn in `Proxy()` godoc\n\nIf backward compatibility must be preserved, add a prominent security warning to `Proxy()` and `ProxyWithConfig()`:\n\n```go\n// Proxy returns a Proxy middleware.\n//\n// SECURITY: By default, an incoming X-Real-IP header supplied by the client\n// is forwarded to the upstream unchanged. This allows any client to spoof its\n// IP identity to upstream services. Configure Echo#IPExtractor before using\n// this middleware to ensure X-Real-IP is derived from a trusted source:\n//\n//   e.IPExtractor = echo.ExtractIPDirect()\n//\n// See https://echo.labstack.com/guide/ip-address for details.\nfunc Proxy(balancer ProxyBalancer) echo.MiddlewareFunc {\n```\n\n## Credit\n\nThis vulnerability was discovered and reported by [bugbunny.ai](https://bugbunny.ai).\n\n## Patches\n\nFixed in `github.com/labstack/echo/v5` v5.4.0: the Proxy middleware always sets `X-Real-IP` from `Context.RealIP()`, which is the address of the direct peer unless `Echo#IPExtractor` is configured.\n\n`github.com/labstack/echo/v4` is not changed, because v4's `Context.RealIP()` without `Echo#IPExtractor` trusts `X-Forwarded-For` and `X-Real-IP` by design, and changing that default would merge all clients behind a proxy into one rate-limit bucket.\n\n## Workarounds\n\nConfigure `Echo#IPExtractor` for your deployment, for example `e.IPExtractor = echo.ExtractIPDirect()` when there is no proxy in front of the application, or `echo.ExtractIPFromXFFHeader()` / `echo.ExtractIPFromRealIPHeader()` (with `TrustIPRange` for proxies on public addresses). With an extractor set, the Proxy middleware overwrites `X-Real-IP` in v4 too.","status":"published","published":"2026-10-28T19:53:24Z","modified":"2026-11-05T23:25:35.191121662Z","cvss":[{"version":"3.1","score":5.3,"vector":"CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:N/I:L/A:N","level":"medium"}],"cwes":["CWE-346"],"updated_at":"2026-11-06T00:02:13.631839Z"},{"id":"UBUNTU-CVE-2026-118651","description":"Echo is a Go web framework. From 4.0.0 until 5.4.0, the Proxy middleware can preserve an attacker-supplied X-Real-IP header and forward it to an upstream service when Echo.IPExtractor is not configured. A remote client can therefore make upstream services receive a spoofed client address, undermining IP-based access controls, rate limiting, audit logging, or geographic restrictions that trust Echo's forwarded identity. This issue is fixed in Echo 5.4.0.","status":"published","published":"2026-10-28T20:17:00Z","modified":"2026-11-05T04:42:51Z","cvss":[{"version":"3.1","score":5.3,"vector":"CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:N/I:L/A:N","level":"medium"}],"updated_at":"2026-11-05T13:33:25.727744Z"},{"id":"GO-2026-6875","title":"Reverse Proxy Forwards Attacker-Supplied X-Real-IP to Upstream by Default in github.com/labstack/echo","description":"Reverse Proxy Forwards Attacker-Supplied X-Real-IP to Upstream by Default in github.com/labstack/echo","status":"published","published":"2026-11-05T22:54:32Z","modified":"2026-11-05T23:15:15.563594538Z","updated_at":"2026-11-06T00:02:13.631839Z"}]}}
