highCVE-2026-58436Jul 21, 2026

CVE-2026-58436: Gitea Locale Middleware ReDoS via Accept-Language Underscore Bypass

Pranav Khune
Penetration Testing Team Lead, SecureLayer7

Any unauthenticated attacker can send a single oversized Accept-Language header full of underscore separators to pin a Gitea server CPU for roughly two seconds per request, making a small flood enough

Packagecode.gitea.io/gitea
Ecosystemgo
Affected< 1.27.0
Fixed in1.27.0

The problem

Gitea's Locale middleware runs before every HTTP request, including unauthenticated ones, and passes the raw Accept-Language header straight into `golang.org/x/text/language.ParseAcceptLanguage` without any size or character filter.

The CVE-2022-32149 guard inside that parser caps inputs containing more than 1000 `-` (hyphen) characters, but the parser's internal scanner also treats `_` (underscore) as an alias for `-` before parsing. A header built entirely from underscore separators contains zero hyphens, passes the guard, and then triggers the O(N^2) `gobble` path.

Go's default `MaxHeaderBytes` is 1 MiB, so an attacker can deliver up to one megabyte of malicious input per request. On a real server this burns roughly two seconds of CPU per request, and ten concurrent clients can saturate a ten-core box indefinitely.

Proof of concept

A working proof-of-concept for CVE-2026-58436 in code.gitea.io/gitea, with the exact payload below.

bash
# Single request, times ~2 s on a standard server (tested against gitea/gitea:1.22.6)
# Build a 1 MiB Accept-Language value using _ separators only (zero - chars, bypasses CVE-2022-32149 guard)
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://<gitea-host>:3000/

Each 9-character `_abcdefghi` token exceeds the BCP 47 parser's 8-character tag-length limit, so the scanner calls `gobble` to discard it. `gobble` shifts the entire remaining buffer with `runtime.memmove`, giving O(N^2) total bytes moved for N tokens. A 1 MiB header holds roughly 104,857 such tokens, and the quadratic work amounts to several gigabytes of memory moves inside a single goroutine.

The existing CVE-2022-32149 guard counts only `-` characters and short-circuits at 1000. Because the attack payload uses `_` exclusively, the guard sees zero hyphens, never fires, and allows the full O(N^2) scan to complete. The root cause is CWE-1333 (Inefficient Regular Expression Complexity) combined with an incomplete guard that missed the `_` alias.

The patch in PR #38323 (commit `f452c369`) adds a pre-call check in `modules/web/middleware/locale.go` that sums `strings.Count(al, "-") + strings.Count(al, "_")` and clears the header value if the total exceeds 32, making the quadratic path unreachable for attacker-supplied input.

The fix

Upgrade to Gitea 1.27.0, which includes PR #38323 (commit `f452c369acc9f1bd05ec6ef9c2e4399062dd6da1`). The patch adds a combined hyphen-plus-underscore separator count in `modules/web/middleware/locale.go` before calling `ParseAcceptLanguage`; if the count exceeds 32 the header is discarded.

No legitimate browser sends more than ~10 separators in that header, so the cap has no practical impact on real users. There is no workaround short of placing a WAF or reverse proxy in front of Gitea that enforces a strict byte limit on the Accept-Language header (e.g. reject any value longer than 256 bytes).

Reported by tonghuaroot.

References: [1][2][3][4]

Related research