Skip to main content
VulnerabilityPublished

CVE-2026-118656: V5 · Echo has Open Redirect via Control-Character Prefix Defeating sanitizeURI

Affects github.com/labstack/echo/v5Source GCVE Database

0
Authoritative record:CVE-2026-118656· Medium 6.1

Not known to be exploited · CVSS 6.1 Medium : upgrade to 5.4.0 when convenient.

Data as of 28 Oct 2026, 22:12 UTC

CVSS 3.1 base score
6.1Medium
How severe the flaw is on the 0–10 CVSS scale.
Patch
Available – fixed in 5.4.0, 4.16.0
CVSS:3.1/AV:N/AC:L/PR:N/UI:R/S:C/C:L/I:L/A:N

Exploitation evidence

  1. Disclosed: evidence found.

    CVE published 28 Oct 2026

  2. Detectable: no evidence on record.

    No public nuclei template indexed

  3. Weaponised: no evidence on record.

    No public exploit code indexed in Metasploit or Exploit-DB

  4. Exploited in the wild: no evidence on record.

    Not in CISA KEV and no source reports exploitation

  5. Observed by OffSeq Mirage: no evidence on record.

    No attack traffic recorded by OffSeq Mirage sensors

A missing step is not evidence of safety. Sources: cve.org (opens in a new tab), the Metasploit, Exploit-DB and nuclei-templates index as of 26 Oct 2026.

What to do

Upgrade github.com/labstack/echo/v5 to 5.4.0; github.com/labstack/echo/v4 to 4.16.0.

Affected software

ProductAffectedFixed in
Gogithub.com/labstack/echo/v5Affected
  • <5.4.0
Fixed in
  • 5.4.0
Gogithub.com/labstack/echo/v4Affected
  • <4.16.0
Fixed in
  • 4.16.0

Check your own hosts for these packages with threat-finder (opens in a new tab), our free open-source scanner.

Description

## Summary Echo's dedicated open-redirect guard, `sanitizeURI`, decides whether a redirect target is a local path by inspecting exactly two byte positions: `uri[0]` and `uri[1]`. If the value begins with a control character such as a tab or a newline, followed by a separator pair, the guard does not fire and the value is written to the `Location` header unchanged. Browsers remove tab and newline characters from URLs before parsing them, and treat a backslash as a forward slash for HTTP and HTTPS URLs. A `Location` value of ``/\t/attacker.example`` is therefore resolved by the browser as an absolute URL on `attacker.example`. Confirmed end to end in a headless browser. ## Technical background ### The guard `middleware/slash.go:144` (duplicated verbatim at `echo.go:958`): ```go func sanitizeURI(uri string) string { // double slash `\\`, `//` or even `\/` is absolute uri for browsers and by redirecting request to that uri // we are vulnerable to open redirect attack. so replace all slashes from the beginning with single slash if len(uri) > 1 && (uri[0] == '\\' || uri[0] == '/') && (uri[1] == '\\' || uri[1] == '/') { uri = "/" + strings.TrimLeft(uri, `/\`) } return uri } ``` The condition is anchored at absolute offsets 0 and 1. It cannot see a separator that has been shifted by one byte. ### Data flow 1. `RemoveTrailingSlash` (`middleware/slash.go:112-140`) reads `url.Path`, which `net/http` has already percent-decoded, strips the trailing slash, and appends the raw query string: ```go path := url.Path ... if l > 0 && strings.HasSuffix(path, "/") { path = path[:l] uri := path if qs != "" { uri += "?" + qs } if config.RedirectCode != 0 { return c.Redirect(config.RedirectCode, sanitizeURI(uri)) // line 132 } ``` 2. `AddTrailingSlash` does the same in the opposite direction at `middleware/slash.go:67`. 3. A request for `/%09/attacker.example/x/` has `url.Path` equal to `"/\t/attacker.example/x/"` (a literal TAB byte). `sanitizeURI` evaluates `uri[0] == '\t'`, the condition is false, and the value passes through untouched. 4. `Context.Redirect` (`context.go:735`) writes it to the `Location` header. Go's header writer removes CR and LF but leaves TAB, which is all that is required. ### Conditional analysis - Guard: `uri[0]` and `uri[1]` are separators. Trust boundary: server-side sanitization of attacker-influenced path data. - Bypass: place a single control byte before the separator pair. The separator moves to index 1 and the guard's second condition (`uri[1]` being a separator) is never reached because the first condition (`uri[0]` being a separator) already failed. - Browser-side trust boundary: the client's URL parser. It is adversary favourable: control characters are stripped and backslashes are normalized before scheme and authority parsing. The attacker does not need any server-side weakness beyond producing the malformed `Location`. Note that the newline variant does not survive: `net/http` replaces CR and LF in header values. TAB does survive, and TAB alone is sufficient because the browser strips it. ## Proof of concept **Attached PoC:** `ECHO-2026-002_poc.zip` (in this directory). Extract it and follow `ECHO-2026-002/README.md` for the full walkthrough. The archive contains a self-contained Go module (`go.mod`, `go.sum`, `main.go`, `README.md`). [ECHO-2026-002_poc.zip](https://github.com/user-attachments/files/32133975/ECHO-2026-002_poc.zip) Quick start: ```bash unzip ECHO-2026-002_poc.zip cd ECHO-2026-002 # edit go.mod: point the replace directive at your Echo checkout sed -i 's|=> .*|=> /absolute/path/to/echo|' go.mod GOFLAGS=-mod=mod go run . # in-process check GOFLAGS=-mod=mod go run . -serve # real listener on 127.0.0.1:18002 ``` ### Environment used for verification - OS: Linux 6.18.33.2-microsoft-standard-WSL2, x86_64 - Go: go1.27.0 linux/amd64 - Target: Echo v5.3.1 (module `github.com/labstack/echo/v5`) - Browser: headless Chromium drove the end-to-end confirmation ### The vulnerable configuration ```go package main import ( "net/http" "github.com/labstack/echo/v5" "github.com/labstack/echo/v5/middleware" ) func main() { e := echo.New() e.Pre(middleware.RemoveTrailingSlashWithConfig(middleware.RemoveTrailingSlashConfig{ RedirectCode: http.StatusMovedPermanently, })) e.GET("/", func(c *echo.Context) error { return c.String(200, "home") }) _ = e.Start("127.0.0.1:18002") } ``` *** ### Reproduction In-process check. The program requests five path shapes and applies the browser URL-parsing steps that matter (strip TAB, LF, CR; treat a backslash as a slash; a leading `//` starts an authority): ```bash GOFLAGS=-mod=mod go run . ``` ``` GET code Location (control chars shown) browser navigates to /normal/path/ 301 /normal/path (victim origin) control: ordinary path, sanitized correctly /%0

References

Community reviews

Loading reviews…