hacktricks/pentesting-web/race-condition.md

24 KiB

Condición de Carrera


Utiliza Trickest para construir y automatizar flujos de trabajo con las herramientas comunitarias más avanzadas del mundo.
Obtén Acceso Hoy:

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

Aprende hacking en AWS de cero a héroe con htARTE (HackTricks AWS Red Team Expert)!

Otras formas de apoyar a HackTricks:

Explotando RC

El principal problema al abusar de los RC es que necesitas que las solicitudes se procesen en paralelo con una diferencia de tiempo muy corta (usualmente >1ms). En la siguiente sección, se proponen diferentes soluciones para hacer esto posible.

Ataque de un solo paquete (HTTP/2) / Sincronización del último byte (HTTP/1.1)

HTTP2 permite enviar 2 solicitudes en una sola conexión TCP (mientras que en HTTP/1.1 tienen que ser secuenciales).
El uso de un solo paquete TCP elimina completamente el efecto del jitter de red, por lo que esto claramente tiene potencial para ataques de condición de carrera también. Sin embargo, dos solicitudes no son suficientes para un ataque de carrera fiable debido al jitter del lado del servidor - variaciones en el tiempo de procesamiento de solicitudes de la aplicación causadas por variables incontrolables como la contención de CPU.

Pero, utilizando la técnica de 'sincronización del último byte' de HTTP/1.1 es posible pre-enviar la mayor parte de los datos reteniendo un pequeño fragmento de cada solicitud y luego 'completar' 20-30 solicitudes con un solo paquete TCP.

Para pre-enviar la mayor parte de cada solicitud:

  • Si la solicitud no tiene cuerpo, envía todos los encabezados, pero no establezcas la bandera END_STREAM. Retén un marco de datos vacío con la bandera END_STREAM establecida.
  • Si la solicitud tiene un cuerpo, envía los encabezados y todos los datos del cuerpo excepto el último byte y la bandera END_STREAM. Retén un marco de datos que contenga el último byte.

A continuación, prepárate para enviar los marcos finales:

  • Espera 100ms para asegurarte de que los marcos iniciales se han enviado.
  • Asegúrate de que TCP_NODELAY esté desactivado - es crucial que el algoritmo de Nagle agrupe los marcos finales.
  • Envía un paquete de ping para calentar la conexión local. Si no haces esto, la pila de red del SO colocará el primer marco final en un paquete separado.

Finalmente, envía los marcos retenidos. Deberías poder verificar que aterrizaron en un solo paquete usando Wireshark.

{% hint style="info" %} Nota que no funciona para archivos estáticos en ciertos servidores, pero los archivos estáticos no son relevantes para ataques de RC. {% endhint %}

Utilizando esta técnica, puedes hacer que 20-30 solicitudes lleguen al servidor simultáneamente - independientemente del jitter de red:

Adaptándose a la arquitectura del objetivo

Es importante notar que muchas aplicaciones están detrás de un servidor frontal, y estos pueden decidir reenviar algunas solicitudes a través de conexiones existentes al back-end, y crear nuevas conexiones para otras.

Como resultado, es importante no atribuir tiempos de solicitud inconsistentes al comportamiento de la aplicación, como mecanismos de bloqueo que solo permiten que un solo hilo acceda a un recurso a la vez. Además, el enrutamiento de solicitudes del front-end a menudo se hace en base a una conexión por conexión, por lo que puedes suavizar el tiempo de las solicitudes realizando un calentamiento de conexión del lado del servidor - enviando algunas solicitudes inconsecuentes por tu conexión antes de realizar el ataque (esto es solo enviar varias solicitudes antes de comenzar el ataque real).

Mecanismos de bloqueo basados en sesión

Algunos frameworks intentan prevenir la corrupción accidental de datos utilizando alguna forma de bloqueo de solicitudes. Por ejemplo, el módulo de manejador de sesiones nativo de PHP solo procesa una solicitud por sesión a la vez.

Es extremadamente importante detectar este tipo de comportamiento ya que de lo contrario puede ocultar vulnerabilidades trivialmente explotables. Si notas que todas tus solicitudes se procesan secuencialmente, intenta enviar cada una de ellas utilizando un token de sesión diferente.

Abusando de límites de tasa o recursos

Si el calentamiento de la conexión no hace ninguna diferencia, hay varias soluciones a este problema.

Usando Turbo Intruder, puedes introducir un corto retraso del lado del cliente. Sin embargo, como esto implica dividir tus solicitudes de ataque reales en múltiples paquetes TCP, no podrás utilizar la técnica de ataque de un solo paquete. Como resultado, en objetivos con alto jitter, el ataque es poco probable que funcione de manera fiable, independientemente del retraso que establezcas.

En cambio, podrías ser capaz de resolver este problema abusando de una característica de seguridad común.

Los servidores web a menudo retrasan el procesamiento de las solicitudes si se envían demasiadas demasiado rápido. Al enviar un gran número de solicitudes ficticias para desencadenar intencionalmente el límite de tasa o recursos, podrías ser capaz de causar un retraso adecuado del lado del servidor. Esto hace viable el ataque de un solo paquete incluso cuando se requiere una ejecución retrasada.

{% hint style="warning" %} Para más información sobre esta técnica, consulta el informe original en https://portswigger.net/research/smashing-the-state-machine {% endhint %}

Ejemplos de Ataque

  • Tubo Intruder - Ataque de un solo paquete HTTP2 (1 endpoint): Puedes enviar la solicitud a Turbo intruder (Extensions -> Turbo Intruder -> Send to Turbo Intruder), puedes cambiar en la solicitud el valor que quieres forzar bruscamente por %s como en csrf=Bn9VQB8OyefIs3ShR2fPESR0FzzulI1d&username=carlos&password=%s y luego seleccionar examples/race-single-packer-attack.py del menú desplegable:

Si vas a enviar diferentes valores, podrías modificar el código con este que usa una lista de palabras del portapapeles:

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

{% hint style="warning" %} Si la web no soporta HTTP2 (solo HTTP1.1) usa Engine.THREADED o Engine.BURP en lugar de Engine.BURP2. {% endhint %}

  • Tubo Intruder - Ataque de paquete único HTTP2 (Varios endpoints): En caso de que necesites enviar una solicitud a 1 endpoint y luego múltiples a otros endpoints para activar el RCE, puedes cambiar el script race-single-packet-attack.py con 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)
  • También está disponible en Repeater a través de la nueva opción 'Enviar grupo en paralelo' en Burp Suite.
  • Para limit-overrun podrías simplemente añadir la misma solicitud 50 veces en el grupo.
  • Para connection warming, podrías añadir al inicio del grupo algunas solicitudes a alguna parte no estática del servidor web.
  • Para retrasar el proceso entre el procesamiento de una solicitud y otra en pasos de 2 subestados, podrías añadir solicitudes adicionales entre ambas solicitudes.
  • Para un RC de multi-endpoint podrías comenzar enviando la solicitud que va al estado oculto y luego 50 solicitudes justo después que explotan el estado oculto.

Raw BF

Antes de la investigación previa, estos eran algunos payloads utilizados que simplemente intentaban enviar los paquetes lo más rápido posible para causar un RC.

  • Repeater: Revisa los ejemplos de la sección anterior.
  • Intruder: Envía la solicitud a Intruder, establece el número de hilos a 30 dentro del menú de Opciones y, selecciona como payload Null payloads y genera 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())

Metodología RC

Exceso de límite / TOCTOU

Este es el tipo más básico de condición de carrera donde las vulnerabilidades que aparecen en lugares que limitan la cantidad de veces que puedes realizar una acción. Como usar el mismo código de descuento varias veces en una tienda web. Un ejemplo muy fácil se puede encontrar en este informe o en este bug.

Hay muchas variaciones de este tipo de ataque, incluyendo:

  • Canjear una tarjeta de regalo varias veces
  • Valorar un producto varias veces
  • Retirar o transferir efectivo en exceso del saldo de tu cuenta
  • Reutilizar una única solución CAPTCHA
  • Evitar un límite de tasa anti-fuerza bruta

Subestados ocultos

Otros RC más complicados explotarán subestados en el estado de la máquina que podrían permitir a un atacante abusar de estados a los que nunca se suponía que tuviera acceso, pero hay una pequeña ventana para que el atacante acceda a él.

  1. Predecir subestados ocultos e interesantes potenciales

El primer paso es identificar todos los puntos finales que escriben en él o leen datos de él y luego usan esos datos para algo importante. Por ejemplo, los usuarios pueden almacenarse en una tabla de base de datos que es modificada por el registro, ediciones de perfil, inicio de restablecimiento de contraseña y finalización de restablecimiento de contraseña.

Podemos usar tres preguntas clave para descartar puntos finales que probablemente no causen colisiones. Para cada objeto y los puntos finales asociados, pregúntate:

  • ¿Cómo se almacena el estado?

Los datos almacenados en una estructura de datos persistente del lado del servidor son ideales para la explotación. Algunos puntos finales almacenan su estado completamente del lado del cliente, como los restablecimientos de contraseña que funcionan enviando un JWT por correo electrónico; estos se pueden omitir con seguridad.

Las aplicaciones a menudo almacenan algún estado en la sesión del usuario. Estos a menudo están algo protegidos contra subestados - más sobre eso más adelante.

  • ¿Estamos editando o añadiendo?

Las operaciones que editan datos existentes (como cambiar la dirección de correo electrónico principal de una cuenta) tienen un amplio potencial de colisión, mientras que las acciones que simplemente se añaden a los datos existentes (como agregar una dirección de correo electrónico adicional) probablemente no sean vulnerables a nada más que ataques de exceso de límite.

  • ¿En qué está basada la operación?

La mayoría de los puntos finales operan sobre un registro específico, que se busca utilizando una 'clave', como un nombre de usuario, token de restablecimiento de contraseña o nombre de archivo. Para un ataque exitoso, necesitamos dos operaciones que usen la misma clave. Por ejemplo, imagina dos implementaciones plausibles de restablecimiento de contraseña:

  1. Buscar pistas

En este punto es hora de lanzar algunos ataques RC sobre los puntos finales potencialmente interesantes para tratar de encontrar resultados inesperados en comparación con los regulares. Cualquier desviación de la respuesta esperada, como un cambio en una o más respuestas, o un efecto secundario como contenidos de correo electrónico diferentes o un cambio visible en tu sesión, podría ser una pista que indica que algo está mal.

  1. Probar el concepto

El paso final es probar el concepto y convertirlo en un ataque viable.

Cuando envías un lote de solicitudes, puedes encontrar que un par de solicitudes tempranas desencadenan un estado final vulnerable, pero las solicitudes posteriores lo sobrescriben/invalidan y el estado final es inexplotable. En este escenario, querrás eliminar todas las solicitudes innecesarias: dos deberían ser suficientes para explotar la mayoría de las vulnerabilidades. Sin embargo, reducir a dos solicitudes hará que el ataque sea más sensible al tiempo, por lo que es posible que necesites intentar el ataque varias veces o automatizarlo.

Ataques Sensibles al Tiempo

A veces es posible que no encuentres condiciones de carrera, pero las técnicas para entregar solicitudes con un tiempo preciso aún pueden revelar la presencia de otras vulnerabilidades.

Un ejemplo es cuando se utilizan marcas de tiempo de alta resolución en lugar de cadenas aleatorias seguras criptográficamente para generar tokens de seguridad.

Considera un token de restablecimiento de contraseña que solo se aleatoriza usando una marca de tiempo. En este caso, podría ser posible desencadenar dos restablecimientos de contraseña para dos usuarios diferentes, que ambos usen el mismo token. Todo lo que necesitas hacer es cronometrar las solicitudes para que generen la misma marca de tiempo.

{% hint style="warning" %} Para confirmar, por ejemplo, la situación anterior, podrías simplemente pedir 2 tokens de restablecimiento de contraseña al mismo tiempo (usando ataque de paquete único) y verificar si son iguales. {% endhint %}

Revisa el ejemplo en este laboratorio.

Estudios de caso de subestados ocultos

Pagar y añadir un artículo

Revisa este laboratorio para ver cómo pagar en una tienda y añadir un artículo extra que no necesitarás pagar.

Confirmar otros correos electrónicos

La idea es verificar una dirección de correo electrónico y cambiarla por otra diferente al mismo tiempo para averiguar si la plataforma verifica la nueva cambiada.

Según este informe Gitlab era vulnerable a una toma de control de esta manera porque podría enviar el token de verificación de correo electrónico de un correo a otro.

También puedes revisar este laboratorio para aprender sobre esto.

Estados de base de datos ocultos / Evitar la confirmación

Si se utilizan 2 escrituras diferentes para añadir información dentro de una base de datos, hay una pequeña porción de tiempo donde solo se ha escrito el primer dato dentro de la base de datos. Por ejemplo, al crear un usuario, el nombre de usuario y la contraseña podrían ser escritos y luego el token para confirmar la cuenta recién creada se escribe. Esto significa que por un pequeño tiempo el token para confirmar una cuenta es nulo.

Por lo tanto, registrar una cuenta y enviar varias solicitudes con un token vacío (token= o token[]= o cualquier otra variación) para confirmar la cuenta de inmediato podría permitir confirmar una cuenta donde no controlas el correo electrónico.

Revisa este laboratorio para ver un ejemplo.

Evitar 2FA

El siguiente pseudo-código demuestra cómo un sitio web podría ser vulnerable a una variación de ataque de carrera de esta manera:

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 puedes ver, esto es de hecho una secuencia de varios pasos dentro del lapso de una sola solicitud. Lo más importante es que pasa por un subestado en el que el usuario temporalmente tiene una sesión válida iniciada, pero la MFA aún no se está aplicando. Un atacante podría potencialmente explotar esto enviando una solicitud de inicio de sesión junto con una solicitud a un punto final sensible y autenticado.

Persistencia eterna de OAuth2

Hay varios proveedores de OAuth. Estos servicios te permitirán crear una aplicación y autenticar usuarios que el proveedor tiene registrados. Para hacerlo, el cliente necesitará permitir que tu aplicación acceda a algunos de sus datos dentro del proveedor de OAuth.
Hasta aquí, solo un inicio de sesión común con Google/LinkedIn/GitHub... donde te aparece una página que dice: "La aplicación <InsertCoolName> quiere acceder a tu información, ¿quieres permitirlo?"

Condición de carrera en authorization_code

El problema aparece cuando lo aceptas y automáticamente se envía un authorization_code a la aplicación maliciosa. Luego, esta aplicación abusa de una Condición de Carrera en el proveedor de servicios de OAuth para generar más de un AT/RT (Token de Autenticación/Token de Actualización) a partir del authorization_code de tu cuenta. Básicamente, abusará del hecho de que has aceptado que la aplicación acceda a tus datos para crear varias cuentas. Entonces, si dejas de permitir que la aplicación acceda a tus datos, se eliminará un par de AT/RT, pero los otros seguirán siendo válidos.

Condición de carrera en Refresh Token

Una vez que hayas obtenido un RT válido, podrías intentar abusar de él para generar varios AT/RT y aunque el usuario cancele los permisos para que la aplicación maliciosa acceda a sus datos, varios RT seguirán siendo válidos.

RC en WebSockets

En WS_RaceCondition_PoC puedes encontrar un PoC en Java para enviar mensajes de WebSocket en paralelo para abusar de Condiciones de Carrera también en Web Sockets.

Referencias

Aprende hacking en AWS de cero a héroe con htARTE (HackTricks AWS Red Team Expert)!

Otras formas de apoyar a HackTricks:


Usa Trickest para construir y automatizar flujos de trabajo con las herramientas comunitarias más avanzadas.
Obtén acceso hoy:

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