Saltearse al contenido

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).

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.

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.

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 conceptual
for i in 1..30: preparar POST /redeem (cupón=X) # sin enviar el último byte
enviar el último byte de las 30 a la vez
-> si varias devuelven "canjeado con éxito", hay carrera
  • 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.
  • 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.

Dinero gratis (doble gasto, cupones ilimitados), saltar límites de negocio, romper unicidad (dos cuentas con el mismo email), escalada de privilegios.

  • 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.

Loguea con timestamp de alta resolución y request-ID; correlaciona peticiones concurrentes sobre el mismo objeto.

  • Atomicidad en la base de datos: usa transacciones con el nivel de aislamiento adecuado; SELECT ... FOR UPDATE (bloqueo pesimista) o versión/WHERE used=false en el UPDATE (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.

Reconcilia el estado (anula canjes/dobles gastos), añade el lock faltante, revisa logs por explotación histórica del mismo patrón.

  • 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.
  • ¿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?