Канал о прекрасном и не очень, вокруг кода, искуственного интеллекта, и их безопасности.
Навигация по каналу: https://t.me/art_code_ai/105
Post #95
512
🔥 BadHost (CVE-2026-48710 ): один символ в Host-заголовке == поломанная авторизация в FastAPI, LiteLLM, vLLM и ещё тысячах проектов
Ребята из X41 D-Sec в рамках аудита, спонсированного OSTIF, нашли уязвимость, которая по своему масштабу и элегантности вполне может претендовать на звание «баг года» в Python-экосистеме. Starlette — это то, на чём стоит, на минуточку, FastAPI. А на FastAPI, в свою очередь, стоит половина инфраструктуры, связанной с LLM: vLLM, LiteLLM, бесчисленные MCP-серверы, AI-агенты и внутренние сервисы. 32 000+ зависимых пакетов только на PyPI 😱
1️⃣ Суть бага
Starlette реконструирует
Проще говоря: если использовать
2️⃣ Атака
Вот как это выглядит на практике. Допустим, есть middleware, проверяющий
Атакующий отправляет:
Что происходит? Starlette склеивает
И, в результате, 403 превращается в 200 🙌
3️⃣ Это критичнее, чем кажется
Проблема не ограничивается
4️⃣ Фикс
В Starlette 1.0.1 добавлена валидация Host-заголовка по грамматике в соответствии с упомянутыми выше RFC:
Тривиальный патч в две (по сути) строки, на тривиальный баг, который жил в кодовой базе годами.
❗️Что делать прямо сейчас:
— Обновить Starlette до >= 1.0.1 (или FastAPI до версии, которая тянет исправленный Starlette);
— Если обновление невозможно немедленно — заменить все обращения к
— Проверить, что reverse прокси, за которым развернуто приложение, отбрасывает запросы с невалидным Host до того, как они доберутся до приложения. Многие делают это по умолчанию, но далеко не все конфигурации это гарантируют.
⚠ TL;DR: CVE-2026-48710 — байпасс авторизации в Starlette через невалидированный Host-заголовок. Затронуты все версии < 1.0.1. Обновляйтесь или используйте
#уязвимости
Ребята из X41 D-Sec в рамках аудита, спонсированного OSTIF, нашли уязвимость, которая по своему масштабу и элегантности вполне может претендовать на звание «баг года» в Python-экосистеме. Starlette — это то, на чём стоит, на минуточку, FastAPI. А на FastAPI, в свою очередь, стоит половина инфраструктуры, связанной с LLM: vLLM, LiteLLM, бесчисленные MCP-серверы, AI-агенты и внутренние сервисы. 32 000+ зависимых пакетов только на PyPI 😱
1️⃣ Суть бага
Starlette реконструирует
request.url, склеивая значение HTTP-заголовка Host с путём запроса. Host при этом никак не валидируется на соответствие RFC 9112 §3.2 и RFC 3986 §3.2.2. А это значит, что символы-разделители URI (/, ?, #) в Host-заголовке при пересборке URL смещают синтаксические границы компонентов.Проще говоря: если использовать
? в Host, то всё, что идёт после него — включая реальный путь — превращается в строку запроса с точки зрения пересобранного URL. И request.url.path начинает вводить в заблуждение.2️⃣ Атака
Вот как это выглядит на практике. Допустим, есть middleware, проверяющий
request.url.path:@app.middleware("http")
async def auth_middleware(request: Request, call_next):
if request.url.path.startswith("/admin"):
if not is_authenticated(request):
return JSONResponse(status_code=403)
return await call_next(request)Атакующий отправляет:
curl -i -H 'Host: foo?' http://target/admin
Что происходит? Starlette склеивает
foo? + /admin и получает URL http://foo?/admin. При парсинге этого URL, /admin уходит в query string. request.url.path становится пустым или /. Middleware видит не /admin, а ерунду — и пропускает запрос. Роутер же работает по request.scope["path"] (который берётся напрямую из ASGI scope, не из Host) — и честно маршрутизирует на /admin.И, в результате, 403 превращается в 200 🙌
3️⃣ Это критичнее, чем кажется
Проблема не ограничивается
?. Символ / в Host позволяет подменить весь компонент пути. Символ # — отрезает путь как фрагмент. Это позволяет обходить практически любые проверки, основанные на пути к эндпоинту. Любой middleware или зависимость, использующая request.url.path для принятия security-решений, уязвима. А таких — тысячи. Особенно в экосистеме AI/ML, где авторизация на inference-эндпоинтах часто реализована именно через path-based middleware.4️⃣ Фикс
В Starlette 1.0.1 добавлена валидация Host-заголовка по грамматике в соответствии с упомянутыми выше RFC:
_HOST_RE = re.compile(r"^([a-z0-9.-]+|\[[a-f0-9]*:[a-f0-9.:]+\])(?::[0-9]+)?$", re.IGNORECASE)
# ...
if host_header is not None and _HOST_RE.fullmatch(host_header):
Тривиальный патч в две (по сути) строки, на тривиальный баг, который жил в кодовой базе годами.
❗️Что делать прямо сейчас:
— Обновить Starlette до >= 1.0.1 (или FastAPI до версии, которая тянет исправленный Starlette);
— Если обновление невозможно немедленно — заменить все обращения к
request.url.path на request.scope["path"];— Проверить, что reverse прокси, за которым развернуто приложение, отбрасывает запросы с невалидным Host до того, как они доберутся до приложения. Многие делают это по умолчанию, но далеко не все конфигурации это гарантируют.
⚠ TL;DR: CVE-2026-48710 — байпасс авторизации в Starlette через невалидированный Host-заголовок. Затронуты все версии < 1.0.1. Обновляйтесь или используйте
request.scope["path"] вместо request.url.path, или блокируйте некорректные по RfC Host'ы на reverse-прокси.#уязвимости
- 🔥 5
- 👍 4
- ❤ 3
- ✍ 1
