Listen to this Post
The vulnerability is a regression in Caddy’s `forward_auth` directive introduced by PR 6608 in November 2024. The `copy_headers` subdirective is designed to copy headers from an authentication service’s response to the request forwarded to the backend. Prior to PR 6608, even if the auth service did not return a header, Caddy would overwrite any client-supplied version of that header with an empty placeholder, effectively deleting it. The PR added a `MatchNot` guard to the route that handles header copying. This guard checks if the placeholder for the auth service’s response header is empty; if so, it skips the `Set` operation entirely. Consequently, for any header listed in `copy_headers` that the auth service does not return, the `Set` operation is bypassed and the original client-supplied header is never removed or overwritten. An attacker with a valid authentication token can therefore inject arbitrary values for these identity headers (e.g., X-User-Id: admin). When the auth service validates the token and returns a `200 OK` without those specific headers, Caddy forwards the request to the backend complete with the attacker’s forged headers, leading to privilege escalation. All stable releases from v2.10.0 to the current v2.11.1 are affected. The fix involves adding an unconditional `Delete` route for each copied header that runs before the conditional Set, ensuring client-supplied values are always removed first.
Platform: Caddy
Version: v2.10.0 – v2.11.1
Vulnerability : Header injection / Privilege Escalation
Severity: High (8.1)
date: March 6, 2026
Prediction: Patch expected Q2 2026
What Undercode Say:
Analytics
The vulnerability is a high-severity regression (CVSS 8.1) introduced in a commit (PR 6608) intended to fix a user experience issue. It affects all users of the `forward_auth` directive with `copy_headers` whose authentication service does not return every copied header. This is a common pattern in stateless JWT validators. The attack vector is network-based, requires low complexity, and requires only a low-privilege valid authentication token. The impact is high for both confidentiality and integrity, allowing an attacker to assume any identity (e.g., admin) the backend trusts via those headers.
Exploit:
The exploit requires a valid authentication token. The attacker sends a request to the Caddy server with the `Authorization` header and forged identity headers (e.g., X-User-Id: admin).
Attack command curl -v http://127.0.0.1:8080/ \ -H "Authorization: Bearer valid_but_non_privileged_token" \ -H "X-User-Id: admin" \ -H "X-User-Role: superadmin"
If the auth service at `127.0.0.1:9091` returns a `200 OK` but does not include `X-User-Id` or `X-User-Role` in its response, Caddy’s flawed logic will forward the request to the backend (127.0.0.1:9092) with the attacker’s headers intact, tricking the backend into granting elevated privileges.
Protection from this CVE
- Immediate Mitigation: If you cannot immediately patch, manually delete the client-supplied headers in your Caddy configuration before they reach the `forward_auth` directive. You can add a `request_header` directive to delete the sensitive headers.
:8080 { request_header { X-User-Id "" X-User-Role "" } forward_auth 127.0.0.1:9091 { uri / copy_headers X-User-Id X-User-Role } reverse_proxy 127.0.0.1:9092 } - Patch: Apply the official fix from PR 7545. The patch adds an unconditional delete operation for each header in `copy_headers` before the conditional set operation.
// Patch from caddyserver/caddy7545 applied to modules/caddyhttp/reverseproxy/forwardauth/caddyfile.go for _, from := range sortedHeadersToCopy { to := http.CanonicalHeaderKey(headersToCopy[bash]) placeholderName := "http.reverse_proxy.header." + http.CanonicalHeaderKey(from) // Security fix: unconditionally delete the client-supplied header copyHeaderRoutes = append(copyHeaderRoutes, caddyhttp.Route{ HandlersRaw: []json.RawMessage{ caddyconfig.JSONModuleObject( &headers.Handler{ Request: &headers.HeaderOps{ Delete: []string{to}, }, }, "handler", "headers", nil, ), }, }) // Existing conditional set operation (now safe) handler := &headers.Handler{ Request: &headers.HeaderOps{ Set: http.Header{ to: []string{"{" + placeholderName + "}"}, }, }, } // ... rest of the route creation with MatchNot guard } - Update: Upgrade to a patched version of Caddy as soon as it is released (expected in v2.11.2 or later).
Impact
The impact is privilege escalation within an application. An attacker with any valid user account can impersonate any other user, including administrators, by injecting the corresponding headers. This bypasses the application’s primary access control mechanism, leading to unauthorized data access, modification, and full compromise of the application’s data and functionality. All deployments using `forward_auth` with `copy_headers` and an auth service that doesn’t return all copied headers are vulnerable.
🎯Let’s Practice Exploiting & Learn Patching For Free:
Sources:
Reported By: github.com
Extra Source Hub:
Undercode

