FlawOpen

flawopen.com/Teardowns/cve-2026-71416-headroom-websocket-origin-hijacking

● CVE-2026-71416 · CVSS 8.8 · Alta
Pesquisa · FlawOpen

CVE-2026-71416: sequestro de origem WebSocket no Headroom

O Headroom não validava Origin no WebSocket; um site visitado podia enviar solicitações LLM pelo proxy acessível. Atualize para 0.35.0+.

💡 Explicação em Linguagem Simples (ELI5)

Um recepcionista deixa qualquer pessoa entrar em um balcão privado porque não confere o crachá do visitante. O cabeçalho Origin do navegador é esse crachá: o proxy precisa verificá-lo antes de abrir um canal que possa usar a chave de API armazenada.

Conceitos Centrais e Termos

WebSocket
Conexão bidirecional e persistente iniciada por uma solicitação HTTP de upgrade e usada para trocar mensagens nos dois sentidos.
Cabeçalho Origin
Esquema, host e porta enviados pelo navegador para identificar a página que iniciou a solicitação. No handshake WebSocket, o servidor deve aplicar sua própria política de confiança; cabeçalhos CORS não substituem essa verificação.
CSWSH
Cross-Site WebSocket Hijacking: uma página não confiável abre um WebSocket para um serviço acessível que não valida a origem iniciadora.
Credencial ambiente
Segredo já disponível ao processo, como uma chave de API em variável de ambiente, que pode autenticar uma solicitação sem credencial própria do cliente.
CWE-1385
Falta de validação de Origin em WebSockets: o endpoint não verifica se a origem da comunicação é permitida.

Mapeamentos de frameworks

Análise de Causa Raiz

O handler WebSocket /v1/responses do Headroom aceitava a conexão do navegador antes de conferir o Origin do handshake. Em seguida, o proxy podia acrescentar OPENAI_API_KEY do próprio ambiente à solicitação upstream. A política de mesma origem do navegador não impede, por si só, que JavaScript inicie um WebSocket para outro host acessível; o navegador normalmente envia Origin, e cabe ao servidor rejeitar origens fora da política. A exploração depende, portanto, de o navegador da vítima alcançar um proxy Headroom vulnerável e de o proxy possuir credenciais ou integrações úteis. O aviso diz que a conexão aceita pode emitir solicitações LLM arbitrárias. A execução de comandos no host depende de a solicitação poder usar uma ferramenta configurada para executar comandos; não decorre automaticamente da falta de validação do cabeçalho. O CNA lista versões anteriores a 0.35.0 como afetadas, enquanto os metadados do aviso GitHub citam 0.23.0. Atualize para 0.35.0 ou posterior e confirme a versão instalada.

Fluxo de Ataque Passo a Paso

Passo 1

O proxy detém autoridade upstream

Um processo Headroom pode ter uma chave OpenAI em OPENAI_API_KEY; o cliente navegador não precisa enviar a chave.

Passo 2

O navegador acessa uma página não confiável

Se conseguir alcançar o endpoint WebSocket do Headroom, o script da página pode tentar uma conexão. O navegador inclui a origem da página no handshake.

Passo 3

O handler vulnerável aceita sem política de origem

No handler afetado, a solicitação chega a websocket.accept() sem rejeitar primeiro um Origin não confiável. A política de mesma origem não é uma verificação de autorização WebSocket no servidor.

Passo 4

O proxy encaminha uma solicitação autenticada

O Headroom pode preencher o cabeçalho Authorization upstream ausente usando o ambiente. A página controlada pelo atacante pode então provocar solicitações com a autoridade do proxy; o impacto depende das ferramentas, credenciais, alcance de rede e interação do usuário.

Passo 5

A correção rejeita origens proibidas antes do roteamento

O patch upstream verifica a origem antes de aceitar e encaminhar a sessão. Por padrão, limita origens de navegador ao loopback, permite origens configuradas e fecha uma conexão proibida com o código 1008.

Código-Fonte: Vulnerável vs. Seguro

O aviso identifica handle_openai_responses_ws() e a ausência de verificação antes de websocket.accept(). O commit c632023 adiciona _is_allowed_websocket_origin() e rejeita uma origem proibida antes do roteamento; preserva clientes nativos sem Origin, limita origens de navegador ao loopback por padrão e permite origens configuradas com HEADROOM_WS_ORIGINS ou HEADROOM_CORS_ORIGINS. Este par compacto destaca a fronteira de confiança alterada; os auxiliares não estão reproduzidos integralmente. Os testes upstream cobrem Origin ausente, loopback, origem remota, valor malformado e configuração explícita.

Trecho de origem: headroom/proxy/handlers/openai.py

IMPLEMENTAÇÃO VULNERÁVEL
async def handle_openai_responses_ws(self, websocket: WebSocket) -> None:
    # Formato vulnerável: aceite a sessão do navegador antes de validar a origem.
    await websocket.accept()
    await self._relay_responses_session(websocket)
PATCH SEGURO E ROBUSTO
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

    # Somente uma sessão do navegador aprovada pela política chega ao roteamento upstream.
    await websocket.accept()
    await self._relay_responses_session(websocket)

Lista de Verificação de Segurança para Engenharia

Fontes