
Detecta e explora vulnerabilidades de contrabando de requisições HTTP através da conversão de HTTP/2 para HTTP/1.1, utilizando técnicas automatizadas de contrabando de cabeçalhos para identificar discrepâncias de análise no backend.
Esta ferramenta ajuda a detectar e explorar o contrabando de requisições HTTP em casos em que pode ser alcançado via conversão HTTP/2 -> HTTP/1.1 pelo servidor frontend.
O esquema é o seguinte:
O atacante quer encontrar uma requisição que seja vista como duas requisições separadas pelo servidor backend.
Se a conexão HTTP/1.1 entre frontend<->backend usar keep-alive, o frontend pode enviar requisições de outros usuários para a mesma conexão. Se formos capazes de "envenenar" a conexão com uma requisição parcial que vem após uma legítima, podemos recuperar a requisição de outro usuário.
Outros cenários possíveis incluem contornar a proteção e reescritas do servidor frontend, envenenamento de cache ou engano de cache.
Para mais informações sobre o Contrabando de Requisições HTTP, consulte a Portswigger Web Security Academy.
No HTTP/2, todos os nomes e valores dos cabeçalhos HTTP são binários. Isso significa que tecnicamente eles podem conter espaços adicionais ou até mesmo quebras de linha.
RFC7540#10.3 afirma que implementações que traduzem requisições HTTP/2 para HTTP/1 devem cuidar das limitações no conjunto de caracteres que surgem dessa conversão; a maioria das implementações de fato os rejeita. Apesar disso, esperamos encontrar algumas que permitam tais cabeçalhos. Eles irão corromper a requisição HTTP/1.1 convertida para o backend.
Outro ponto é que algumas correções recentes relacionadas ao Contrabando de Requisições HTTP podem ser implementadas apenas para parsers HTTP/1.1.
Em geral, esperamos que existam implementações de HTTP/2 que não estejam muito cientes das pesquisas recentes sobre Contrabando de Requisições HTTP em HTTP/1.1 e não incluam mitigações correspondentes.
Surpreendentemente, sim!
Encontrei a possibilidade de contrabandear um cabeçalho com um caractere de espaço através do Cloudflare, abrindo assim uma porta para contrabando entre Cloudflare e cliente (caso o software de um cliente do Cloudflare aceite e faça trim nos nomes dos cabeçalhos). Aqui está o post no blog.
Há também outro relatório de bug bounty que ainda não é público. Ele faz uso do fato de que softwares personalizados não filtram quebras de linha em cabeçalhos HTTP2, e o contrabando acontece 100% (consigo ver requisições de outros usuários).
No entanto, entendo que esse tipo de vulnerabilidade deve ser frustrantemente raro: ao contrário do HTTP/1.1, não existem tantas implementações de HTTP/2, e a maioria delas é feita com segurança em mente, rejeitando cabeçalhos suspeitos ou inválidos.
A ferramenta possui um subcomando que tenta detectar automaticamente se um alvo é vulnerável ao ataque de Contrabando de Requisições HTTP. O algoritmo por trás dessa funcionalidade é descrito nesta seção.
Para realizar um ataque de Contrabando de Requisições HTTP, na verdade precisamos primeiro "contrabandear" um único cabeçalho (seja Content-Length ou Transfer-Encoding). Isso significa que precisamos enviar um cabeçalho que a) controle onde o corpo da requisição termina e b) não seja processado pelo frontend, mas seja processado pelo backend.
Isso geralmente é conseguido modificando um cabeçalho de alguma forma: adicionando espaços ou tabulações ao final do seu nome, substituindo o valor por um semiequivalente, etc.
A ideia básica do algoritmo de detecção de vulnerabilidade é detectar se o servidor realmente processa um cabeçalho contrabandeado como se fosse Content-Length ou Transfer-Encoding. Fazemos isso enviando múltiplas requisições: algumas com valores válidos e outras com valores inválidos para o cabeçalho. Em seguida, tentamos detectar se há uma maneira de distinguir as respostas desses dois grupos.
É por isso que a saída da ferramenta não contém as palavras "vulnerável/invulnerável": ela apenas informa se consegue distinguir as respostas que vieram das requisições desses dois grupos.
A ferramenta considera dois conjuntos de respostas HTTP como distinguíveis se pelo menos uma das duas condições a seguir for atendida:
Os timeouts são tratados como um valor de código de status único, não igual a nenhum outro; assim, a ferramenta substitui o esquema clássico de "detectar por tempo".
Vamos considerar um exemplo. Suponha que estamos tentando contrabandear o cabeçalho transfer-encoding substituindo o traço por um sublinhado.
Se parecer que o servidor responde com status 400 toda vez que enviamos transfer_encoding:zalupa e trava quando é transfer_encoding:chunked, podemos dizer que o servidor provavelmente processa o cabeçalho como o valor para transfer encoding. Teoricamente, poderia ser o servidor frontend ou backend.
O primeiro caso não é interessante, pois poderíamos enviar a versão não contrabandeada do cabeçalho de qualquer forma, e o segundo é o que procuramos. Como tudo acontece sobre HTTP/2, o primeiro caso é evitável na maioria das vezes: o servidor HTTP/2 determina o final do corpo da requisição de outra forma, não relacionada aos cabeçalhos da requisição, e nunca espera que o corpo esteja no formato chunked do HTTP/1.1 (aquele com comprimentos de chunk hexadecimais).
As variações concretas das técnicas de detecção são:
Enviamos uma versão contrabandeada de Transfer-Encoding: chunked (por exemplo, transfer_encoding:chunked) e corpos diferentes: válido é 0\r\n\r\n e inválido é 999\r\n.
Caso as respostas sejam diferentes, podemos ter certeza de que o servidor backend recebe e processa o cabeçalho contrabandeado. Não há razão para o frontend fazer isso: HTTP/2 não usa o formato chunked que enviamos, então seria inválido.
Esperamos que o backend trave (ou seja, a requisição expire) para as requisições inválidas, pois ele aguarda a chegada de mais dados.
Esta é a variante de detecção mais confiável: se o servidor travar ao ler o corpo, algo provavelmente deu errado, já que não há uso para transfer encoding HTTP/1.1 em uma requisição HTTP/2.
Enviamos uma versão contrabandeada de Transfer-Encoding e corpos diferentes novamente: 0\r\n\r\n como corpo válido e X\r\n\r\n como inválido.
O caso é o mesmo que acima, mas em vez de ler o corpo, esperamos que o backend pelo menos o valide.
Enviamos uma versão contrabandeada do cabeçalho Content-Length com valores 1 e -1.
Ambos os valores são inválidos do ponto de vista do frontend: há outro mecanismo para determinar o comprimento do corpo em HTTP/2, e nenhum corpo de requisição é realmente enviado em ambos os casos. Se as respostas forem diferentes, supomos que foi o servidor backend que parseou os cabeçalhos.
Este método é o menos confiável: o frontend pode emitir erros diferentes quando Content-Length tem um valor inválido e quando não corresponde ao comprimento real.
Ao enviar múltiplos pares de requisições válidas/inválidas, podemos reduzir a chance de falsos positivos aleatórios. Por outro lado, podemos parar cedo se percebermos que não há como separar as respostas das requisições "válidas" das respostas das "inválidas".
A ferramenta tenta empregar múltiplas técnicas de contrabando modificando um cabeçalho de várias maneiras. Nenhuma delas é nova e ao mesmo tempo óbvia.