7 min de leitura

Uma injeção de solicitação HTTP pode começar com uma quebra de linha?

A injeção de solicitação HTTP pode começar com uma quebra de linha oculta. Saiba como rejeitar CRLF em destinos e cabeçalhos antes de renderizar solicitações com credenciais.

Uma injeção de solicitação HTTP pode começar com uma quebra de linha?

Um injetor de credenciais pode manter uma chave de API fora do contexto de um agente e, ainda assim, enviar essa chave na solicitação errada. A falha acontece quando o gateway trata a entrada do agente como texto inofensivo e depois a insere na sintaxe HTTP, depois que a camada de credenciais já fez seu trabalho.

Já vi equipes se esforçarem de verdade com armazenamento em cofre, prompts de aprovação e logs de auditoria, mas deixarem um formatador de strings entre o agente e a rede. Esse formatador passa a fazer parte da fronteira de segurança. Se aceitar um retorno de carro ou uma nova linha em um destino, no nome de um cabeçalho personalizado ou no valor de cabeçalho fornecido pelo agente, poderá deixar dados criarem a estrutura da solicitação.

A correção é deliberadamente simples: rejeite retornos de carro e novas linhas antes de renderizar a solicitação, analise entradas estruturadas em vez de montá-las com strings e teste os bytes que saem do processo. Não tente higienizar isso removendo caracteres ou fazendo substituições. Uma solicitação rejeitada é honesta. Uma solicitação reparada pode ser outra solicitação.

Uma injeção de solicitação HTTP pode começar com uma quebra de linha?

Sim. Em HTTP/1.1, uma quebra de linha separa partes da mensagem. Se o código colocar texto não confiável em uma linha de solicitação ou de cabeçalho sem impor esse limite, um invasor poderá usar CRLF, retorno de carro seguido de nova linha, para encerrar a linha atual e iniciar outra.

Um renderizador vulnerável simplificado costuma parecer inofensivo:

GET {path} HTTP/1.1\r\n
Host: {host}\r\n
Authorization: Bearer {vault_token}\r\n
{custom_name}: {agent_value}\r\n
\r\n

Suponha que o agente forneça este valor:

blue\r\nX-Forwarded-Host: internal.example

Os bytes renderizados agora contêm um cabeçalho extra. A credencial não vazou para o prompt do agente. Mesmo assim, o agente influenciou a solicitação autenticada de uma forma que o projetista não pretendia.

A mesma classe de erro aparece em uma string de destino quando um gateway constrói a primeira linha por conta própria, ou quando uma camada analisa uma URL e outra, mais tarde, combina um caminho, host ou destino de proxy por formatação de texto. O resultado exato depende da biblioteca HTTP e do comportamento dos intermediários. Essa incerteza não é uma defesa. Quando componentes diferentes discordam sobre os limites de uma mensagem, uma pequena falha de validação pode virar um incidente de segurança.

A RFC 9110 define linhas de campo HTTP como sintaxe formada por um nome de campo, dois-pontos e valor de campo. Ela também trata quebras de linha como delimitação da mensagem, não como conteúdo comum dentro de um campo. Essa distinção importa mais que um aviso genérico para «escapar a entrada do usuário». Não há forma útil de escapar uma quebra de linha dentro de um campo de cabeçalho para esse fim. Rejeite-a.

Um cofre protege credenciais, não a estrutura da solicitação

Injeção de credenciais e injeção de solicitação resolvem problemas diferentes. A primeira mantém o segredo longe do agente. A segunda garante que a solicitação que recebe o segredo tenha a estrutura aprovada por uma pessoa.

Considere uma ferramenta que permite a um agente chamar uma API de cobrança com um token bearer guardado no cofre e aceita um cabeçalho personalizado opcional. Um desenvolvedor pode achar que o cabeçalho traz pouco risco porque o token nunca aparece nos argumentos da ferramenta. Esse raciocínio ignora a proximidade. Uma linha injetada pode adicionar um cabeçalho de encaminhamento, alterar um content length, duplicar um cabeçalho da aplicação ou contaminar um rótulo de auditoria usado depois. A credencial continua secreta, mas a ação se torna insegura.

Destinos merecem a mesma desconfiança. Um nome de host que parece dado pode virar sintaxe de autoridade. Um caminho pode virar alvo de solicitação. Um seletor de proxy pode mudar para onde o cliente se conecta. Se o agente puder influenciar qualquer um desses valores, o gateway terá de decidir quais formatos são válidos antes de anexar uma credencial.

Por isso, uma lista de hosts aprovados é útil, mas insuficiente. Ela limita para onde uma URL analisada pode apontar. Não prova que todos os componentes receberam a mesma URL analisada, nem impede quebras de linha em informações de usuário, uma substituição de cabeçalho ou uma string anexada depois da verificação da lista. A fronteira precisa rejeitar caracteres de controle e preservar valores estruturados até o cliente HTTP.

Rejeite CR e LF antes de existir qualquer objeto de solicitação

A regra mais segura cabe em uma frase: campos de destino controlados pelo agente, nomes de cabeçalho personalizados e valores de cabeçalho personalizados não podem conter \r nem \n após a decodificação.

Aplique essa regra antes de montar uma URL, construir cabeçalhos, escolher uma opção de cliente ou gravar uma entrada de log que depois alimente uma nova tentativa de solicitação. Verifique a string original para detectar caracteres de controle literais. Decodifique uma vez, conforme o contrato de entrada, e verifique novamente a string decodificada. Não decodifique repetidamente até nada mudar. Decodificação repetida transforma um contrato claro em adivinhação e pode produzir resultados surpreendentes.

Uma função de validação compacta tem a forma certa quando se recusa a reparar a entrada:

validateRequestText(field, value):
  if value contains "\r" or "\n":
    fail(field + " contains a line break")
  return value

No código real, valide o valor efetivo da string na linguagem, não uma representação impressa. Uma carga JSON pode conter "\n"; depois da análise do JSON, isso vira um único caractere de nova linha. Verificar os dois caracteres visíveis, barra invertida e n, deixa passar o valor perigoso.

Não chame trim() e siga em frente. Na maioria das implementações, ele remove apenas as extremidades, então uma quebra de linha no meio permanece. Substituir quebras de linha por espaços é pior de outro modo: muda a ação solicitada sem deixar para o operador um registro claro de que o agente pediu sintaxe proibida. Trate a entrada como inválida e pare antes da E/S de rede.

A resposta de rejeição deve identificar o campo, mas evitar refletir conteúdo sensível. custom header value contains a line break informa o suficiente a quem chamou. Repetir o valor inteiro pode colocar credenciais ou dados privados em uma transcrição de terminal.

Nomes de cabeçalho precisam de um contrato mais restrito que valores

Um valor de cabeçalho pode conter legitimamente espaços e muitos caracteres visíveis. Um nome de cabeçalho não deveria. Permitir que um agente escolha nomes arbitrários cria mais ambiguidade do que a maioria dos gateways de ação precisa.

Use uma regra conservadora para nomes: apenas letras ASCII, dígitos e hífen, com um limite razoável de tamanho. Rejeite dois-pontos, espaços em branco, caracteres de controle e bytes não ASCII, a menos que exista uma razão documentada para aceitar uma extensão específica. O objetivo não é imitar todos os analisadores permissivos da internet. O objetivo é produzir uma única solicitação sem ambiguidade.

Esta entrada precisa falhar, embora seu valor aparente seja comum:

{
  "name": "X-Trace\r\nAuthorization",
  "value": "debug"
}

Esta também:

{
  "name": "X-Trace: injected",
  "value": "debug"
}

Os dois-pontos pertencem ao renderizador, não ao nome fornecido. Se o gateway os aceitar, um analisador pode enxergar um nome, enquanto outro enxerga uma linha de campo completa. Um nome de cabeçalho com espaço inicial convida a uma discordância parecida.

Valores de cabeçalho precisam de sua própria política além de CRLF. Decida se o gateway aceita cabeçalhos repetidos, se permite vírgulas e quais cabeçalhos os agentes podem substituir. Não una nomes duplicados juntando texto, a menos que a semântica do cabeçalho permita isso explicitamente. Set-Cookie, Content-Length, Host, Authorization e cabeçalhos de encaminhamento merecem tratamento especial ou uma proibição rígida. Prefiro gateways que reservem inteiramente cabeçalhos de protocolo e credenciais e ofereçam apenas um pequeno conjunto de cabeçalhos de aplicação que os agentes possam fornecer.

Às vezes, essa recomendação recebe resistência porque cabeçalhos personalizados arbitrários fazem uma ferramenta HTTP genérica parecer flexível. A flexibilidade é popular porque evita acrescentar uma opção à ferramenta quando uma API precisa de mais um cabeçalho. Ainda assim, esse é o padrão errado para tráfego de agentes com credenciais. Uma opção nomeada, como idempotency_key ou request_id, tem formato e responsável claros. Um conjunto irrestrito de cabeçalhos não tem nenhum dos dois.

Analise destinos uma vez e mantenha suas partes estruturadas

Exija aprovação para cada chave
Marque uma chave para aprovação em cada chamada quando todo uso exigir uma decisão humana.

Depois que o gateway aceita um destino, ele deixa de ser uma string. Passa a ser um valor estruturado com esquema, host, porta, caminho, consulta e, às vezes, informações de usuário ou fragmento. Analise-o uma vez com um analisador de URL que siga os padrões, rejeite componentes não permitidos e entregue o valor analisado ao cliente HTTP sem convertê-lo de volta em uma string de modelo.

A lista exata de permissões depende da ação. Uma ferramenta que chama a API de um único fornecedor pode exigir HTTPS, um nome de host e a porta 443. Uma ferramenta mais ampla pode permitir várias origens pré-configuradas. Em qualquer caso, rejeite credenciais incorporadas, fragmentos, nomes de host malformados e qualquer retorno de carro ou nova linha decodificados antes de criar a solicitação. Fragmentos não seguem pela rede, mas aceitá-los acrescenta confusão desnecessária a logs e telas de aprovação.

Uma fronteira de solicitação útil se parece com isto:

input destination
  -> decode once
  -> reject CR and LF
  -> parse URL
  -> require HTTPS
  -> require allowed host and allowed port
  -> reject user info and fragment
  -> pass parsed URL to HTTP client

A ordem é deliberada. Uma lista de hosts aplicada a uma string bruta pode ser enganada por informações de usuário como https://[email protected]/. Uma verificação de quebra de linha aplicada apenas depois que uma biblioteca normaliza um valor malformado pode não detectar o que o analisador anterior aceitou. Analise primeiro para entender o significado, mas rejeite caracteres de controle proibidos antes que algum componente possa renderizá-los como sintaxe.

Não construa uma solicitação HTTP concatenando scheme + "://" + host + path. Esse padrão transforma um valor estruturado de volta em texto justamente quando você mais precisa das garantias do analisador. Se uma biblioteca exigir argumentos separados para host e caminho, valide cada argumento com a mesma regra de caracteres de controle e use a API estruturada da biblioteca.

Codificações criam contornos quando as camadas discordam

CRLF literal é o teste de que todos se lembram. CRLF codificado é o teste que encontra o erro real.

Um agente pode enviar %0d%0a em um componente de URL. Se o gateway verificar a string bruta e mais tarde decodificá-la como URL antes de usar um renderizador personalizado, a verificação protegeu a representação errada. A quebra de linha resultante aparece depois da validação. A dupla codificação, como %250d%250a, se torna relevante se camadas diferentes decodificarem separadamente.

Escolha uma fronteira explícita de normalização. Por exemplo, analise JSON uma vez, decodifique por porcentagem apenas onde o padrão de URL exige, valide a representação que o renderizador consumirá e proíba decodificação genérica posterior. Mantenha um teste para cada transição. Isso é menos chamativo do que um grande higienizador, mas dá aos revisores algo que podem avaliar.

Uma tabela de testes deve cobrir pelo menos estas entradas para cada campo controlado pelo agente:

  • um \r literal, um \n literal e a forma CRLF adjacente
  • %0d, %0a e %0d%0a quando a codificação por porcentagem for aceita
  • %250d%250a se outro componente puder decodificar mais tarde
  • um nome de cabeçalho com dois-pontos ou espaço em branco
  • um destino com informações de usuário, uma porta inesperada ou um host não aprovado

Não teste apenas se há uma mensagem de erro. Confirme que o transporte do cliente nunca é executado para entrada rejeitada. Um validador que informa um erro depois de construir ou enfileirar a solicitação já falhou na propriedade de segurança que importa.

Há uma armadilha relacionada em formatos de configuração. Variáveis de ambiente, YAML, JSON e argumentos de shell têm suas próprias regras de escape. Uma fixture de teste que visualmente contém \n pode conter dois caracteres inofensivos, enquanto a decodificação de JSON em produção a transforma em uma nova linha. Crie testes pelo mesmo caminho de análise que aceita chamadas reais de agentes.

A falha geralmente se esconde em código de conveniência

Verifique offline o histórico de ações
Verifique offline a cadeia de auditoria criptografada do Sallyport com sp audit verify, sem uma chave do cofre.

O código perigoso costuma chegar depois da revisão de segurança inicial. Alguém adiciona cabeçalhos personalizados para uma nova API, torna um proxy de depuração configurável ou escreve uma função de nova tentativa que reconstrói uma solicitação para registro. Cada mudança faz sentido isoladamente. Juntas, podem reintroduzir a renderização por strings depois de o gateway original ter usado APIs seguras do cliente.

Já encontrei isso rastreando uma ação da entrada ao soquete, não procurando apenas a palavra header. Procure interpolação de strings perto de Host:, Authorization:, Cookie:, Content-Length:, alvos de solicitação, comandos de proxy, auxiliares de teste HTTP bruto e utilitários de repetição de logs. Procure também funções de decodificação. Um validador seguro perto do ponto de entrada ajuda pouco se um caminho posterior aceitar outra representação.

Uma falha representativa tem quatro partes. O gateway valida o host de destino contra uma lista de permissões. Ele armazena o restante do destino como texto para um cliente de baixo nível. Um auxiliar de nova tentativa decodifica esse texto por porcentagem para tornar os logs mais fáceis de ler. Em seguida, o auxiliar escreve uma linha de solicitação bruta para a nova tentativa, e %0d%0aX-Test:%20yes virou sintaxe de mensagem.

Nenhuma dessas linhas diz «permitir injeção de solicitação». O defeito é a invariável ausente: conteúdo não confiável nunca pode conter caracteres de quebra de linha quando o código renderiza uma mensagem HTTP. Coloque essa invariável em um validador compartilhado e faça da renderização bruta uma exceção que os testes precisem justificar.

Se usar uma biblioteca HTTP de alto nível, mantenha tudo em alto nível até o fim. Evite recorrer a um soquete bruto para atender um endpoint incomum. Se uma API realmente exigir um formato de rede incomum, dê a essa integração uma implementação dedicada e um modelo de entrada restrito. Não enfraqueça o caminho genérico por causa dela.

Os logs devem mostrar a intenção rejeitada sem repetir segredos

Quando a validação rejeitar uma solicitação, registre contexto suficiente para investigar: hora, sessão do agente ou identidade do processo, nome da ação, categoria do campo, motivo da rejeição e um resumo seguro ou o tamanho do valor fornecido. Não guarde o valor completo do cabeçalho por padrão. Cabeçalhos costumam conter tokens, dados pessoais ou material assinado que vira outro problema de gestão de segredos quando é copiado para logs.

Separe o evento de auditoria de uma ação tentada do evento de uma ação executada. Uma solicitação rejeitada não deve parecer uma chamada de rede com falha. Ela nunca chegou à rede. Essa distinção ajuda na resposta a incidentes e evita que operadores procurem um serviço remoto que jamais viu a solicitação.

O Sallyport mantém um diário de Sessões para execuções de agentes e um diário de Atividade para chamadas, ambos projetados a partir de seu log de auditoria criptografado e encadeado por hash. Isso dá ao gateway um lugar adequado para mostrar uma ação recusada, mas a regra de entrada ainda deve estar antes da renderização HTTP.

A propriedade de verificação offline importa aqui. sp audit verify pode verificar a cadeia sobre o texto cifrado sem a chave do cofre, o que pode estabelecer que os eventos registrados não foram alterados. Ele não pode dizer se um validador foi permissivo demais. O esquema de eventos e os testes precisam tornar visível a categoria de campo rejeitada sem preservar dados perigosos.

Teste a solicitação serializada, não só o validador

Saiba qual agente pode agir
A autorização por sessão identifica um novo processo de agente antes da primeira ação externa.

Testes unitários para contains('\r') || contains('\n') são necessários, mas insuficientes. Eles provam que uma função rejeita dois caracteres. Não provam que o transporte real não possa receber uma quebra de linha introduzida por decodificação, padrões, novas tentativas ou um adaptador personalizado.

Use um servidor de teste local ou um transporte de gravação que capture o objeto de solicitação de saída e, onde sua pilha permitir, seus bytes serializados. Envie entradas aprovadas pelo mesmo ponto de entrada de ferramenta que os agentes usam. Confirme o método, a autoridade, o caminho e o conjunto completo de cabeçalhos. Depois, envie casos rejeitados e confirme que o transporte de gravação não viu nenhuma solicitação.

Uma matriz de testes compacta encontra mais regressões do que uma longa coleção de testes de segurança vagos:

field                 input                         expected result
custom header value   "ok\r\nX-Added: yes"          reject before transport
custom header name    "X-Mode: injected"            reject before transport
destination path      "/v1/a%0d%0aX-Added:%20yes"   reject after decoding
allowed destination   "https://api.example/v1/a"    one request, expected host

Adicione testes de propriedade se a linguagem tornar isso viável. Gere caracteres de controle ao redor de cada classe de caracteres aceita e confirme a rejeição. Fuzzing ajuda apenas depois de o contrato estar claro. Um fuzzer encontrará casos estranhos do analisador; ele não pode dizer se nomes de cabeçalho arbitrários foram uma decisão de produto ou um acidente.

Quando um teste falhar, inspecione os bytes finais antes de mudar o validador. Já vi equipes adicionarem mais substituições até um caso passar e só então descobrirem que o cliente tinha dobrado espaços em branco ou decodificado duas vezes. O reparo certo costuma ser remover o formatador ou decodificador que tornou possível a sintaxe bruta.

Aprovação humana não justifica ações malformadas

Uma aprovação por sessão responde se determinado processo de agente pode agir. Uma aprovação por chamada responde se esse uso de uma credencial protegida merece uma decisão humana. Nenhuma das duas torna segura uma solicitação ambígua. Uma pessoa não consegue inspecionar com segurança caracteres de controle ocultos em um destino ou valor de cabeçalho longo, e a fadiga de aprovação transformará um prompt sutil em um carimbo automático.

Mantenha os dados de aprovação estruturados também. Mostre host normalizado, método, caminho, categorias de cabeçalho nomeadas e uma indicação clara quando a ação acrescenta uma credencial. Rejeite entradas malformadas antes de o cartão de aprovação aparecer. Pedir a uma pessoa que aprove uma solicitação inválida transfere um erro de analisador para a interface.

O controle de cofre, a autorização de sessão e a definição de chave por chamada do Sallyport oferecem momentos diferentes de controle humano. A fronteira da ação HTTP ainda precisa de um renderizador rígido, porque a decisão de aprovação deve abranger uma ação bem-formada, não uma string cujo significado muda depois do clique.

A primeira mudança prática não exige uma reescrita ampla. Encontre cada caminho que aceita texto de destino ou cabeçalhos personalizados controlados pelo agente, adicione uma rejeição incondicional de CRLF antes da renderização e escreva um teste de transporte que prove que a entrada rejeitada não envia nada. Depois, remova o código de conveniência que reconstrói HTTP com strings. É aí que esse defeito continua voltando.

FAQ

Devo rejeitar CRLF em valores de cabeçalho HTTP?

Rejeite os dois caracteres em qualquer lugar em que possam entrar na sintaxe da solicitação: strings de destino, nomes e valores de cabeçalho, substituições de método, fragmentos de cookie e configurações de proxy. Trate dados decodificados por porcentagem da mesma forma. A validação precisa ocorrer antes de interpolação, análise ou de uma chamada de biblioteca que possa normalizar a entrada.

Uma biblioteca HTTP segura pode impedir request smuggling?

Uma biblioteca HTTP normal ajuda, mas não elimina a necessidade de validar na fronteira. Seu gateway pode decodificar, combinar ou formatar a entrada do agente antes que ela chegue à biblioteca, e algumas entradas afetam primeiro outro analisador. Rejeitar quebras de linha na fronteira deixa o contrato pretendido explícito.

Por que retornos de carro são perigosos em solicitações de API?

As quebras de linha separam a sintaxe das mensagens HTTP. Permiti-las em um destino ou campo de cabeçalho pode transformar dados em outro campo ou componente da solicitação. O código vulnerável ainda pode enviar uma solicitação sintaticamente válida, por isso esse erro pode passar despercebido em testes comuns.

CRLF codificado por porcentagem contorna a validação?

O analisador deve decodificar uma vez e depois validar o valor decodificado. Se ele aceitar uma quebra de linha codificada por porcentagem e uma camada posterior a decodificar, a verificação ocorreu cedo demais. Mantenha os valores de solicitação decodificados e renderizados separados para que o código posterior não os decodifique outra vez.

Como devo validar nomes de cabeçalho personalizados?

Os nomes de cabeçalho devem seguir uma lista pequena de caracteres permitidos, como letras, dígitos e hífen, e rejeitar todo o resto. Isso impede quebras de linha, inserção de dois-pontos, truques com espaços em branco e nomes que componentes diferentes interpretam de formas distintas. Não aceite nomes de cabeçalho arbitrários fornecidos pelo agente só porque o valor foi verificado.

Analisar a URL basta para proteger um destino de agente?

Não. Um analisador de URL pode informar onde estão os limites de host e caminho, mas não decide se um destino é permitido. Primeiro analise a URL, depois aplique suas próprias regras para esquema, host, porta, informações de usuário, fragmentos e caracteres de controle decodificados.

O que um gateway de ações deve fazer ao encontrar uma quebra de linha?

Rejeite-a antes de criar qualquer objeto de solicitação. Não remova espaços nas pontas, não substitua a quebra de linha por um espaço nem a elimine silenciosamente, pois esses reparos ocultam a ação original nos logs e podem mudar o significado de uma entrada assinada ou autenticada. Retorne um erro que identifique o campo sem repetir um segredo.

A validação deve ocorrer antes ou depois da decodificação de URL?

Verifique a entrada bruta, depois a representação decodificada uma única vez e, nos testes, os bytes finais serializados. A primeira verificação encontra entradas obviamente inválidas; a verificação após decodificação encontra tentativas de contorno por codificação; o teste de serialização encontra código de formatação inseguro. Uma única verificação deixa espaço para uma transformação posterior mudar o valor.

A injeção de credenciais pode causar injeção de solicitação?

Mesmo que código confiável adicione a credencial, a solicitação ao redor ainda pode ser controlada por um agente. Um valor de cabeçalho hostil pode colocar um segundo cabeçalho ao lado da credencial injetada, enquanto um destino hostil pode redirecionar para onde vai a solicitação com credenciais. Proteger o segredo e proteger a estrutura da solicitação são tarefas separadas.

Quais casos de teste detectam injeção de cabeçalho HTTP?

Teste cada posição de entrada aceita com retorno de carro literal, nova linha literal, CRLF, formas codificadas por porcentagem, formas com dupla codificação, espaço em branco inicial e dois-pontos no nome de cabeçalho. Confirme que o gateway rejeita cada caso antes de qualquer E/S de rede. Depois, examine uma solicitação capturada para casos aprovados e verifique se ela contém exatamente os campos pretendidos.

Sallyport

O Sallyport executa chamadas de API e comandos SSH pelo seu agente de IA. As chaves ficam em um cofre local no seu Mac; você aprova cada execução e toda ação vai para um registro selado.

© 2026 Sallyport · Código aberto sob Apache-2.0 · Oleg Sotnikov