CVE-2026-118653
Echo: Rate Limiter Bypass via Spoofed X-Forwarded-For Header in Default Configuration
Echo is a Go web framework. From 4.0.0 until 5.1.0, Context.RealIP falls back to client-supplied X-Forwarded-For or X-Real-IP values when Echo.IPExtractor is unset, and the default RateLimiter uses that result as its client identifier. A remote attacker can rotate arbitrary forwarded-address values to obtain new rate-limit buckets and bypass IP-based throttling used for controls such as brute-force or request-volume protection. This issue is fixed in Echo v5.1.0.
Attack profile
How easy it is to exploit
- Attack vector
- Network
- Complexity
- Low
- Privileges needed
- None
- User interaction
- None
What an attacker can affect
- Confidentiality
- None
- Integrity
- Low
- Availability
- None
Exploit likelihood
What each source reports
CVE List Score used
5.3
Echo: Rate Limiter Bypass via Spoofed X-Forwarded-For Header in Default Configuration
Echo is a Go web framework. From 4.0.0 until 5.1.0, Context.RealIP falls back to client-supplied X-Forwarded-For or X-Real-IP values when Echo.IPExtractor is unset, and the default RateLimiter uses that result as its client identifier. A remote attacker can rotate arbitrary forwarded-address values to obtain new rate-limit buckets and bypass IP-based throttling used for controls such as brute-force or request-volume protection. This issue is fixed in Echo v5.1.0.
- Record
CVE-2026-118653- CVSS 3.1
- 5.3 Medium
CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:N/I:L/A:N - Weakness
- CWE-290
- Updated
- 2026-11-03
EUVDEUVD-2026-112409
5.3
Echo is a Go web framework. From 4.0.0 until 5.1.0, Context.RealIP falls back to client-supplied X-Forwarded-For or X-Real-IP values when Echo.IPExtractor is unset, and the default RateLimiter uses that result as its client identifier. A remote attacker can rotate arbitrary forwarded-address values to obtain new rate-limit buckets and bypass IP-based throttling used for controls such as brute-force or request-volume protection. This issue is fixed in Echo v5.1.0.
- Record
EUVD-2026-112409- CVSS 3.1
- 5.3 Medium
CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:N/I:L/A:N - Updated
- 2026-11-03
GitHub AdvisoriesGHSA-246p-cpwv-v3jq
5.3
Echo has Rate Limiter Bypass via Spoofed X-Forwarded-For Header in Default Configuration
Summary
When echo.New() is used without explicitly setting Echo#IPExtractor, the RealIP() method falls back to reading the client-supplied X-Forwarded-For (or X-Real-IP) header without any proxy-origin validation. The default RateLimiter() middleware derives its per-client identifier from ctx.RealIP(), so an attacker can rotate arbitrary values in the X-Forwarded-For header on each request to obtain a fresh rate-limit bucket and bypass IP-based throttling entirely.
Affected Component
context.go—(*Context).RealIP()(lines 186–208), legacy fallback branch (lines 190–200)middleware/rate_limiter.go—DefaultRateLimiterConfig.IdentifierExtractor(lines 46–58);RateLimiter()(lines 71–76)echo.go—New()(lines 326–343),IPExtractorleftnilby default
Description
RealIP() unconditionally trusts X-Forwarded-For when IPExtractor is unset
Context.RealIP() (context.go:186-208) first checks c.echo.IPExtractor. When this field is nil (the default for echo.New()), it falls through to legacy behavior and returns the first IP from the X-Forwarded-For header — the position an attacker has full control over:
// context.go:186-200
func (c *Context) RealIP() string {
if c.echo != nil && c.echo.IPExtractor != nil {
return c.echo.IPExtractor(c.request)
}
// Fall back to legacy behavior
if ip := c.request.Header.Get(HeaderXForwardedFor); ip != "" {
i := strings.IndexAny(ip, ",")
if i > 0 {
xffip := strings.TrimSpace(ip[:i])
xffip = strings.TrimPrefix(xffip, "[")
xffip = strings.TrimSuffix(xffip, "]")
return xffip // ← attacker-controlled first IP
}
return ip
}
if ip := c.request.Header.Get(HeaderXRealIP); ip != "" {
...
return ip // ← also attacker-controlled
}
ra, _, _ := net.SplitHostPort(c.request.RemoteAddr)
return ra
}
Rate limiter default identifier extractor calls RealIP() unconditionally
DefaultRateLimiterConfig (rate_limiter.go:46-58) sets the identifier extractor to ctx.RealIP() with no guard for whether a safe IPExtractor is configured:
// rate_limiter.go:46-58
var DefaultRateLimiterConfig = RateLimiterConfig{
Skipper: DefaultSkipper,
IdentifierExtractor: func(ctx *echo.Context) (string, error) {
id := ctx.RealIP() // ← reads attacker-controlled header
return id, nil
},
...
}
RateLimiter(store) (rate_limiter.go:71-76) copies DefaultRateLimiterConfig directly:
// rate_limiter.go:71-76
func RateLimiter(store RateLimiterStore) echo.MiddlewareFunc {
config := DefaultRateLimiterConfig
config.Store = store
return RateLimiterWithConfig(config)
}
The result is that the most common usage pattern — e.GET("/path", h, middleware.RateLimiter(store)) — produces a rate limiter whose per-client identifier is fully controlled by the requester.
Framework acknowledges the issue for IP extraction but the rate limiter carries no warning
ip.go:27 explicitly states the default is insecure:
"Note: if you don't set
Echo#IPExtractorexplicitly, Echo fallback to legacy behavior, which is not a good choice."
And ip.go:117-119:
"In default behavior, Echo sees all of first XFF header, X-Real-IP header and IP from network layer. As you might already notice, after reading this article, this is not good. Sole reason this is default is just backward compatibility."
The IPExtractor mechanism (ExtractIPDirect(), ExtractIPFromXFFHeader(), ExtractIPFromRealIPHeader()) in ip.go exists precisely to close this gap. However, the RateLimiter() function and DefaultRateLimiterConfig have no corresponding warning and make no attempt to detect or flag that IPExtractor is unset, leaving operators who follow the standard rate limiter example silently exposed.
The test…
- Record
GHSA-246p-cpwv-v3jq- CVSS 3.1
- 5.3 Medium
CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:N/I:L/A:N - Weakness
- CWE-290
- Updated
- 2026-11-02
NVD
5.3
Echo is a Go web framework. From 4.0.0 until 5.1.0, Context.RealIP falls back to client-supplied X-Forwarded-For or X-Real-IP values when Echo.IPExtractor is unset, and the default RateLimiter uses that result as its client identifier. A remote attacker can rotate arbitrary forwarded-address values to obtain new rate-limit buckets and bypass IP-based throttling used for controls such as brute-force or request-volume protection. This issue is fixed in Echo v5.1.0.
- Record
CVE-2026-118653- CVSS 3.1
- 5.3 Medium
CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:N/I:L/A:N - Weakness
- CWE-290
- Updated
- 2026-11-03
OSV
5.3
Echo: Rate Limiter Bypass via Spoofed X-Forwarded-For Header in Default Configuration
Echo is a Go web framework. From 4.0.0 until 5.1.0, Context.RealIP falls back to client-supplied X-Forwarded-For or X-Real-IP values when Echo.IPExtractor is unset, and the default RateLimiter uses that result as its client identifier. A remote attacker can rotate arbitrary forwarded-address values to obtain new rate-limit buckets and bypass IP-based throttling used for controls such as brute-force or request-volume protection. This issue is fixed in Echo v5.1.0.
- Record
CVE-2026-118653- CVSS 3.1
- 5.3 Medium
CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:N/I:L/A:N - Weakness
- CWE-290
- Updated
- 2026-11-06
OSVDEBIAN-CVE-2026-118653
5.3
Echo is a Go web framework. From 4.0.0 until 5.1.0, Context.RealIP falls back to client-supplied X-Forwarded-For or X-Real-IP values when Echo.IPExtractor is unset, and the default RateLimiter uses that result as its client identifier. A remote attacker can rotate arbitrary forwarded-address values to obtain new rate-limit buckets and bypass IP-based throttling used for controls such as brute-force or request-volume protection. This issue is fixed in Echo v5.1.0.
- Record
DEBIAN-CVE-2026-118653- CVSS 3.1
- 5.3 Medium
CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:N/I:L/A:N - Updated
- 2026-11-03
OSVGHSA-246p-cpwv-v3jq
5.3
Echo has Rate Limiter Bypass via Spoofed X-Forwarded-For Header in Default Configuration
Summary
When echo.New() is used without explicitly setting Echo#IPExtractor, the RealIP() method falls back to reading the client-supplied X-Forwarded-For (or X-Real-IP) header without any proxy-origin validation. The default RateLimiter() middleware derives its per-client identifier from ctx.RealIP(), so an attacker can rotate arbitrary values in the X-Forwarded-For header on each request to obtain a fresh rate-limit bucket and bypass IP-based throttling entirely.
Affected Component
context.go—(*Context).RealIP()(lines 186–208), legacy fallback branch (lines 190–200)middleware/rate_limiter.go—DefaultRateLimiterConfig.IdentifierExtractor(lines 46–58);RateLimiter()(lines 71–76)echo.go—New()(lines 326–343),IPExtractorleftnilby default
Description
RealIP() unconditionally trusts X-Forwarded-For when IPExtractor is unset
Context.RealIP() (context.go:186-208) first checks c.echo.IPExtractor. When this field is nil (the default for echo.New()), it falls through to legacy behavior and returns the first IP from the X-Forwarded-For header — the position an attacker has full control over:
// context.go:186-200
func (c *Context) RealIP() string {
if c.echo != nil && c.echo.IPExtractor != nil {
return c.echo.IPExtractor(c.request)
}
// Fall back to legacy behavior
if ip := c.request.Header.Get(HeaderXForwardedFor); ip != "" {
i := strings.IndexAny(ip, ",")
if i > 0 {
xffip := strings.TrimSpace(ip[:i])
xffip = strings.TrimPrefix(xffip, "[")
xffip = strings.TrimSuffix(xffip, "]")
return xffip // ← attacker-controlled first IP
}
return ip
}
if ip := c.request.Header.Get(HeaderXRealIP); ip != "" {
...
return ip // ← also attacker-controlled
}
ra, _, _ := net.SplitHostPort(c.request.RemoteAddr)
return ra
}
Rate limiter default identifier extractor calls RealIP() unconditionally
DefaultRateLimiterConfig (rate_limiter.go:46-58) sets the identifier extractor to ctx.RealIP() with no guard for whether a safe IPExtractor is configured:
// rate_limiter.go:46-58
var DefaultRateLimiterConfig = RateLimiterConfig{
Skipper: DefaultSkipper,
IdentifierExtractor: func(ctx *echo.Context) (string, error) {
id := ctx.RealIP() // ← reads attacker-controlled header
return id, nil
},
...
}
RateLimiter(store) (rate_limiter.go:71-76) copies DefaultRateLimiterConfig directly:
// rate_limiter.go:71-76
func RateLimiter(store RateLimiterStore) echo.MiddlewareFunc {
config := DefaultRateLimiterConfig
config.Store = store
return RateLimiterWithConfig(config)
}
The result is that the most common usage pattern — e.GET("/path", h, middleware.RateLimiter(store)) — produces a rate limiter whose per-client identifier is fully controlled by the requester.
Framework acknowledges the issue for IP extraction but the rate limiter carries no warning
ip.go:27 explicitly states the default is insecure:
"Note: if you don't set
Echo#IPExtractorexplicitly, Echo fallback to legacy behavior, which is not a good choice."
And ip.go:117-119:
"In default behavior, Echo sees all of first XFF header, X-Real-IP header and IP from network layer. As you might already notice, after reading this article, this is not good. Sole reason this is default is just backward compatibility."
The IPExtractor mechanism (ExtractIPDirect(), ExtractIPFromXFFHeader(), ExtractIPFromRealIPHeader()) in ip.go exists precisely to close this gap. However, the RateLimiter() function and DefaultRateLimiterConfig have no corresponding warning and make no attempt to detect or flag that IPExtractor is unset, leaving operators who follow the standard rate limiter example silently exposed.
The test…
- Record
GHSA-246p-cpwv-v3jq- CVSS 3.1
- 5.3 Medium
CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:N/I:L/A:N - Weakness
- CWE-290
- Updated
- 2026-11-06
OSVUBUNTU-CVE-2026-118653
5.3
Echo is a Go web framework. From 4.0.0 until 5.1.0, Context.RealIP falls back to client-supplied X-Forwarded-For or X-Real-IP values when Echo.IPExtractor is unset, and the default RateLimiter uses that result as its client identifier. A remote attacker can rotate arbitrary forwarded-address values to obtain new rate-limit buckets and bypass IP-based throttling used for controls such as brute-force or request-volume protection. This issue is fixed in Echo v5.1.0.
- Record
UBUNTU-CVE-2026-118653- CVSS 3.1
- 5.3 Medium
CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:N/I:L/A:N - Updated
- 2026-11-05
OSVGO-2026-6872 No score
Rate Limiter Bypass via Spoofed X-Forwarded-For Header in Default Configuration in github.com/labstack/echo
Rate Limiter Bypass via Spoofed X-Forwarded-For Header in Default Configuration in github.com/labstack/echo
- Record
GO-2026-6872- Updated
- 2026-11-06
Affected products
Affected packages
| Package | Affected | Fixed |
|---|---|---|
github.com/labstack/echo/v5Go |
before 5.1.0 | 5.1.0 |
github.com/labstack/echo/v4Go |
4.0.0 to 4.16.2 | None known |
golang-github-labstack-echoDebian:12 |
All versions, plus listed versions 4.11.1-14.11.1-24.12.0-14.2.1-3 |
None known |
golang-github-labstack-echoDebian:13 |
All versions, plus listed versions 4.12.0-1 |
None known |
golang-github-labstack-echoDebian:14 |
All versions, plus listed versions 4.12.0-1 |
None known |
github.com/labstack/echo/v4Go |
4.0.0 and later | None known |
golang-github-labstack-echo.v2Ubuntu:20.04:LTS |
All versions, plus listed versions 2.2.0-1 |
None known |
golang-github-labstack-echo.v3Ubuntu:20.04:LTS |
All versions, plus listed versions 3.3.5-1 |
None known |
golang-github-labstack-echoUbuntu:22.04:LTS |
All versions, plus listed versions 4.2.1-2 |
None known |
golang-github-labstack-echo.v2Ubuntu:22.04:LTS |
All versions, plus listed versions 2.2.0-2 |
None known |
Show 4 moreShow less
| Package | Affected | Fixed |
|---|---|---|
golang-github-labstack-echo.v3Ubuntu:22.04:LTS |
All versions, plus listed versions 3.3.10-1 |
None known |
golang-github-labstack-echoUbuntu:24.04:LTS |
All versions, plus listed versions 4.11.1-2 |
None known |
golang-github-labstack-echoUbuntu:26.10 |
All versions, plus listed versions 4.12.0-1 |
None known |
golang-github-labstack-echoUbuntu:26.04:LTS |
All versions, plus listed versions 4.12.0-1 |
None known |
History
| Recorded | Changed | Sources |
|---|---|---|
| affected, aliases | OSV | |
| facts | EUVD, OSV | |
| references | CVE List, NVD | |
| first indexed | CVE List, EUVD, GitHub Advisories, NVD, OSV |
Dates are when Every CVE recorded each change. Upstream dates may differ.
In short
CVE-2026-118653 is a medium vulnerability (CVSS 5.3) in Labstack Echo, published October 28, 2026. No source lists public exploit code. EPSS puts the chance of exploitation in the next 30 days at 0.41%, the 36th percentile. Fixed in github.com/labstack/echo/v5 5.1.0.
References
github.com/labstack/echo/security/advisories/GHSA-246p-cpwv-v3jq
github.com/labstack/echo/commit/22e4b71109ef072ef011a489066ec662ca9565b2
github.com/labstack/echo/releases/tag/v5.1.0
github.com/advisories/GHSA-246p-cpwv-v3jq
github.com/CVEProject/cvelistV5/tree/main/cves/2026/118xxx/CVE-2026-118653.json
nvd.nist.gov/vuln/detail/CVE-2026-118653
Left out here: the rest of 2 source descriptions. The JSON record has them.