hacktricks/pentesting-web/race-condition.md

23 KiB

Condição de Corrida


Use Trickest para construir e automatizar fluxos de trabalho com as ferramentas comunitárias mais avançadas do mundo.
Obtenha Acesso Hoje:

{% embed url="https://trickest.com/?utm_campaign=hacktrics&utm_medium=banner&utm_source=hacktricks" %}

Aprenda a hackear AWS do zero ao herói com htARTE (HackTricks AWS Red Team Expert)!

Outras formas de apoiar o HackTricks:

Explorando RC

O principal problema ao abusar de RCs é que você precisa que as solicitações sejam processadas em paralelo com uma diferença de tempo muito curta (geralmente >1ms). Na seção a seguir, diferentes soluções são propostas para tornar isso possível.

Ataque de pacote único (HTTP/2) / Sincronização do último byte (HTTP/1.1)

HTTP2 permite enviar 2 solicitações em uma única conexão TCP (enquanto no HTTP/1.1 elas têm que ser sequenciais).
O uso de um único pacote TCP elimina completamente o efeito da variação de rede, então isso claramente tem potencial para ataques de condição de corrida também. No entanto, duas solicitações não são suficientes para um ataque de corrida confiável devido à variação do lado do servidor - variações no tempo de processamento de solicitações da aplicação causadas por variáveis incontroláveis como a contenção de CPU.

Mas, usando a técnica de 'sincronização do último byte' do HTTP/1.1, é possível pré-enviar a maior parte dos dados retendo um pequeno fragmento de cada solicitação e depois 'completar' 20-30 solicitações com um único pacote TCP.

Para pré-enviar a maior parte de cada solicitação:

  • Se a solicitação não tem corpo, envie todos os cabeçalhos, mas não defina a flag END_STREAM. Retenha um quadro de dados vazio com END_STREAM definido.
  • Se a solicitação tem um corpo, envie os cabeçalhos e todos os dados do corpo exceto o último byte. Retenha um quadro de dados contendo o último byte.

Em seguida, prepare-se para enviar os quadros finais:

  • Espere por 100ms para garantir que os quadros iniciais foram enviados.
  • Garanta que TCP_NODELAY está desativado - é crucial que o algoritmo de Nagle agrupe os quadros finais.
  • Envie um pacote ping para aquecer a conexão local. Se você não fizer isso, a pilha de rede do SO colocará o primeiro quadro final em um pacote separado.

Finalmente, envie os quadros retidos. Você deve ser capaz de verificar que eles chegaram em um único pacote usando o Wireshark.

{% hint style="info" %} Note que não funciona para arquivos estáticos em certos servidores, mas arquivos estáticos são irrelevantes para ataques de RC. {% endhint %}

Usando essa técnica, você pode fazer com que 20-30 solicitações cheguem ao servidor simultaneamente - independentemente da variação de rede:

Adaptando-se à arquitetura alvo

Vale ressaltar que muitas aplicações estão por trás de um servidor frontal, e estes podem decidir encaminhar algumas solicitações por conexões existentes para o back-end, e criar novas conexões para outras.

Como resultado, é importante não atribuir inconsistências no tempo das solicitações ao comportamento da aplicação, como mecanismos de bloqueio que permitem apenas um único thread acessar um recurso por vez. Além disso, o roteamento de solicitações front-end é frequentemente feito com base em cada conexão, então você pode ser capaz de uniformizar o tempo das solicitações realizando um aquecimento de conexão do lado do servidor - enviando algumas solicitações inconsequentes pela sua conexão antes de realizar o ataque (isso é apenas enviar várias solicitações antes de começar o ataque real).

Mecanismos de bloqueio baseados em sessão

Alguns frameworks tentam prevenir a corrupção acidental de dados usando alguma forma de bloqueio de solicitação. Por exemplo, o módulo de manipulador de sessão nativo do PHP processa apenas uma solicitação por sessão por vez.

É extremamente importante identificar esse tipo de comportamento, pois ele pode mascarar vulnerabilidades trivialmente exploráveis. Se você notar que todas as suas solicitações estão sendo processadas sequencialmente, tente enviar cada uma delas usando um token de sessão diferente.

Abusando de limites de taxa ou recursos

Se o aquecimento de conexão não fizer diferença, existem várias soluções para esse problema.

Usando o Turbo Intruder, você pode introduzir um curto atraso do lado do cliente. No entanto, como isso envolve dividir suas solicitações de ataque reais em vários pacotes TCP, você não poderá usar a técnica de ataque de pacote único. Como resultado, em alvos com alta variação de rede, o ataque é improvável de funcionar de forma confiável, independentemente do atraso que você definir.

Em vez disso, você pode ser capaz de resolver esse problema abusando de um recurso de segurança comum.

Servidores web frequentemente atrasam o processamento de solicitações se muitas forem enviadas rapidamente. Ao enviar um grande número de solicitações fictícias para acionar intencionalmente o limite de taxa ou recursos, você pode ser capaz de causar um atraso adequado do lado do servidor. Isso torna o ataque de pacote único viável mesmo quando a execução atrasada é necessária.

{% hint style="warning" %} Para mais informações sobre essa técnica, confira o relatório original em https://portswigger.net/research/smashing-the-state-machine {% endhint %}

Exemplos de Ataque

  • Tubo Intruder - Ataque de pacote único HTTP2 (1 endpoint): Você pode enviar a solicitação para o Turbo Intruder (Extensões -> Turbo Intruder -> Enviar para Turbo Intruder), você pode alterar na solicitação o valor que deseja forçar bruta para %s como em csrf=Bn9VQB8OyefIs3ShR2fPESR0FzzulI1d&username=carlos&password=%s e depois selecionar o examples/race-single-packer-attack.py no menu suspenso:

Se você for enviar valores diferentes, você poderia modificar o código com este que usa uma lista de palavras da área de transferência:

passwords = wordlists.clipboard
for password in passwords:
engine.queue(target.req, password, gate='race1')

{% hint style="warning" %} Se a web não suportar HTTP2 (apenas HTTP1.1) use Engine.THREADED ou Engine.BURP em vez de Engine.BURP2. {% endhint %}

  • Tubo Intruder - Ataque de pacote único HTTP2 (Vários endpoints): Caso precise enviar uma solicitação para 1 endpoint e depois várias para outros endpoints para acionar o RCE, você pode alterar o script race-single-packet-attack.py com algo como:
def queueRequests(target, wordlists):
engine = RequestEngine(endpoint=target.endpoint,
concurrentConnections=1,
engine=Engine.BURP2
)

# Hardcode the second request for the RC
confirmationReq = '''POST /confirm?token[]= HTTP/2
Host: 0a9c00370490e77e837419c4005900d0.web-security-academy.net
Cookie: phpsessionid=MpDEOYRvaNT1OAm0OtAsmLZ91iDfISLU
Content-Length: 0

'''

# For each attempt (20 in total) send 50 confirmation requests.
for attempt in range(20):
currentAttempt = str(attempt)
username = 'aUser' + currentAttempt

# queue a single registration request
engine.queue(target.req, username, gate=currentAttempt)

# queue 50 confirmation requests - note that this will probably sent in two separate packets
for i in range(50):
engine.queue(confirmationReq, gate=currentAttempt)

# send all the queued requests for this attempt
engine.openGate(currentAttempt)
  • Também está disponível no Repeater através da nova opção 'Enviar grupo em paralelo' no Burp Suite.
  • Para limit-overrun, você poderia simplesmente adicionar o mesmo pedido 50 vezes no grupo.
  • Para aquecimento de conexão, você poderia adicionar no início do grupo alguns pedidos para alguma parte não estática do servidor web.
  • Para atrasar o processo entre o processamento de um pedido e outro em etapas de 2 subestados, você poderia adicionar pedidos extras entre ambos os pedidos.
  • Para um RC de vários pontos finais, você poderia começar enviando o pedido que vai para o estado oculto e depois 50 pedidos logo após ele que exploram o estado oculto.

BF Bruto

Antes da pesquisa anterior, estes eram alguns payloads usados que apenas tentavam enviar os pacotes o mais rápido possível para causar um RC.

  • Repeater: Verifique os exemplos da seção anterior.
  • Intruder: Envie o pedido para o Intruder, defina o número de threads para 30 dentro do menu Opções e, selecione como payload Null payloads e gere 30.
  • Turbo Intruder
def queueRequests(target, wordlists):
engine = RequestEngine(endpoint=target.endpoint,
concurrentConnections=5,
requestsPerConnection=1,
pipeline=False
)
a = ['Session=<session_id_1>','Session=<session_id_2>','Session=<session_id_3>']
for i in range(len(a)):
engine.queue(target.req,a[i], gate='race1')
# open TCP connections and send partial requests
engine.start(timeout=10)
engine.openGate('race1')
engine.complete(timeout=60)

def handleResponse(req, interesting):
table.add(req)
  • Python - asyncio
import asyncio
import httpx

async def use_code(client):
resp = await client.post(f'http://victim.com', cookies={"session": "asdasdasd"}, data={"code": "123123123"})
return resp.text

async def main():
async with httpx.AsyncClient() as client:
tasks = []
for _ in range(20): #20 times
tasks.append(asyncio.ensure_future(use_code(client)))

# Get responses
results = await asyncio.gather(*tasks, return_exceptions=True)

# Print results
for r in results:
print(r)

# Async2sync sleep
await asyncio.sleep(0.5)
print(results)

asyncio.run(main())

Metodologia RC

Excedente de limite / TOCTOU

Este é o tipo mais básico de condição de corrida onde vulnerabilidades que aparecem em locais que limitam o número de vezes que você pode realizar uma ação. Como usar o mesmo código de desconto em uma loja online várias vezes. Um exemplo muito fácil pode ser encontrado neste relatório ou neste bug.

Existem muitas variações desse tipo de ataque, incluindo:

  • Resgatar um cartão-presente várias vezes
  • Avaliar um produto várias vezes
  • Sacar ou transferir dinheiro em excesso ao saldo da sua conta
  • Reutilizar uma única solução CAPTCHA
  • Bypassar um limite de taxa anti-força bruta

Subestados ocultos

Outras RC mais complicadas explorarão subestados no estado da máquina que poderiam permitir a um atacante abusar de estados aos quais ele nunca deveria ter acesso, mas existe uma pequena janela para o atacante acessá-lo.

  1. Prever potenciais subestados ocultos e interessantes

O primeiro passo é identificar todos os endpoints que escrevem nele ou leem dados dele e, em seguida, usam esses dados para algo importante. Por exemplo, usuários podem ser armazenados em uma tabela de banco de dados que é modificada por registro, edições de perfil, iniciação de reset de senha e conclusão de reset de senha.

Podemos usar três perguntas-chave para descartar endpoints que provavelmente não causarão colisões. Para cada objeto e os endpoints associados, pergunte:

  • Como o estado é armazenado?

Dados armazenados em uma estrutura de dados persistente do lado do servidor são ideais para exploração. Alguns endpoints armazenam seu estado inteiramente do lado do cliente, como resets de senha que funcionam enviando um JWT por e-mail - esses podem ser ignorados com segurança.

Aplicações frequentemente armazenam algum estado na sessão do usuário. Esses geralmente são um tanto protegidos contra subestados - mais sobre isso mais tarde.

  • Estamos editando ou acrescentando?

Operações que editam dados existentes (como alterar o e-mail principal de uma conta) têm um grande potencial de colisão, enquanto ações que simplesmente acrescentam a dados existentes (como adicionar um endereço de e-mail adicional) são improváveis de serem vulneráveis a algo além de ataques de excedente de limite.

  • Em que a operação é baseada?

A maioria dos endpoints opera em um registro específico, que é procurado usando uma 'chave', como um nome de usuário, token de reset de senha ou nome de arquivo. Para um ataque bem-sucedido, precisamos de duas operações que usem a mesma chave. Por exemplo, imagine duas implementações plausíveis de reset de senha:

  1. Procurar por pistas

Neste ponto, é hora de lançar alguns ataques RC sobre os endpoints potencialmente interessantes para tentar encontrar resultados inesperados em comparação com os regulares. Qualquer desvio da resposta esperada, como uma mudança em uma ou mais respostas, ou um efeito secundário como conteúdos de e-mail diferentes ou uma mudança visível na sua sessão, pode ser uma pista indicando que algo está errado.

  1. Comprovar o conceito

O passo final é comprovar o conceito e transformá-lo em um ataque viável.

Quando você envia um lote de solicitações, pode descobrir que um par de solicitações iniciais dispara um estado final vulnerável, mas solicitações posteriores sobrescrevem/invalidam isso e o estado final é inexplorável. Neste cenário, você vai querer eliminar todas as solicitações desnecessárias - duas devem ser suficientes para explorar a maioria das vulnerabilidades. No entanto, reduzir para duas solicitações tornará o ataque mais sensível ao tempo, então você pode precisar tentar o ataque várias vezes ou automatizá-lo.

Ataques Sensíveis ao Tempo

Às vezes você pode não encontrar condições de corrida, mas as técnicas para entregar solicitações com tempo preciso ainda podem revelar a presença de outras vulnerabilidades.

Um exemplo é quando timestamps de alta resolução são usados em vez de strings aleatórias criptograficamente seguras para gerar tokens de segurança.

Considere um token de reset de senha que é randomizado apenas usando um timestamp. Neste caso, pode ser possível disparar dois resets de senha para dois usuários diferentes, que usam o mesmo token. Tudo o que você precisa fazer é cronometrar as solicitações para que elas gerem o mesmo timestamp.

{% hint style="warning" %} Para confirmar, por exemplo, a situação anterior, você poderia simplesmente pedir 2 tokens de reset de senha ao mesmo tempo (usando ataque de pacote único) e verificar se eles são os mesmos. {% endhint %}

Confira o exemplo neste laboratório.

Estudos de caso de subestados ocultos

Pagar & adicionar um item

Confira este laboratório para ver como pagar em uma loja e adicionar um item extra que você não precisará pagar.

Confirmar outros e-mails

A ideia é verificar um endereço de e-mail e alterá-lo para um diferente ao mesmo tempo para descobrir se a plataforma verifica o novo alterado.

De acordo com este relato o Gitlab era vulnerável a uma tomada deste modo porque poderia enviar o token de verificação de e-mail de um e-mail para o outro e-mail.

Você também pode conferir este laboratório para aprender sobre isso.

Estados ocultos do banco de dados / Bypass de confirmação

Se 2 escritas diferentes são usadas para adicionar informações dentro de um banco de dados, existe uma pequena porção de tempo onde apenas os primeiros dados foram escritos no banco de dados. Por exemplo, ao criar um usuário, o nome de usuário e senha podem ser escritos e então o token para confirmar a nova conta é escrito. Isso significa que por um pequeno tempo o token para confirmar uma conta é nulo.

Portanto, registrar uma conta e enviar várias solicitações com um token vazio (token= ou token[]= ou qualquer outra variação) para confirmar a conta imediatamente poderia permitir confirmar uma conta onde você não controla o e-mail.

Confira este laboratório para verificar um exemplo.

Bypass de 2FA

O seguinte pseudo-código demonstra como um site poderia ser vulnerável a uma variação de ataque de corrida deste tipo:

session['userid'] = user.userid
if user.mfa_enabled:
session['enforce_mfa'] = True
# generate and send MFA code to user
# redirect browser to MFA code entry form

Como você pode ver, isso é de fato uma sequência de múltiplos passos dentro do intervalo de uma única requisição. Mais importante, ela passa por um subestado no qual o usuário temporariamente possui uma sessão válida de login, mas a MFA ainda não está sendo aplicada. Um atacante poderia potencialmente explorar isso enviando uma requisição de login junto com uma requisição para um endpoint sensível e autenticado.

Persistência eterna OAuth2

Existem vários provedores OAuth. Esses serviços permitem que você crie uma aplicação e autentique usuários que o provedor registrou. Para fazer isso, o cliente precisará permitir que sua aplicação acesse alguns dos seus dados dentro do provedor OAuth.
Até aqui, apenas um login comum com google/linkedin/github... onde você é apresentado com uma página dizendo: "Aplicação <InsertCoolName> quer acessar suas informações, você deseja permitir?"

Condição de Corrida em authorization_code

O problema aparece quando você aceita e automaticamente envia um authorization_code para a aplicação maliciosa. Então, essa aplicação abusa de uma Condição de Corrida no provedor de serviço OAuth para gerar mais de um AT/RT (Token de Autenticação/Token de Atualização) a partir do authorization_code para sua conta. Basicamente, ela abusará do fato de você ter aceitado a aplicação acessar seus dados para criar várias contas. Então, se você parar de permitir que a aplicação acesse seus dados, um par de AT/RT será deletado, mas os outros ainda serão válidos.

Condição de Corrida em Refresh Token

Uma vez que você tenha obtido um RT válido, você poderia tentar abusar dele para gerar vários AT/RT e mesmo que o usuário cancele as permissões para a aplicação maliciosa acessar seus dados, vários RTs ainda serão válidos.

RC em WebSockets

Em WS_RaceCondition_PoC você pode encontrar um PoC em Java para enviar mensagens websocket em paralelo para abusar de Condições de Corrida também em Web Sockets.

Referências

Aprenda hacking no AWS do zero ao herói com htARTE (HackTricks AWS Red Team Expert)!

Outras formas de apoiar o HackTricks:


Use Trickest para construir e automatizar fluxos de trabalho com as ferramentas comunitárias mais avançadas do mundo.
Obtenha Acesso Hoje:

{% embed url="https://trickest.com/?utm_campaign=hacktrics&utm_medium=banner&utm_source=hacktricks" %}