Skip to content
Medium 5.3

Echo has Rate Limiter Bypass via Spoofed X-Forwarded-For Header in Default Configuration

CVE-2026-118653 First seen on SecPulse

Affected packages

EcosystemPackageAffectedFixed
labstack echo — —
go github.com/labstack/echo/v5
< 5.1.0
5.1.0
go github.com/labstack/echo/v4
4.0.0 – 4.16.2
—
Original description The description as published by the source.

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), IPExtractor left nil by 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, "…

Source: CVE.org (CVE Program・cvelistV5) NVD GitHub Advisory

References

We use cookies for analytics and ad measurement. This setting applies to all Game co., Ltd. sites. Details