KEVWire

Security intelligence, with context.

Response snapshot Oct 29, 2026, 02:31 UTC

Back to the wire
Permalink
VulnerabilityCVE-2026-118651

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.

At a glance

Exploited
Not known
Severity
5.3 Medium (CVSS 3.1)
Chance of exploitation
Not estimated
Sources
1 source

What to do

GitHub lists first patched versions: github.com/labstack/echo/v5: 5.4.0. Confirm the affected range and upgrade guidance in the exact advisory.

Read GitHub-reviewed advisory GHSA-99jh-6h7p-pp36 (opens a new tab)

What happened

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)

Continue reading

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.

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

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 pro

Affected package ranges reported by GitHub: github.com/labstack/echo/v5: < 5.4.0 github.com/labstack/echo/v4: >= 4.0.0, <= 4.16.2

Scores and how they were measured

CVSS base severity

5.3

Version 3.1As of

CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:N/I:L/A:N

Measurement source (opens a new tab)

How bad it is if exploited. Not a prediction of exploitation or proof you are exposed.

EPSS probability

Not available

A model's estimate of exploitation in the next 30 days, not certainty or your own risk.

Related technology

Sources

  1. Primary source

    GitHub-reviewed advisory GHSA-99jh-6h7p-pp36 (opens a new tab)

    The public advisory supplies the description, affected package/version ranges, and any listed CVSS metric. GitHub review is not local review.

    Published Checked

  2. Primary source

    GitHub public advisory API page (opens a new tab)

    The API page supplied GHSA-99jh-6h7p-pp36, its actual identifiers, source dates, and structured metrics.

    Published Date not suppliedChecked

Cite this record

For factual claims, cite the primary sources above; cite KEVWire for its compilation, dates, and classifications.

APA

KEVWire. (2026, October 28). Echo: Reverse Proxy Forwards Attacker-Supplied X-Real-IP to Upstream by Default (CVE-2026-118651). https://kevwire.com/record/CVE-2026-118651

MLA

"Echo: Reverse Proxy Forwards Attacker-Supplied X-Real-IP to Upstream by Default (CVE-2026-118651)." KEVWire, 28 Oct. 2026, kevwire.com/record/CVE-2026-118651.

Chicago

KEVWire. "Echo: Reverse Proxy Forwards Attacker-Supplied X-Real-IP to Upstream by Default (CVE-2026-118651)." Last modified October 28, 2026. https://kevwire.com/record/CVE-2026-118651.

Evidence summary (plain text)

Echo: Reverse Proxy Forwards Attacker-Supplied X-Real-IP to Upstream by Default (Oct 28, 2026).
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.
CVSS 5.3 (version 3.1; evidence as of Oct 28, 2026; https://github.com/advisories/GHSA-99jh-6h7p-pp36).
Sources:
GitHub-reviewed advisory GHSA-99jh-6h7p-pp36: https://github.com/advisories/GHSA-99jh-6h7p-pp36
GitHub public advisory API page: https://api.github.com/advisories?type=reviewed&per_page=100&sort=updated&direction=desc
Record ID: CVE-2026-118651.
Record origin: Ingested record.
AI evidence: Not assessed.
Known exploitation: Not assessed.
Zero-day status: Not assessed. KEV membership alone does not establish zero-day status.
About this data

This record was ingested from a data source. Retrieved: Oct 29, 2026, 02:00 UTC. Ingestion is not an independent editorial investigation.

A CISA KEV listing documents known exploitation, not necessarily a zero-day; absence from the catalog does not prove a vulnerability has never been exploited.

Technology tags indicate possible relevance, not verified exposure. Confirm the affected product, version, and configuration against your own inventory; KEVWire does not scan your systems.

A personal reading checklist on this device, not verified remediation.

Record JSON

Your account

Email alerts

Email alerts are coming as part of KEVWire Premium: join the waitlist. To follow your stack today, use its RSS feed, which honors your filters, or connect an AI agent through the API.

Suggest an improvement

Ideas, missing sources, corrections, and bugs are all welcome. No account needed. Suggestions are reviewed by a person before anything changes.

Record details

Record detail