CVE-2026-118651

CVE-2026-118651 is a medium-severity security vulnerability in github.com/labstack/echo/v5 (go), affecting versions < 5.4.0. It is fixed in 5.4.0.

Does this CVE actually affect you?

Kodem shows which CVEs are reachable and running in your applications, so you fix what's exploitable, not just what's listed.

Thank you! Your submission has been received!
Oops! Something went wrong while submitting the form.

Runtime intelligence, not another scanner.

Summary

Echo: Reverse Proxy Forwards Attacker-Supplied X-Real-IP to Upstream by Default

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:

// 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

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"))
}
# 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.

Recommended Remediation

Option 1: Always overwrite X-Real-IP from an authoritative source (preferred)

Remove the conditional that preserves the incoming header. Echo, as the edge proxy, is the only authoritative source for the real client IP:

// proxy.go, always set X-Real-IP from an authoritative source
req.Header.Set(echo.HeaderXRealIP, c.RealIP())

For this to be safe, operators using Proxy() should also configure IPExtractor:

e.IPExtractor = echo.ExtractIPDirect() // no upstream proxy
// or
e.IPExtractor = echo.ExtractIPFromXFFHeader() // behind a trusted upstream LB

Option 2: Document and warn in Proxy() godoc

If backward compatibility must be preserved, add a prominent security warning to Proxy() and ProxyWithConfig():

// Proxy returns a Proxy middleware.
//
// SECURITY: By default, an incoming X-Real-IP header supplied by the client
// is forwarded to the upstream unchanged. This allows any client to spoof its
// IP identity to upstream services. Configure Echo#IPExtractor before using
// this middleware to ensure X-Real-IP is derived from a trusted source:
//
//   e.IPExtractor = echo.ExtractIPDirect()
//
// See https://echo.labstack.com/guide/ip-address for details.
func Proxy(balancer ProxyBalancer) echo.MiddlewareFunc {

Credit

This vulnerability was discovered and reported by bugbunny.ai.

Workarounds

Configure 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.

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 finding, now on every upstream service)
  • 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
  • Any upstream admin endpoint restricted by IP allowlist is reachable by any attacker who knows or guesses a trusted IP address

CVE-2026-118651 has a CVSS score of 5.3 (Medium). The vector is network-reachable, no privileges required, and no user interaction. A CVSS score reflects the worst-case severity of the vulnerability, not your specific exposure. Whether this affects your application depends on whether the vulnerable code is present and reachable in your environment. A fixed version is available (5.4.0); upgrading removes the vulnerable code path.

Affected versions

github.com/labstack/echo/v5 (< 5.4.0) github.com/labstack/echo/v4 (>= 4.0.0, <= 4.16.2)

Security releases

github.com/labstack/echo/v5 → 5.4.0 (go)

Kodem intelligence

Severity tells you how bad this could be in the worst case. It does not tell you whether you are exposed. Exploitability and impact are functions of runtime truth: whether the vulnerable code is present, reachable, and actually executes in your application. A vulnerable package can sit in your dependency tree and never run.

Kodem, an Intelligent Application Security platform, uses runtime intelligence to reveal which vulnerabilities actually execute in production, so teams prioritize the ones that genuinely matter. Kodem's runtime-powered SCA identifies whether this CVE is reachable in your applications.

Already deployed Kodem?

See it in your environmentNew to Kodem? Get a demo →

Remediation advice

Fixed 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.

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.

Frequently Asked Questions

  1. What is CVE-2026-118651? CVE-2026-118651 is a medium-severity security vulnerability in github.com/labstack/echo/v5 (go), affecting versions < 5.4.0. It is fixed in 5.4.0.
  2. How severe is CVE-2026-118651? CVE-2026-118651 has a CVSS score of 5.3 (Medium). This score reflects the worst-case severity of the vulnerability, not your specific exposure. Whether it represents real risk in your environment depends on whether the vulnerable code is present and reachable.
  3. Which packages are affected by CVE-2026-118651?
    • github.com/labstack/echo/v5 (go) (versions < 5.4.0)
    • github.com/labstack/echo/v4 (go) (versions >= 4.0.0, <= 4.16.2)
  4. Is there a fix for CVE-2026-118651? Yes. CVE-2026-118651 is fixed in 5.4.0. Upgrade to this version or later.
  5. Is CVE-2026-118651 exploitable, and should I be worried? Whether CVE-2026-118651 is exploitable in your environment depends on whether the vulnerable code is present and reachable. A CVSS score is a worst-case rating; it does not account for your specific deployment, configuration, or usage patterns. Kodem, an Intelligent Application Security platform, uses runtime intelligence to show which vulnerabilities actually execute in production, so you can focus on the ones that represent real risk. Get a demo
  6. What actually determines whether CVE-2026-118651 is exploitable, and how bad it is? Exploitability and impact are not fixed properties of a CVE. They depend on runtime truth: whether the vulnerable code is present, reachable, and actually executes in your application. A high CVSS score on a dependency that never runs is not the same as real risk. Kodem, an Intelligent Application Security platform, uses runtime intelligence to reveal which vulnerabilities actually execute in production, so teams prioritize the ones that genuinely matter.
  7. How do I fix CVE-2026-118651? Upgrade github.com/labstack/echo/v5 to 5.4.0 or later.

Other vulnerabilities in github.com/labstack/echo/v5

Stop the waste.
Protect your environment with Kodem.