highJul 14, 2026

Ech0: Accept-Language `_` Separator Bypass Causes ~70x CPU Amplification DoS

Pranav Khune
Penetration Testing Team Lead, SecureLayer7

Ech0's i18n middleware passes the raw Accept-Language HTTP header directly into a Go language parser on every request, and a 1 MB header full of underscore separators bypasses an existing DoS guard, b

Packagegithub.com/lin-snow/ech0
Ecosystemgo
Affected< 5.0.1

The problem

Ech0's `i18n.Middleware` reads the full `Accept-Language` header and forwards it, unsanitized, to `goi18n.NewLocalizer`, which calls `golang.org/x/text/language.ParseAcceptLanguage` on every single HTTP request, including unauthenticated endpoints.

The BCP 47 tag parser has O(N²) time complexity. CVE-2022-32149 added a guard that counts `-` characters and rejects inputs with more than 1000. That guard does not count `_` even though the parser's internal scanner treats `_` identically to `-` before parsing.

A 1 MB header containing only `_`-separated tokens has zero `-` characters, clears the guard, and drives the quadratic code path. Go's default `MaxHeaderBytes` is 1 MiB, so an attacker can send the maximum allowed input with no special access.

With the `X-Locale: en` header also set, Ech0 calls `ParseAcceptLanguage` a second time on the combined explicit-locale + Accept-Language value, pushing per-request CPU time to roughly 7.9 seconds on the same host.

Proof of concept

A working proof-of-concept for this issue in github.com/lin-snow/ech0, with the exact payload below.

bash
# Single request: ~1.5 s CPU per request (default path)
PAYLOAD="en$(python3 -c 'print("_abcdefghi" * 100000, end="")')"
curl -sS -o /dev/null \
  -w 'http=%{http_code} t=%{time_total}\n' \
  -H "Accept-Language: ${PAYLOAD}" \
  http://TARGET:6277/

# Amplified path: ~7.9 s CPU per request (add X-Locale to trigger second parse)
curl -sS -o /dev/null \
  -w 'http=%{http_code} t=%{time_total}\n' \
  -H "Accept-Language: ${PAYLOAD}" \
  -H "X-Locale: en" \
  http://TARGET:6277/

Each `_abcdefghi` token is 10 characters long, which exceeds the scanner's 8-character tag-length limit. The scanner calls an internal `gobble` routine that `memmove`s the entire remaining buffer past the invalid token. With N such tokens the total bytes moved is O(N²), producing the observed ~1.5 s wall time for a 1 MiB payload.

The CVE-2022-32149 fix in `golang.org/x/text` v0.3.8 only counts `-` characters in its complexity guard, completely missing `_` even though `_` is aliased to `-` before parsing begins.

The patch adds a `sanitizeAcceptLanguage` helper at the middleware boundary that counts both `-` and `_` separators and returns an empty string when the combined total exceeds 32, a ceiling no real browser ever reaches. The same sanitizer must be applied to the `HeaderLocale` path and the `user.go` call sites that also forward user input into `ResolveLocale`.

Root cause: CWE-400 (Uncontrolled Resource Consumption) and CWE-772 (Missing Release of Resource), combined with an incomplete upstream guard that normalizes `_` to `-` only after the complexity check.

The fix

Upgrade to `github.com/lin-snow/Ech0` v5.0.1 or later, which adds a `sanitizeAcceptLanguage` function that drops any `Accept-Language` value containing more than 32 combined `-` and `_` separators before the value reaches the parser. If you cannot upgrade immediately, add an equivalent filter at your reverse proxy or WAF to reject `Accept-Language` headers longer than a few hundred bytes.

Reported by tonghuaroot.

References: [1][2]

Related research