Condiciones de carrera
Una condición de carrera ocurre cuando dos o más peticiones tocan el mismo estado a la vez y el resultado depende del orden exacto en que el servidor las procesa. Entre el momento en que la aplicación comprueba algo (saldo, cupón, stock, límite) y el momento en que actúa sobre ello, hay una ventana —a veces de milisegundos— en la que varias peticiones ven el estado “antiguo” y todas pasan el check. Es el clásico TOCTOU (Time-Of-Check to Time-Of-Use).
Modelo de amenaza
Sección titulada «Modelo de amenaza»El atacante no rompe la lógica: la ejecuta muchas veces simultáneamente antes de que el estado se actualice. Si “canjear cupón” comprueba usado == false y luego pone usado = true, mandar 50 canjes en paralelo puede colar 50 antes de que el primero escriba. El daño es de negocio directo: dinero, stock, límites, unicidad.
Red Team
Sección titulada «Red Team»Descubrimiento
Sección titulada «Descubrimiento»Busca operaciones donde “una vez” o “solo N veces” importe:
- Canjear cupón/gift card, aplicar descuento una sola vez.
- Retirar saldo / transferir (gastar el mismo dinero dos veces).
- Límite de intentos (saltar rate-limit), límite de votos/likes.
- Reservar stock/asiento único; registrar un username/email único.
- Elevar privilegios en un flujo de dos pasos.
A mano: la técnica
Sección titulada «A mano: la técnica»El objetivo es que todas las peticiones lleguen en la misma ventana. Dos métodos clave:
- Single-packet attack (HTTP/2): se envían ~20-30 peticiones en un único paquete TCP, eliminando el jitter de red → llegan prácticamente simultáneas. Es la técnica moderna de referencia (James Kettle).
- Last-byte sync (HTTP/1.1): se envían todas las peticiones menos el último byte, y luego ese último byte de todas a la vez.
Con Burp Repeater basta agrupar las pestañas y usar “Send group in parallel”. Con Turbo Intruder, el script race-single-packet-attack.py.
# patrón conceptualfor i in 1..30: preparar POST /redeem (cupón=X) # sin enviar el último byteenviar el último byte de las 30 a la vez-> si varias devuelven "canjeado con éxito", hay carreraVariantes
Sección titulada «Variantes»- Limit-overrun: gastar un recurso de un solo uso varias veces (el caso de saldo/cupón).
- Multi-endpoint: dos endpoints distintos que tocan el mismo objeto en paralelo (p. ej. aplicar cupón mientras confirmas el pago).
- Single-endpoint colisión de estado: dos actualizaciones del mismo registro que se pisan.
- Partial construction: usar un objeto a medio crear, antes de que se validen sus invariantes.
Herramientas
Sección titulada «Herramientas»- Burp Suite — Repeater con grupos en paralelo (single-packet integrado).
- Turbo Intruder — scripts de single-packet / last-byte sync.
- ffuf/curl + GNU parallel — para casos simples en HTTP/1.1.
Impacto
Sección titulada «Impacto»Dinero gratis (doble gasto, cupones ilimitados), saltar límites de negocio, romper unicidad (dos cuentas con el mismo email), escalada de privilegios.
Blue Team
Sección titulada «Blue Team»Detección
Sección titulada «Detección»- Múltiples peticiones idénticas al mismo recurso en una ventana de milisegundos desde el mismo usuario.
- Estados inconsistentes: saldo negativo, cupón con N usos cuando el máximo era 1.
- Picos de concurrencia sobre un único registro.
Telemetría
Sección titulada «Telemetría»Loguea con timestamp de alta resolución y request-ID; correlaciona peticiones concurrentes sobre el mismo objeto.
Hardening
Sección titulada «Hardening»- Atomicidad en la base de datos: usa transacciones con el nivel de aislamiento adecuado;
SELECT ... FOR UPDATE(bloqueo pesimista) o versión/WHERE used=falseen elUPDATE(optimista) para que solo una gane. - Locks a nivel de aplicación/registro (idempotency keys, bloqueos por usuario/recurso).
- Restricciones únicas en la propia base de datos (unique index) que fallen la segunda escritura.
- Operaciones idempotentes con token de idempotencia para pagos.
- No separar check y acción: hazlo en una sola operación atómica.
Respuesta
Sección titulada «Respuesta»Reconcilia el estado (anula canjes/dobles gastos), añade el lock faltante, revisa logs por explotación histórica del mismo patrón.
CVEs y casos reales
Sección titulada «CVEs y casos reales»- James Kettle — “Smashing the state machine” (PortSwigger, 2023) — introduce el single-packet attack; base de la explotación moderna de carreras web.
- Plataformas de e-commerce / fintech — numerosos reportes en HackerOne de cupones y saldos canjeados múltiples veces.
- Starbucks, programas de puntos — casos públicos de duplicación de saldo por carrera.
- GitLab / Shopify — bugs de carrera en invitaciones y límites reportados públicamente.
Checklist de prueba
Sección titulada «Checklist de prueba»- ¿Hay operaciones de “un solo uso” o “límite N”? (cupón, saldo, votos)
- ¿Enviar 20-30 peticiones en paralelo (single-packet) cuela más de una?
- ¿Se puede gastar el mismo recurso dos veces? (limit-overrun)
- ¿Dos endpoints tocan el mismo objeto sin lock común?
- ¿Existe restricción única en base de datos o solo check en código?
- ¿El check y la acción son una sola transacción atómica?
- ¿Hay idempotency keys en pagos?