GHSA-6384-m2mw-rf54: Traefik's ForwardAuth trustForwardHeader=false allows spoofed X-Forwarded-Prefix to bypass authentication
SummaryThere is a high-severity authentication bypass vulnerability in Traefik's `ForwardAuth` middleware when `trustForwardHeader=false` is configured and Traefik is deployed behind a trusted upstream proxy.While `X-Forwarded-*` headers (such as `X-Forwarded-For`, `X-Forwarded-Host`, and `X-Forwarded-Proto`) from trusted context are correctly rebuilt, it does not strip or rebuild `X-Forwarded-Prefix`, leaving any attacker-supplied value intact in the subrequest forwarded to the authentication service.When the authentication service makes authorization decisions based on `X-Forwarded-Prefix`, an external attacker can spoof a trusted prefix value and gain unauthorized access to protected backend routes.Patcheshttps://github.com/traefik/traefik/releases/tag/v2.11.43https://github.com/traefik/traefik/releases/tag/v3.6.14https://github.com/traefik/traefik/releases/tag/v3.7.0-rc.2For more informationIf there are any questions or comments about this advisory, please [open an issue](https://github.com/traefik/traefik/issues).Summary`ForwardAuth` with `trustForwardHeader=false` still forwards an attacker-controlled `X-Forwarded-Prefix` header to the authentication service when Traefik is deployed behind a trusted upstream proxy. If the auth service relies on `X-Forwarded-Prefix` for authorization or routing decisions, an external attacker can bypass access controls and reach protected backend routes.This was validated this against Traefik `v3.6.12` using the official Docker image and a minimal local Docker setup. A direct request to Traefik is correctly rejected, but the same request succeeds when sent through a trusted reverse proxy, which shows the issue is in the `ForwardAuth` subrequest handling rather than general ingress header stripping.DetailsThe vulnerable behavior comes from the way Traefik builds the subrequest sent to the forward-auth server.In [`pkg/middlewares/auth/forward.go`](pkg/middlewares/auth/forward.go), `writeHeader` first copies all incoming request headers into the auth subrequest:It then selectively rebuilds only a subset of forwarded headers when `trustForwardHeader=false`, for example:`X-Forwarded-For``X-Forwarded-Method``X-Forwarded-Proto``X-Forwarded-Port``X-Forwarded-Host``X-Forwarded-Uri`However, it does **not** remove or rebuild `X-Forwarded-Prefix`, so an attacker-supplied value remains in the auth request even when forwarded headers are supposed to be untrusted.This becomes security-relevant when `StripPrefix` is used before `ForwardAuth`. In [`pkg/middlewares/stripprefix/strip_prefix.go`](pkg/middlewares/stripprefix/strip_prefix.go), Traefik appends the stripped prefix using `Header.Add`:If the attacker already sent `X-Forwarded-Prefix: /admin`, and `StripPrefix` later adds `/forbidden`, the auth service receives both values in this order:`/admin` (attacker-controlled)`/forbidden` (Traefik-generated)An auth service that uses the first `X-Forwarded-Prefix` value can therefore be tricked into authorizing a protected route.Why this appears unintended:The docs say `trustForwardHeader` means "Trust all X-Forwarded-* headers" and defaults to `false`.The migration notes say `X-Forwarded-Prefix` is handled like other `X-Forwarded-*` headers and removed from untrusted sources.The direct-to-Traefik test case behaves consistently with that expectation and returns `403`.Only the auth subrequest path still honors the spoofed `X-Forwarded-Prefix`.Relevant source/documentation locations:`pkg/middlewares/auth/forward.go` lines 393-459`pkg/middlewares/stripprefix/strip_prefix.go` lines 65-68`pkg/middlewares/forwardedheaders/forwarded_header.go` lines 15-43`docs/content/reference/routing-configuration/http/middlewares/forwardauth.md` lines 59-62 and 130-140`docs/content/migrate/v3.md` lines 192-196This was only tested and validated with `X-Forwarded-Prefix`. By source review, other forwarded headers that are copied but not rebuilt in `writeHeader` may deserve separate review, but I am not claiming impact for them here.PoCThe following uses the official `traefik:v3.6.12` Docker image and a mounted `traefik.toml`, matching the documented deployment style.Create `traefik.toml`:Create `dynamic.toml`:Create `auth.py`:Create `frontend.conf`:Start the containers:Send three requests:Direct to Traefik, spoofed header:Expected result:Through trusted proxy, no spoofing:Expected result:Through trusted proxy, spoofed header:Observed result:The backend response confirms that the request reached the protected upstream after the auth service accepted the attacker-controlled prefix.Optional log confirmation from the auth service:Observed log sequence:Cleanup:ImpactThis is an authentication bypass / trust-boundary bypass.Affected deployments are those that:run Traefik behind a trusted upstream proxyuse `ForwardAuth`rely on `trustForwardHeader=false` to avoid trusting client-supplied forwarded headerspass `X-Forwarded-Prefix` to the auth service, which happens by default when `authRequestHeaders` is emptymake authorization or routing decisions based on `X-Forwarded-Prefix`, especially when `StripPrefix` runs before `ForwardAuth`
In those environments, an unauthenticated external attacker can influence the auth service's view of the protected path and gain access to backend routes that should be denied.
CVSS v4.0: 7.8
CVSS v3.1: 10