Veamos algunos conceptos sobre arquitectura de aplicaciones web para entender cómo son posibles las race conditions.
Modelo Cliente-Servidor
Las aplicaciones web siguen un modelo cliente-servidor:
- Cliente: El cliente es el programa o aplicación que inicia la petición de un servicio. Por ejemplo, cuando el navegador busca una página web y envía una petición al servidor para que le devuelva la página.
- Servidor: El servidor es el programa o sistema que ofrece estos servicios en respuesta a las peticiones entrantes. Por ejemplo, el servidor web responde a una petición HTTP
GETentrante con la página o recurso pedido.
Generalmente, el modelo cliente-servidor corre en una red. El cliente manda la petición a través de esta y el servidor la recibe y la procesa antes de mandar de vuelta el recurso requerido.
Aplicación Web Típica
Una aplicación web sigue una arquitectura multinivel. Dicha arquitectura separa la lógica de la aplicación en varias capas. Los diseños más comunes usan estos tres niveles:
- Nivel de Presentación: En aplicaciones web, este nivel consiste en el navegador del lado del cliente. El navegador renderiza el contenido HTML, CSS y JavaScript.
- Nivel de Aplicación: Este nivel contiene la lógica de la aplicación y su funcionalidad. Recibe peticiones del cliente, las procesa e interactúa con el nivel de datos. Se integra usando lenguajes de programación del lado del servidor como Node.js o PHP.
- Nivel de Datos: Este nivel es responsable de guardar y manipular la información de la aplicación. Las operaciones típicas de bases de datos incluyen CRUD. Se suele implementar usando DBMS, Sistemas de Gestión de Bases de Datos.

Estados
Veamos algunos ejemplos de lógica de negocio antes de seguir:
- Validar y realizar transferencias de dinero.
- Validar códigos de cupones y aplicar descuentos
Validar y Realizar Transferencias de Dinero
Considera el ejemplo de transferir dinero a un amigo o a tu otra cuenta. El proceso del programa hará lo siguiente:
- El usuario hace click en "Confirmar Transacción".
- La aplicación le pide a la base de datos que confirme que el balance de la cuenta cubre el gasto de la transferencia.
- La base de datos responde a la petición:
- Si la cantidad está en los límites, la aplicación lleva a cabo la transacción.
- Si la cantidad no está dentro de los límites, la aplicación muestra un mensaje de error.

En un escenario ideal, el código de arriba conlleva a dos estados de programa:
- Cantidad no enviada.
- Cantidad enviada.

Validar Códigos de Cupones y Aplicar Descuentos
Consideremos el ejemplo de aplicar cupones distintos. El usuario va a su carrito de la compra y añade un cupón para obtener un descuento. Los pasos pueden ser algo como:
- El usuario introduce el código del cupón.
- La aplicación hace una petición a la base de datos para determinar si el cupón es válido y si existen restricciones.
- La base de datos responde con la validación y restricciones:
- Se aplica el descuento si es válido y no aplican restricciones para dicho usuario.
- Salta un mensaje de error si el código es inválido o si existen restricciones que se apliquen al usuario.

El ejemplo de arriba conlleva a dos resultados:
- Se aplica el cupón.
- No se aplica el cupón.

¿Dos Estados? Piensa Otra Vez
Continuemos nuestro análisis de aplicar un cupón. Idealmente, esperamos dos estados: Cupón Aplicado y Cupón no Aplicado. Sin embargo, esto es muy simplista en un entorno sofisticado real. Podemos añadir estados intermediarios: Comprobar la Aplicabilidad del Cupón.

Dependiendo de cómo esté desarrollada la aplicación, podemos añadir más estados. Por ejemplo, comprobar la aplicabilidad de un cupón puede implicar más de un estado: Comprobar la Validez del Cupón y Comprobar las Restricciones del Usuario que lo Introduce.

¿Por qué es importante esto para las Race Conditions?
En el diagrama de estado de arriba, podemos ver que pasamos a través de múltiples estados antes de que el cupón se marque como Aplicado. Dibujemos los estados en un eje:

Hay una ventana de tiempo entre el instante en el que tratamos de añadir un cupón y el instante en el cual el cupón es marcado como aplicado y no puede ser aplicado de nuevo. Mientras que el cupón no sea marcado como aplicado, no habrá controles (lo más seguro) que impidan volver a aplicarlo.
La situación es similar a cuando consideramos los estados del programa que hace una transferencia de dinero. Aunque idealmente serían dos estados, considerando la lógica de negocio, podemos modificar rápidamente el flujo para incluir un estado más.

Sin embargo, incluso si la aplicación web es vulnerable, tenemos otro reto que resolver: el timing. Incluso en aplicaciones vulnerables, el margen de tiempo (ventana de oportunidad) es relativamente pequeño. En la práctica esperamos poder repetir las peticiones necesarias en milisegundos.
¿Cómo podemos conseguir esto? Necesitamos una herramienta como Burp Suite.
