From 06cdff074f61a7c9082d86ffb67c442951bfb210 Mon Sep 17 00:00:00 2001 From: DINESH YEDDALA Date: Thu, 15 Oct 2026 05:28:14 +0000 Subject: [PATCH] templated/CVE-2026-48020 --- traefik/CVE-2026-48020/README.md | 73 +++++++++++++++++++++++ traefik/CVE-2026-48020/backend.py | 28 +++++++++ traefik/CVE-2026-48020/docker-compose.yml | 36 +++++++++++ traefik/CVE-2026-48020/dynamic.yml | 33 ++++++++++ 4 files changed, 170 insertions(+) create mode 100644 traefik/CVE-2026-48020/README.md create mode 100644 traefik/CVE-2026-48020/backend.py create mode 100644 traefik/CVE-2026-48020/docker-compose.yml create mode 100644 traefik/CVE-2026-48020/dynamic.yml diff --git a/traefik/CVE-2026-48020/README.md b/traefik/CVE-2026-48020/README.md new file mode 100644 index 00000000..c70e0555 --- /dev/null +++ b/traefik/CVE-2026-48020/README.md @@ -0,0 +1,73 @@ +# Traefik - StripPrefix Middleware Authentication Bypass (CVE-2026-48020) + +## Overview + +Traefik versions prior to `2.11.48`, `3.6.19`, and `3.7.3` contain a path normalization flaw in the `StripPrefix` middleware. When a public router using `StripPrefix` shares a backend with protected routes (such as `/admin` or `/internal` guarded by authentication middleware), improper handling of relative path segments after prefix stripping can allow requests matched by the public router to resolve to protected backend paths without passing through the protected router's authentication middleware. + +- **Vulnerability Type**: Authentication Bypass / Path Normalization Mismatch in `StripPrefix` Middleware +- **Severity**: Critical +- **Vulnerable Versions**: `< 2.11.48`, `3.0.0 - 3.6.18`, `3.7.0 - 3.7.2` (e.g., `v3.7.0`) +- **Patched Versions**: `2.11.48`, `3.6.19`, `3.7.3` or later + +--- + +## Testbed Setup + +The testbed uses Docker Compose to deploy two Traefik reverse proxy instances fronting a shared Python HTTP backend (`cve_backend` on port `9000`): +- **Vulnerable instance (`traefik_vulnerable_lab`)**: `traefik:v3.7.0` running on host port `18080`. +- **Patched / Safe instance (`traefik_patched_lab`)**: `traefik:v3.7.3` running on host port `18081`. +- **Backend service (`cve_backend`)**: Python `3.12-slim` application ([`backend.py`](./backend.py)) configured via [`dynamic.yml`](./dynamic.yml) with: + - A `public` router matching `PathPrefix(\`/api\`)` with the `strip-api` (`StripPrefix: /api`) middleware. + - A `protected` router matching `PathPrefix(\`/admin\`) || PathPrefix(\`/internal\`)` guarded by `BasicAuth` (`auth`) middleware. + +### Start the Testbed + +```bash +cd /usr/local/google/home/dyeddala/tsunami/security-testbeds/traefik/CVE-2026-48020 +docker compose up -d +``` + +### Stop the Testbed + +```bash +docker compose down -v +``` + +--- + +## Verification + +### 1. Verify Endpoint Readiness + +#### Step 1: Verify the Traefik instances and public `/api` route are active +```bash +curl -i -s "http://127.0.0.1:18080/api" | head -n 15 +curl -i -s "http://127.0.0.1:18081/api" | head -n 15 +``` +**Expected Response**: `HTTP/1.1 404 Not Found` with JSON body `{"status": "not_found", "seen_path": "/"}` from the upstream Python backend, confirming `/api` was stripped and forwarded to `backend:9000`. + +#### Step 2: Verify direct access to `/admin` requires authentication on both instances +```bash +curl -i -s "http://127.0.0.1:18080/admin" | head -n 15 +curl -i -s "http://127.0.0.1:18081/admin" | head -n 15 +``` +**Expected Response**: `HTTP/1.1 401 Unauthorized` (with `Www-Authenticate: Basic realm="traefik"`), confirming the `protected` router's `BasicAuth` middleware blocks unauthenticated direct access. + +--- + +### 2. Automated Side-by-Side Verification with Tsunami + +```bash +for port in 18080 18081; do + echo "=== Scanning Port $port ===" + docker run --rm --net=host \ + --entrypoint /usr/bin/tsunami \ + -v /tmp:/tmp \ + ghcr.io/google/tsunami-scanner-full \ + --ip-v4-target=127.0.0.1 \ + --detectors-include=Traefik_CVE_2026_48020 \ + --port-ranges-target="${port}" \ + --scan-results-local-output-format=JSON \ + --scan-results-local-output-filename="/tmp/tsunami-out-${port}.json" 2>&1 | grep "# of detected vulnerability:" +done +`` diff --git a/traefik/CVE-2026-48020/backend.py b/traefik/CVE-2026-48020/backend.py new file mode 100644 index 00000000..c5a83329 --- /dev/null +++ b/traefik/CVE-2026-48020/backend.py @@ -0,0 +1,28 @@ +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.end_headers() + self.wfile.write(body) + + def do_GET(self): + print(f"DEBUG: Request line: {self.requestline}, path: {self.path}") + if "/admin" in self.path or "/internal" in self.path: + self._json(200, { + "status": "success", + "seen_path": self.path, + "message": "Reached endpoint successfully" + }) + else: + self._json(404, {"status": "not_found", "seen_path": self.path}) + +if __name__ == "__main__": + server = HTTPServer(("0.0.0.0", 9000), Handler) + server.serve_forever() diff --git a/traefik/CVE-2026-48020/docker-compose.yml b/traefik/CVE-2026-48020/docker-compose.yml new file mode 100644 index 00000000..104f11a6 --- /dev/null +++ b/traefik/CVE-2026-48020/docker-compose.yml @@ -0,0 +1,36 @@ +services: + traefik_vulnerable: + image: traefik:v3.7.0 + container_name: traefik_vulnerable_lab + command: + - "--providers.file.filename=/etc/traefik/dynamic.yml" + - "--entrypoints.web.address=:8080" + ports: + - "18080:8080" + volumes: + - ./dynamic.yml:/etc/traefik/dynamic.yml:ro + depends_on: + - backend + + traefik_patched: + image: traefik:v3.7.3 + container_name: traefik_patched_lab + command: + - "--providers.file.filename=/etc/traefik/dynamic.yml" + - "--entrypoints.web.address=:8080" + ports: + - "18081:8080" + volumes: + - ./dynamic.yml:/etc/traefik/dynamic.yml:ro + depends_on: + - backend + + backend: + image: python:3.12-slim + container_name: cve_backend + working_dir: /app + environment: + - PYTHONUNBUFFERED=1 + command: python /app/backend.py + volumes: + - ./backend.py:/app/backend.py:ro diff --git a/traefik/CVE-2026-48020/dynamic.yml b/traefik/CVE-2026-48020/dynamic.yml new file mode 100644 index 00000000..76cc9bf2 --- /dev/null +++ b/traefik/CVE-2026-48020/dynamic.yml @@ -0,0 +1,33 @@ +http: + routers: + public: + rule: "PathPrefix(`/api`)" + 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:{PLAIN}test" + + services: + backend: + loadBalancer: + servers: + - url: "http://backend:9000"