Race Conditions
Last updated
Last updated
Las condiciones de carrera son un tipo común de vulnerabilidad estrechamente relacionada con los defectos de la lógica empresarial. Se producen cuando los sitios web procesan las solicitudes simultáneamente sin las salvaguardias adecuadas. Esto puede llevar a múltiples hilos distintos interactuando con los mismos datos al mismo tiempo, resultando en una "colisión" que causa un comportamiento no intencionado en la aplicación. Un ataque con condición de carrera utiliza peticiones cuidadosamente cronometradas para causar colisiones intencionales y explotar este comportamiento no deseado con fines maliciosos.
El período de tiempo durante el cual es posible una colisión se conoce como la "ventana de la carrera". Esta podría ser la fracción de un segundo entre dos interacciones con la base de datos, por ejemplo.
Al igual que otros defectos lógicos, el impacto de una condición de carrera depende en gran medida de la aplicación y la funcionalidad específica en la que se produce.
En esta sección, aprenderás cómo identificar y explotar diferentes tipos de condiciones de carrera. Te enseñaremos cómo la herramienta incorporada de Burp Suite puede ayudarte a superar los desafíos de realizar ataques clásicos, además de una metodología probada que te permita detectar nuevas clases de condiciones de carrera en procesos ocultos de varios pasos. Estos van mucho más allá del límite por los sobrecostos que usted puede estar familiarizado ya.
El tipo de condición de carrera más conocido le permite superar algún tipo de límite impuesto por la lógica de negocio de la aplicación.
Por ejemplo, considere una tienda en línea que le permita introducir un código promocional durante el checkout para obtener un descuento de una sola vez en su pedido. Para aplicar este descuento, la solicitud podrá realizar los siguientes pasos de alto nivel:
Comítelo que no ha usado este código.
Aplicar el descuento al total del pedido.
Actualice el registro en la base de datos para reflejar el hecho de que ahora ha utilizado este código.
Si más tarde intentas reutilizar este código, las comprobaciones iniciales realizadas al inicio del proceso deberían impedirte hacer esto:
Ahora considere lo que pasaría si un usuario que nunca haya aplicado este código de descuento antes de intentar aplicarlo dos veces casi exactamente al mismo tiempo:
Como puedes ver, la aplicación pasa a través de un sub-estado temporal; es decir, un estado en el que entra y luego sale de nuevo antes de que el procesamiento de solicitudes esté completo. En este caso, el sub-estado comienza cuando el servidor comienza a procesar la primera solicitud, y termina cuando actualiza la base de datos para indicar que ya ha utilizado este código. Esto introduce una pequeña ventana de carrera durante la cual puedes reclamar repetidamente el descuento tantas veces como quieras.
Hay muchas variaciones de este tipo de ataque, incluyendo:
Redeeming a gift card multiple times
Rating a product multiple times
Withdrawing or transferring cash in excess of your account balance
Reusing a single CAPTCHA solution
Bypassing an anti-brute-force rate limit
Los límites son un subtipo de las llamadas fallas de "tiempo de control al tiempo de uso" (TOCTOU). Más tarde en este tema, veremos algunos ejemplos de vulnerabilidades de condiciones de carrera que no caen en ninguna de estas categorías.
El proceso de detección y explotación de las condiciones de carreras de sobrecostos es relativamente sencillo. En términos de alto nivel, todo lo que necesitas hacer es:
Identificar una variable de un solo uso o límite de tarifa que tenga algún tipo de impacto de seguridad u otro propósito útil.
Emite múltiples solicitudes a este endpoint en rápida sucesión para ver si puedes sobrepasar este límite.
El principal reto es la sincronización de las peticiones para que al menos dos ventanas de carreras se alineen, causando una colisión. Esta ventana a menudo es de sólo milisegundos y puede ser aún más corta.
Incluso si usted envía todas las solicitudes exactamente al mismo tiempo, en la práctica hay varios factores externos incontrolables e impredecibles que afectan cuando el servidor procesa cada solicitud y en qué orden.
Para HTTP/1, utiliza la clásica técnica de sincronización de último byte.
Para HTTP/2, utiliza la técnica de ataque de un solo paquete, demostrada por PortSwigger Research en Black Hat USA 2023.
El ataque de un solo paquete le permite neutralizar completamente la interferencia del nervios de la red mediante el uso de un solo paquete TCP para completar 20-30 solicitudes simultáneamente.
Aunque a menudo se pueden utilizar sólo dos solicitudes para desencadenar un exploit, enviar un gran número de solicitudes como esta ayuda a mitigar la latencia interna, también conocida como nervios de la parte del servidor. Esto es especialmente útil durante la fase inicial de descubrimiento. Cubriremos esta metodología con más detalle.
If you select Send group in parallel, Repeater sends the requests from all of the group's tabs at once. This is useful as a way to identify and exploit race conditions.
Repeater synchronizes parallel requests to ensure that they all arrive in full at the same time. It uses different synchronization techniques depending on the HTTP version used:
When sending over HTTP/1, Repeater uses last-byte synchronization. This is where multiple requests are sent over concurrent connections, but the last byte of each request in the group is withheld. After a short delay, these last bytes are sent down each connection simultaneously.
When sending over HTTP/2+, Repeater sends the group using a single packet attack. This is where multiple requests are sent via a single TCP packet.
When you select a tab containing a response to a parallel request, an indicator in the bottom-right corner displays the order in which that response was received within the group (for example, 1/3, 2/3).
Según mi aprendizaje y lo que he estudiado por ejm con golang podemos implementar las waitgroups en conjunción con los mutexes para llevar a cabo la solución de este problema:
Turbo Intruder requiere cierta competencia en Python, pero se adapta a ataques más complejos, como aquellos que requieren múltiples reinicias, tiempo de solicitud escameteado o un número extremadamente grande de solicitudes.
Para usar el ataque de un solo paquete en Turbo Intruder:
Asegúrese de que el objetivo soporta HTTP/2. El ataque de un solo paquete es incompatible con HTTP/1.
Fisean el engine=Engine.BURP2
y concurrentConnections=1
opciones de configuración para el motor de solicitud.
Al hacer cola de sus solicitudes, agruéllas asignándolas a una puerta llamada usando el gate
argumento para la engine.queue()
método.
Para enviar todas las solicitudes en un grupo dado, abra la puerta respectiva con la engine.openGate()
método.
Para más detalles, vea la race-single-packet-attack.py
template proporcionada en el directorio de ejemplos por defecto de Turbo Intruder.
En la práctica, una sola solicitud puede iniciar una secuencia completa de varios pasos entre bastidores, la transición de la aplicación a través de múltiples estados ocultos que entra y luego sale de nuevo antes de que se complete el procesamiento de solicitudes. Nos referiremos a estos como "sub-estados".
Si puede identificar una o más solicitudes de HTTP que causan una interacción con los mismos datos, puede potencialmente abusar de estos sub-estado para exponer variaciones sensibles al tiempo de los tipos de defectos lógicos que son comunes en los flujos de trabajo de varios pasos. Esto permite explotar la condición de la carrera que van mucho más allá de los sobrecostos límite.
Por ejemplo, usted puede estar familiarizado con los flujos de trabajo defectos de autenticación multifactor (MFA) que le permiten realizar la primera parte del inicio de sesión utilizando credenciales conocidas, luego navegar directamente a la aplicación a través de la navegación forzada, evitando efectivamente MFA por completo.
Si no está familiarizado con este exploit, echa un alta laboratorio de bypass 2FA en nuestro tema de vulnerabilidades de autententicación.
El siguiente pseudocódigo demuestra cómo un sitio web podría ser vulnerable a una variación de raza de este ataque:
Como puedes ver, esta es de hecho una secuencia de varios pasos dentro del lapso de una sola solicitud. Lo más importante es que pasa a través de un sub-estado en el que el usuario tiene temporalmente una sesión válida, pero 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 endpoint sensible y autenticado.
Iremos algunos ejemplos más de secuencias ocultas de varios pasos más tarde, y usted será capaz de practicar explotándolos en nuestros laboratorios interactivos. Sin embargo, dado que estas vulnerabilidades son bastante específicas de la aplicación, es importante primero entender la metodología más amplia que necesitarás aplicar para identificarlas eficientemente, tanto en los laboratorios como en la naturaleza.
Probando cada endpoint es poco práctico. Después de mapear el sitio de destino como es normal, puede reducir el número de puntos de end que necesita probar haciéndole las siguientes preguntas:
Es esto crítico la seguridad de endpoint? Muchas variables no tocan la funcionalidad crítica, por lo que no vale la pena probarlas.
Hay algún potencial de colisión? Para una colisión exitosa, normalmente necesitas dos o más solicitudes que desencadenen operaciones en el mismo registro. Por ejemplo, considere las siguientes variaciones de una implementación de restablecimiento de contraseña:
Con el primer ejemplo, es poco probable que la solicitud de reinicias de contraseña paralelas para dos usuarios diferentes cause una colisión, ya que resulta en cambios en dos registros diferentes. Sin embargo, la segunda implementación le permite editar el mismo registro con solicitudes para dos usuarios diferentes.
Cualquier cosa puede ser una pista. Sólo busque alguna forma de desviación de lo que observó durante el benchmarking. Esto incluye un cambio en una o más respuestas, pero no olvide efectos de segundo orden como diferentes contenidos de correo electrónico o un cambio visible en el comportamiento de la aplicación después.
Trate de entender lo que está sucediendo, eliminar las solicitudes superfluas y asegúrese de que todavía puede replicar los efectos. Las condiciones avanzadas de la carrera pueden causar primitivos inusuales y únicos, por lo que el camino al máximo impacto no siempre es inmediatamente obvio. Puede ayudar pensar en cada condición de carrera como una debilidad estructural en lugar de una vulnerabilidad aislada.
Tal vez la forma más intuitiva de estas condiciones raciales son aquellas que implican enviar solicitudes a múltiples puntos finales al mismo tiempo.
Piensa en el fallo de lógica clásico en las tiendas online donde añades un artículo a tu cesta o carrito, paga por ello, luego añade más artículos al carro antes de la cesta antes de la cesta de la fuerza a la página de confirmación del pedido.
Si no está familiarizado con este exploit, echa un registro de validación de flujo de trabajo Insuficiente de nuestro tema de vulnerabilidades de lógica de negocio.
Una variación de esta vulnerabilidad puede ocurrir cuando se realiza la validación del pago y la confirmación del pedido durante el procesamiento de una sola solicitud. La máquina estatal para el estado del pedido podría verse algo como esto:
En este caso, puedes añadir más elementos a tu cesta durante la ventana de la carrera entre cuando se valida el pago y cuando el pedido se confirma finalmente.
Cuando se hace una prueba para las condiciones de carrera multi-punto, puede encontrar problemas tratando de alinear las ventanas de la carrera para cada solicitud, incluso si los envía a todos exactamente al mismo tiempo utilizando la técnica de un solo paquete.
Este problema común es causado principalmente por los dos factores siguientes:
Retraso introducido por la arquitectura de la red - Por ejemplo, puede haber un retraso cuando el servidor frontal establece una nueva conexión con el back-end. El protocolo utilizado también puede tener un gran impacto.
Retrasados introducidos por procesamiento específico de la variable - Diferentes variables varían inherentemente en sus tiempos de procesamiento, a veces significativamente, dependiendo de las operaciones que desencadenen.
Afortunadamente, hay posibles soluciones a ambos temas.
Los retrasos en la conexión trasero no suelen interferir con los ataques de condiciones de carrera porque normalmente retrasan las solicitudes paralelas por igual, por lo que las solicitudes permanecen en sincronía.
Es esencial poder distinguir estos retrasos de los causados por factores específicos de la variable. Una forma de hacerlo es "calentamiento" de la conexión con una o más solicitudes intrasecuentes para ver si esto suaviza los tiempos de procesamiento restantes. En Burp Repeater, puedes intentar añadir un GET
solicitud para la página de inicio del grupo de pestañas, luego usando el grupo Enviar en la opción secuencia (conexión única).
Si la primera solicitud todavía tiene un tiempo de procesamiento más largo, pero el resto de las solicitudes ahora se procesan dentro de una ventana corta, puede ignorar el retraso aparente y continuar probando con normalidad.
Si el calentamiento de la conexión no hace ninguna diferencia, hay varias soluciones a este problema.
Usando Turbo Intruder, puede introducir un breve retraso en el lado del cliente. Sin embargo, como esto implica dividir sus solicitudes de ataque reales a través de varios paquetes TCP, usted no será capaz de utilizar la técnica de ataque de un solo paquete. Como resultado, en objetivos de alto jrúd, es poco probable que el ataque funcione de manera confiable independientemente del retraso que establezcas.
En su lugar, usted puede ser capaz de resolver este problema abusando de una característica de seguridad común.
Los servidores web a menudo retrasen el procesamiento de las solicitudes si se envían demasiado rápido. Al enviar un gran número de solicitudes ficcionadas para activar intencionalmente la tarifa o el límite de recursos, es posible que pueda causar un retraso adecuado del lado del servidor. Esto hace que el ataque de un solo paquete sea viable incluso cuando se requiere una ejecución tardía.
Enviar solicitudes paralelas con diferentes valores a una sola variable a veces puede desencadenar poderosas condiciones de carrera.
Considere un mecanismo de restablecimiento de contraseña que almacena el ID de usuario y resticio de token en la sesión del usuario.
En este escenario, enviar dos solicitudes paralelas de restablecimiento de contraseña de la misma sesión, pero con dos nombres de usuario diferentes, podría potencialmente causar la siguiente colisión:
Note the final state when all operations are complete:
session['reset-user'] = victim
session['reset-token'] = 1234
The session now contains the victim's user ID, but the valid reset token is sent to the attacker.
Para que este ataque funcione, las diferentes operaciones realizadas por cada proceso deben ocurrir en el orden correcto. Probablemente requeriría múltiples intentos, o un poco de suerte, para lograr el resultado deseado.
Las confirmaciones de direcciones de correo electrónico, o cualquier operación basada en correo electrónico, son generalmente un buen objetivo para las condiciones de carrera de un solo punto. Los correos electrónicos a menudo se envían en un hilo de fondo después de que el servidor emite la respuesta HTTP al cliente, haciendo que las condiciones de la carrera sean más probables.
Algunos marcos tratan de prevenir la corrupción accidental de datos utilizando algún tipo de bloqueo de solicitudes. Por ejemplo, el módulo de manejador de sesión nativa 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 enmascarar vulnerabilidades triviamente explotables. Si te das cuenta de que todas tus solicitudes están siendo procesadas secuencialmente, intente enviarlas a cada una de ellas usando un símbolo de sesión diferente.
Muchas aplicaciones crean objetos en múltiples pasos, que pueden introducir un estado medio temporal en el que el objeto es explotable.
Por ejemplo, al registrar un nuevo usuario, una aplicación puede crear el usuario en la base de datos y establecer su clave de API usando dos declaraciones SQL separadas. Esto deja una pequeña ventana en la que el usuario existe, pero su clave API no se presenta.
Este tipo de comportamiento allana el camino para las hazañas por las que se inyecta un valor de entrada que devuelve algo que coincida con el valor de base de datos no inicializado, como una cadena vacía, o null
en JSON, y esto se compara como parte de un control de seguridad.
Los marcos a menudo le permiten pasar en matrices y otras estructuras de datos no encadenadas usando sintaxis no estándar. Por ejemplo, en PHP:
param[]=foo
es equivalente a param = ['foo']
param[]=foo¶m[]=bar
es equivalente a param = ['foo', 'bar']
param[]
es equivalente a param = []
Ruby on Rails te permite hacer algo similar proporcionando una consulta o POST
parámetro con una llave pero sin valor. En otras palabras param[key]
resultados en el siguiente objeto del lado del servidor:
En el ejemplo anterior, esto significa que durante la ventana de la carrera, usted podría potencialmente hacer solicitudes de API autenticadas de la siguiente manera:
Es posible causar colisiones de construcción parcial similares con una contraseña en lugar de una clave de la API. Sin embargo, como las contraseñas se han despreciado, esto significa que necesita inyectar un valor que haga que el hash digerido coincida con el valor no inicializado.
A veces puede que no encuentres condiciones de carrera, pero las técnicas para entregar solicitudes con un momento preciso todavía pueden revelar la presencia de otras vulnerabilidades.
Un ejemplo es cuando se utilizan timestamps de alta resolución en lugar de cuerdas aleatorias criptográficamente seguras para generar fichas de seguridad.
Considere una ficha de restablecimiento de contraseña que sólo se aleatoriza usando una marca de tiempo. En este caso, podría ser posible activar dos resets de contraseñas para dos usuarios diferentes, que ambos utilizan el mismo token. Todo lo que tienes que hacer es timear las peticiones para que generen la misma timestamp.
añade nuevas capacidades poderosas a Burp Repeater que le permiten enviar fácilmente un grupo de solicitudes paralelas de una manera que reduce en gran medida el impacto de uno de estos factores, a saber, el nervio de red. Burp ajusta automáticamente la técnica que utiliza para adaptarse a la versión HTTP compatible con el servidor:
For more information on testing for race conditions, see the Web Security Academy topic.
Además de proporcionar soporte nativo para el ataque de un solo paquete en Burp Repeater, también hemos mejorado la extensión de Turbo Intruder para apoyar esta técnica. Puede descargar la última versión de la .
Para detectar y explotar secuencias ocultas en varios pasos, recomendamos la siguiente metodología, que se resume en el papel blanco de PortSwigger Research.
Para reconocer pistas, primero necesita comparar cómo se comporta el endpoint en condiciones normales. Puede hacerlo en Burp Repeater agrupando todas sus solicitudes y usando el grupo Send en la opción secuencia (conexiones separadas). Para más información, consulte .
A continuación, envíe el mismo grupo de solicitudes a la vez utilizando el ataque de un solo pócket (o sincronización de último byte si HTTP/2 no es compatible) para minimizar el nervio de la red. Puede hacerlo en Burp Repeater seleccionando el grupo Enviar en paralelo. Para obtener más información, consulte . Alternativamente, puede utilizar la extensión Turbo Intruder, que está disponible en la .