flawopen.com/Teardowns/cve-2026-71416-headroom-websocket-origin-hijacking
CVE-2026-71416: перехват WebSocket Origin в Headroom
Headroom не проверял Origin WebSocket; посещённый сайт мог отправлять запросы LLM через прокси. Обновитесь до 0.35.0+.
Охранник пропускает всех к закрытой стойке, потому что не проверяет пропуск посетителя. Origin — это такой пропуск: прокси должен проверить его, прежде чем открыть канал с доступом к сохранённому API-ключу.
Ключевые понятия и термины
WebSocket- Долговременное двустороннее соединение, начинающееся с HTTP-запроса Upgrade и передающее сообщения в обе стороны.
Заголовок Origin- Схема, узел и порт страницы, которые браузер передаёт как источник запроса. При WebSocket-handshake сервер применяет собственную политику доверия; заголовки CORS эту проверку не заменяют.
CSWSH- Cross-Site WebSocket Hijacking: недоверенная страница открывает WebSocket к доступному сервису, который не проверяет источник соединения.
Неявно доступный секрет- Секрет, уже доступный процессу, например API-ключ в переменной окружения, которым можно аутентифицировать запрос без учётных данных клиента.
CWE-1385- Отсутствие проверки Origin в WebSockets: конечная точка не проверяет, разрешён ли источник связи.
Связи со стандартами
- Ссылка CWE для слабого места, описанного в этом руководстве: CWE-1385
- OWASP GenAI 2025 — LLM06:2025 Чрезмерные полномочия агента
Анализ первопричины
WebSocket-обработчик Headroom /v1/responses принимал соединение браузера до проверки Origin в handshake. Затем прокси мог добавить OPENAI_API_KEY из собственного окружения к upstream-запросу. Политика одного источника браузера сама по себе не запрещает JavaScript начать WebSocket-соединение с другим доступным узлом; обычно браузер передаёт Origin, и сервер должен отклонять источники вне своей политики. Поэтому для эксплуатации браузер жертвы должен иметь доступ к уязвимому прокси Headroom, а прокси должен располагать полезными учётными данными или интеграциями. В предупреждении сказано, что принятое соединение может отправлять произвольные запросы к LLM. Выполнение команд на хосте зависит от доступности настроенного инструмента, исполняющего команды; оно не возникает автоматически из-за отсутствия проверки заголовка. CNA указывает затронутые версии до 0.35.0, а метаданные GitHub Advisory называют 0.23.0. Обновитесь до 0.35.0 или новее и проверьте установленную версию.
Пошаговый сценарий атаки
Прокси обладает полномочиями upstream
У процесса Headroom может быть ключ OpenAI в OPENAI_API_KEY; браузерному клиенту не нужно передавать сам ключ.
Браузер открывает недоверенную страницу
Если браузер достигает WebSocket-точки Headroom, скрипт страницы может попытаться подключиться. Браузер указывает источник страницы в handshake.
Уязвимый обработчик принимает соединение без политики источника
В затронутом обработчике запрос доходит до websocket.accept() без предварительного отклонения недоверенного Origin. Политика одного источника не является серверной авторизацией WebSocket.
Прокси пересылает аутентифицированный запрос
Headroom может заполнить отсутствующий upstream-заголовок Authorization значением из окружения. Страница злоумышленника тогда вызывает запросы с полномочиями прокси; последствия зависят от инструментов, ключей, сетевой доступности и действий пользователя.
Исправление отклоняет запрещённые источники до маршрутизации
Upstream-патч проверяет источник до принятия и передачи сессии. Для браузера по умолчанию разрешён loopback, нужные источники можно настроить, а запрещённое соединение закрывается с кодом 1008.
Исходный код: Уязвимый vs Защищённый вариант
В предупреждении указаны handle_openai_responses_ws() и отсутствие проверки перед websocket.accept(). Коммит c632023 добавляет _is_allowed_websocket_origin() и отклоняет запрещённый источник до маршрутизации. Нативные клиенты без Origin сохраняют совместимость, для браузера по умолчанию разрешён loopback, а явные источники задаются через HEADROOM_WS_ORIGINS или HEADROOM_CORS_ORIGINS. Сокращённый пример показывает изменённую границу доверия, но не включает все вспомогательные функции. Upstream-тесты проверяют отсутствие Origin, loopback, удалённый и некорректный источник, а также явную конфигурацию.
Фрагмент исходного кода: headroom/proxy/handlers/openai.py
async def handle_openai_responses_ws(self, websocket: WebSocket) -> None:
# Уязвимый вариант: принять сеанс браузера до проверки источника.
await websocket.accept()
await self._relay_responses_session(websocket)
async def handle_openai_responses_ws(self, websocket: WebSocket) -> None:
ws_headers = dict(websocket.headers)
if not _is_allowed_websocket_origin(ws_headers):
logger.warning(
"event=websocket_origin_not_allowed origin=%r",
_header_get(ws_headers, "origin"),
)
await websocket.close(code=1008, reason="origin not allowed")
return
# До маршрутизации к upstream доходит только сеанс браузера, разрешённый политикой.
await websocket.accept()
await self._relay_responses_session(websocket)
Чек-лист по защите системы для инженеров
- ✓Обновите Headroom до 0.35.0 или новее и проверьте установленную версию и образ развёртывания, а не только список зависимостей.
- ✓Проверяйте WebSocket
Originдоaccept()и передачи сообщений upstream; отклоняйте не-loopback источники, кроме явно разрешённых. - ✓Оставляйте поддержку нативных клиентов без
Originтолько при независимой проверке типа клиента; отсутствие браузерного источника не означает авторизацию. - ✓Не задавайте
*в списке разрешённых источников прокси с секретами; перечислите доверенные источники точно и проверьте нормализацию стандартных портов. - ✓По возможности привяжите локальный прокси разработки к loopback; если к нему обращаются другие узлы, требуйте аутентификацию или сетевые ограничения.
- ✓Используйте upstream-ключи с ограниченными полномочиями и сроком действия; не предоставляйте shell или другие опасные инструменты запросам, проходящим из браузера.
- ✓Добавьте тесты, подтверждающие, что удалённый Origin закрывается с нарушением политики до upstream-маршрутизации, а loopback, настроенные источники и доверенные нативные клиенты работают ожидаемо.
- ✓Считайте выполнение команд условным и зависящим от настроенных инструментов; ошибка Origin сама по себе не даёт автоматический RCE хоста.