Blog
dark mode light mode Search Archivos descargables
Search

Qué es Race Condition y cómo solucionarlo

race condition

¿Qué es una Race Condition?

Race Condition es una vulnerabilidad que ocurre cuando un sistema que maneja tareas en una secuencia específica es forzado a realizar dos o más operaciones simultáneamente

Esto permite que los atacantes tomen ventaja del intervalo de tiempo que se da entre el inicio de un servicio y el momento en que un control de seguridad surte efecto. 

Con las Race Condition los atacantes pueden acceder a recursos compartidos como bases de datos, archivos y objetos en código para modificarlos simultáneamente y/o afectar la integridad del recurso.

Hackmetrix Insight: esta vulnerabilidad también recibe otros nombres como Time of Check (TOC) Time of Use (TOU) y puedes encontrar más especificaciones en su ficha.

¿Por qué suceden las Race Condition?

Este ataque, que depende de las aplicaciones “multihilo”, puede producirse de dos maneras:

  1. Interferencia de un proceso no confiable: el atacante inserta un fragmento de código entre los pasos de un proceso seguro.
  2. Interferencia de un proceso confiable: el atacante explota dos procesos diferentes que comparten algún estado en común.

Sin los controles adecuados, los diferentes procesos pueden interferir entre sí. Cuando el tiempo de algunas acciones impacta en otras, los eventos pueden ocurrir fuera de secuencia, lo que resulta en un comportamiento anómalo. Este comportamiento anómalo es una Race Condition.

Actualmente, la mayoría de los programas y aplicaciones web utilizan un procesamiento “multihilo”: es decir, son capaces de ejecutar múltiples acciones a la vez.

Si bien esto permite que las aplicaciones sean significativamente más rápidas, también abre la posibilidad de errores si más de un proceso (o subproceso) intenta modificar los mismos datos al mismo tiempo.

Ejemplo de Race Condition

El siguiente código PHP es para retirar dinero o créditos de una cuenta en línea. Suponga que getBalance () y setBalance () son funciones que cargan y almacenan el saldo de una cuenta desde una base de datos MySQL.

Simplemente se obtiene el saldo, se ve si hay suficiente dinero en la cuenta y, de ser así, procede con la transacción y deduce el monto del saldo. 

Sin embargo, esta función, aunque se ejecuta en menos de medio milisegundo, es vulnerable a un ataque de Race Condition que permite a un atacante retirar fondos sin disminuir su saldo, porque los procesos se producen en simultáneo.

A modo de prueba de concepto, levantamos un servidor basado en el pseudo-código para emular el caso en un exchange de crypto.

Si ingresamos la cantidad de dinero a retirar y presionamos el botón RETIRAR, la aplicación eliminará ese monto del total asociado a la cuenta. 

La solicitud HTTP será:

Y la respuesta HTTP será:

No parece haber problema alguno al usar la aplicación, ¿verdad? Sin embargo, debido a su código, es probable que sea vulnerable a ataques de Race Condition.

Para comprobar esto, se pueden utilizar varias herramientas. En este caso se utilizará el software Burpsuite, que mediante una extensión (Turbo Intruder) puede ejecutar múltiples solicitudes HTTP de forma simultánea. 

Esta extensión realizará 100 solicitudes simultáneas y todas retirarán $10.000 (el dinero total de la cuenta).

Una vez ejecutado el programa, podemos ver que 9 solicitudes retiraron $10.000. Esto quiere decir que retiramos $90.000 de una cuenta con solo $10.000.

Si refrescamos el sitio de exchange, veremos en el historial que se extrajeron $10.000 en múltiples ocasiones, a pesar de no tener suficiente dinero en la cuenta para hacerlo.

Esto ocurre debido a que existe la falla de Race Condition en la aplicación, la cual permitió que un número de peticiones HTTP se ejecutarán al mismo tiempo (gracias al multi-hilo) , ocasionando que el usuario pudiera retirar dinero sin que se debite en el total de su cuenta.

Aprovechándose de esta vulnerabilidad, un atacante logró sacar $90.000 de una cuenta que solo tenía $10.000.

¿Cómo se ve la vulnerabilidad en el código?

Ejemplo en Python

En este ejemplo, el script tiene una variable global que se actualiza localmente en un hilo separado y luego se actualiza en la variable global.

Sin embargo, esto puede ejecutarse todo al mismo tiempo, por lo tanto, cada vez que baja esa variable global se aferra a ese valor. 

Y mientras se aferra a ese valor (antes de actualizar la variable global), el otro hilo está tomando una variable global antes que se actualice.

Al ejecutar el script se obtiene un resultado como el siguiente:

Solución

Python tiene una función de bloqueo incorporada para el acceso a los recursos llamada threading.Lock.

Si agregas las funciones lock.acquire y lock.release logras bloquear el acceso al recurso para que ningún otro hilo acceda a ese recurso al mismo tiempo.

Y así al ejecutar el script obtenemos el resultado correcto:

Ejemplo en Go

El siguiente ejemplo funciona de forma similar al de Python: aquí almacena el valor en un hilo separado, lo actualiza y el resultado final debería ser 50.

Pero al ejecutar el script obtenemos un resultado incorrecto.

Solución

Go también posee una función que permite limitar el acceso a un recurso para que otros hilos no interfieran en el proceso.

Esto se logra agregando la librería sync.Mutex y utilizando las funciones Lock() y Unlock().

Entonces, al ejecutar el script obtenemos el resultado correcto:

Los riesgos de las Race Condition

Como definimos antes, la vulnerabilidad Race Condition permite que procesos o acciones ocurran fuera de secuencia y que eso ocasione comportamientos anómalos. 

Sin embargo, esto puede llegar a ser crítico especialmente si se trabaja con operaciones sensibles como aquellas que incluyen información financiera ya que, por ejemplo, podría ocasionar:

  • Ataque de denegación de servicio (DoS): puede ocurrir cuando dos o más hilos esperan el uno al otro para adquirir o liberar un candado en una cadena circular. Esta situación da como resultado un punto muerto, donde todo el sistema de software se detiene debido a que no pueden liberarse y siguen una cadena circular. También, puede darse debido al número de hilos, procesos y acciones, y a su propio comportamiento anómalo, que pueden generar errores que causen la indisponibilidad de ciertos servicios.
  • Fugas de información: si el Race Condition se combina con nombres de recursos predecibles y permisos sueltos, un atacante puede sobrescribir o acceder a datos confidenciales.
  • Pérdidas financieras: en aplicaciones de operaciones financieras o intercambio de divisas, esta vulnerabilidad permite “abrumar” el sistema con miles de solicitudes idénticas a la vez.

¿Cómo prevenir las Race Condition?

Bloqueos o locks

La clave para prevenir esta vulnerabilidad es sincronizar o controlar el orden de las operaciones en las funciones y acciones vulnerables. Como vimos, esto puede lograrse mediante bloqueos o locks.

La mayoría de los lenguajes de programación tienen esta función incorporada para los datos; por ejemplo, Python tiene threading.Lock, Go tiene sync.Mutex. De todas formas, si el lenguaje tiene capacidades asíncronas o de multihilo incorporadas, es muy probable que tenga un mecanismo de bloqueo disponible.

Políticas de seguridad ACID

Por otro lado, te recomendamos asegurar las transacciones de la base de datos implementando políticas y normativas de seguridad como ACID (Atomicity, Consistency, Isolation and Durability).  

En bases de datos, las ACID son las características de los parámetros que permiten clasificar las transacciones de los sistemas de gestión de bases de datos.

  • Atomicity: una transacción se realiza correctamente o se revierte.
  • Consistency: al finalizar una transacción, la base de datos es estructuralmente consistente (sin errores o datos no válidos). De lo contrario, vuelve al estado consistente anterior.
  • Isolation: las transacciones no interfieren entre sí.
  • Durability: El resultado de aplicar una transacción es permanente, incluso en presencia de fallas.

La más relevante de estas para corregir las fallas de Race Condition es Isolation (aislamiento), que garantiza la individualidad de cada transacción y evita que se vean afectados por otras transacciones. Asegura que las transacciones se procesen de forma segura e independiente al mismo tiempo sin interferencias.

Tips adicionales

Finalmente, como medidas de seguridad adicionales, te recomendamos:

  1. Implementar un Token CSRF: un valor único, secreto e impredecible que genera la aplicación del lado del servidor y se transmite al cliente de tal manera que se incluye en la siguiente solicitud realizada por el cliente. Cuando se realiza la siguiente solicitud, la aplicación del lado del servidor la valida o la rechaza dependiendo de si incluye el token esperado o no. 
  2. Mantén la base de datos bien optimizada.
  3. Accede a la información de la base de datos solo cuando se necesite.

Un pentest nunca está de más

Si bien existen herramientas automatizadas que ayudan a encontrar este tipo de fallas, por su naturaleza y comportamiento anómalo, son difíciles de detectar y testear. 

Es por ello que lo más conveniente es realizar un ejercicio de ethical hacking (del tipo penetration testing, pruebas de intrusión o pentest) periódicamente para detectar estas vulnerabilidades y otras fallas de seguridad que pudieran estar presentes tanto en la aplicación como en las redes de tu empresa. 

Para encontrar vulnerabilidades Race Condition, te aconsejamos realizar pruebas de intrusión del tipo white box, en las que el experto obtiene acceso al código fuente y revisa fácilmente las funciones en el código para identificar la lógica que asume acciones sincrónicas. Si encuentra esta función, el experto simplemente llama simultáneamente a esa función una gran cantidad de veces para forzar la probabilidad de que ocurra una colisión.

Ten en cuenta que en el caso de que decidas hacer pruebas de intrusión del tipo black box (cuando el experto no obtiene acceso al código fuente), pueden llegar a ser un poco más complejas ya que es más difícil de encontrar esas funciones (y probarlas aún más), pero esto ofrece un enfoque más cercano a la perspectiva del atacante.

Conclusión

Las fallas de Race Condition se dan debido a la ejecución de procesos que acceden a un mismo recurso simultáneamente, produciendo errores o comportamientos anómalos en el proceso. Debido a su naturaleza, son difíciles de detectar y analizar.

Este tipo de fallas pueden considerarse vulnerabilidades de seguridad críticas cuando los procesos trabajan con información sensible, pudiendo generar elevaciones de privilegios, pérdidas financieras, causar la indisponibilidad de varios servicios, divulgar información, etc.

La mejor forma de prevenir estas fallas es seguir las recomendaciones y consejos que detallamos previamente, y realizar engagements o pruebas de intrusión de forma periódica para encontrar esta y cualquier otro tipo de vulnerabilidad.

Hackmetrix newsletter ciberseguridad