Condición de carrera en aplicaciones web

TL; DR Este artículo describe trucos impopulares de condición de carrera que no se usan comúnmente en este tipo de ataque. Con base en los resultados de nuestra investigación, creamos nuestro propio marco para ataques de racepwn.

Vasya quiere transferir 100 dólares que tiene en su cuenta, Petya. Él va a la pestaña de transferencias, lleva el apodo de Petin al campo con la cantidad de fondos que deben transferirse, el número 100. Luego, hace clic en el botón de transferencia. Datos a quién y cuánto se envía a la aplicación web. ¿Qué puede pasar adentro? ¿Qué debe hacer el programador para que todo funcione correctamente?

  • Debe asegurarse de que la cantidad esté disponible para Vasya para la transferencia.

Es necesario obtener el valor del saldo actual del usuario, si es menor que la cantidad que desea transferir, dígaselo. Dado el hecho de que nuestro sitio no proporciona préstamos y no debe entrar en saldo negativo.

  • Reste la cantidad a transferir del saldo del usuario

Es necesario anotar el saldo del usuario actual con la deducción de la cantidad transferida. Era 100, se convirtió en 100-100 = 0.

  • Agregue al saldo del usuario Petya la cantidad que se transfirió.

Petia, por el contrario, era 0, se convirtió en 0 + 100 = 100.

  • ¡Muestre un mensaje al usuario de que está bien hecho!

Al escribir programas, una persona toma los algoritmos más simples, que combina en una sola trama, que será el guión del programa. En nuestro caso, la tarea del programador es escribir la lógica de las transferencias de dinero (puntos, créditos) de una persona a otra en una aplicación web. Guiado por la lógica, puede crear un algoritmo que consta de varias comprobaciones. Imagine que eliminamos todo lo innecesario y compilamos un pseudocódigo.

 (. >= _)  .=.-_ .=.+_ ()  () 

Pero todo estaría bien si todo sucediera a su vez. Pero un sitio puede servir a muchos usuarios al mismo tiempo, y esto no sucede en un solo hilo, porque las aplicaciones web modernas usan multiprocesamiento y subprocesamiento múltiple para el procesamiento de datos en paralelo. Con el advenimiento de los subprocesos múltiples, los programas tienen una vulnerabilidad arquitectónica divertida: condición de carrera (o condición de carrera).

Ahora imagine que nuestro algoritmo funciona simultáneamente 3 veces.

Vasya todavía tiene 100 puntos en su saldo, solo que de alguna manera recurrió a la aplicación web en tres hilos al mismo tiempo (con una cantidad mínima de tiempo entre solicitudes). Las tres transmisiones verifican si el usuario es Petya y si Vasya tiene suficiente saldo para la transferencia. En ese momento cuando el algoritmo verifica el saldo, todavía es igual a 100. Una vez que se completa la verificación, 100 se resta del saldo actual 3 veces y se agrega Pete.

Que tenemos Vasya tiene un saldo negativo en su cuenta (100 - 300 = -200 puntos). Mientras tanto, Petya tiene 300 puntos, aunque en realidad debería ser 100. Este es un ejemplo típico de la explotación de una condición de carrera. Es comparable al hecho de que varias personas pasan una sola vez. A continuación se muestra una captura de pantalla de esta situación de 4lemon



La condición de carrera puede estar en aplicaciones de subprocesos múltiples, así como en las bases de datos en las que trabajan. No necesariamente en aplicaciones web, por ejemplo, este es un criterio común para la escalada de privilegios en los sistemas operativos. Aunque las aplicaciones web tienen sus propias características para una operación exitosa, de lo que quiero hablar.

Operación típica de condición de carrera


Un pirata informático entra en una habitación de cachimba, una búsqueda y un bar, y para él, ¡tienes una condición de carrera! Omar Ganiev

En la mayoría de los casos, el software multiproceso se utiliza como cliente para verificar / operar la condición de la carrera. Por ejemplo, Burp Suite y su herramienta Intruso. Ponen una solicitud HTTP para repetir, instalan muchas transmisiones y activan la inundación. Como por ejemplo en este artículo . O en este . Esta es una forma bastante funcional, si el servidor permite el uso de múltiples hilos a su recurso, y como dicen en los artículos anteriores, si no funciona, intente nuevamente. Pero el hecho es que en algunas situaciones, esto puede no ser efectivo. Especialmente si recuerda cómo tales aplicaciones acceden al servidor.

¿Qué hay en el servidor?


Cada subproceso establece una conexión TCP, envía datos, espera una respuesta, cierra la conexión, se abre de nuevo, envía datos, etc. A primera vista, todos los datos se envían simultáneamente, pero las solicitudes HTTP pueden no llegar sincrónicamente y son inconsistentes debido a la naturaleza de la capa de transporte, la necesidad de establecer una conexión segura (HTTPS) y resolver DNS (no en el caso de eructos) y muchas capas abstracciones que pasan datos antes de ser enviados a un dispositivo de red. Cuando se trata de milisegundos, esto puede desempeñar un papel clave.

Canalización HTTP


Puede recuperar HTTP-Pipelining, en el que puede enviar datos utilizando un único socket. Puedes ver por ti mismo cómo funciona usando la utilidad netcat (tienes GNU / Linux, ¿verdad?).

De hecho, debe usar Linux por muchas razones, porque hay una pila TCP / IP más moderna, que es compatible con los núcleos del sistema operativo. Lo más probable es que el servidor también esté en él.

Por ejemplo, ejecute nc google.com 80 e inserte las líneas allí

 GET / HTTP/1.1 Host: google.com GET / HTTP/1.1 Host: google.com GET / HTTP/1.1 Host: google.com 

Por lo tanto, dentro de una conexión, se enviarán tres solicitudes HTTP y recibirá tres respuestas HTTP. Esta característica se puede utilizar para minimizar el tiempo entre solicitudes.

¿Qué hay en el servidor?


El servidor web recibirá las solicitudes de forma secuencial (palabra clave) y procesará las respuestas en orden de prioridad. Esta característica se puede usar para atacar en varios pasos (cuando es necesario realizar dos acciones secuencialmente en el tiempo mínimo) o, por ejemplo, ralentizar el servidor en la primera solicitud para aumentar el éxito del ataque.
Truco: puede evitar que el servidor procese su solicitud cargando su DBMS, especialmente si se utiliza INSERT / UPDATE. Las solicitudes más pesadas pueden "ralentizar" su carga, por lo tanto, será más probable que gane esta carrera.

Dividir una solicitud HTTP en dos partes


Primero, recuerde cómo se genera la solicitud HTTP. Bueno, como saben, la primera línea es el método, la ruta y la versión del protocolo:

GET / HTTP/1.1

Los siguientes son los encabezados antes del salto de línea:

Host: google.com
Cookie: a=1

Pero, ¿cómo sabe el servidor web que la solicitud HTTP ha finalizado?

Veamos un ejemplo, ingrese nc google.com 80 , y allí

GET / HTTP/1.1
Host: google.com
GET / HTTP/1.1
Host: google.com
, después de presionar ENTRAR, no pasará nada. Haga clic nuevamente; verá la respuesta.

Es decir, para que el servidor web acepte la solicitud HTTP, se necesitan dos avances de línea. Una consulta válida se ve así:

GET / HTTP/1.1\r\nHost: google.com\r\n\r\n

Si este fuera el método POST (no se olvide de Content-Length), la solicitud HTTP correcta sería la siguiente:

POST / HTTP/1.1
Host: google.com
Content-Length: 3

a=1


o

POST / HTTP/1.1\r\nHost: google.com\r\nContent-Length: 3\r\n\r\na=1

Intente enviar una solicitud similar desde la línea de comando:

 echo -ne "GET / HTTP/1.1\r\nHost: google.com\r\n\r\n" | nc google.com 80 

Como resultado, recibirá una respuesta, ya que nuestra solicitud HTTP está completa. Pero si elimina el último \ n carácter, no obtendrá una respuesta.

De hecho, muchos servidores web solo necesitan usar \ n como transferencia, por lo que es importante no intercambiar \ r y \ n, de lo contrario, otros trucos podrían no funcionar.

Que da Puede abrir simultáneamente muchas conexiones a un recurso, enviar el 99% de su solicitud HTTP y dejar el último byte sin enviar. El servidor esperará hasta que llegue al último salto de línea. Después de que quede claro que se ha enviado la parte principal de los datos, envíe el último byte (o varios).

Esto es especialmente importante cuando se trata de una solicitud POST grande, por ejemplo, cuando es necesario cargar archivos. Pero incluso en una solicitud pequeña, esto tiene sentido, ya que entregar unos pocos bytes es mucho más rápido que simultáneamente kilobytes de información.

Demora antes de enviar la segunda parte de la solicitud


Según la investigación de Vlad Roskov , es necesario no solo dividir la solicitud, sino que también tiene sentido retrasar varios segundos entre el envío de la parte principal de los datos y la final. Y todo porque los servidores web comienzan a analizar las solicitudes incluso antes de que las reciban en su totalidad.



¿Qué hay en el servidor?


Por ejemplo, al recibir encabezados de solicitud HTTP, nginx comenzará a analizarlos y guardará en caché la solicitud defectuosa. Cuando llega el último byte, el servidor web tomará la solicitud parcialmente procesada y la enviará directamente a la aplicación, reduciendo así el tiempo de procesamiento de las solicitudes, lo que aumenta la probabilidad de un ataque.

Cómo lidiar con eso


En primer lugar, esto es, por supuesto, un problema arquitectónico, si diseña correctamente una aplicación web, puede evitar tales carreras.

Por lo general, se utilizan los siguientes métodos de control de ataques:


La operación bloquea el acceso al objeto bloqueado en el DBMS hasta que se desbloquea. Otros se paran y esperan al margen. Es necesario trabajar con las cerraduras correctamente, no bloquear nada superfluo.


Transacciones ordenadas (serializables): asegúrese de que las transacciones se realizarán estrictamente secuencialmente, sin embargo, esto puede afectar el rendimiento.


Tome algo (por ejemplo, etcd). Al momento de llamar a las funciones, se crea una entrada con una clave, si no fue posible crear una entrada, entonces ya existe y luego se interrumpe la solicitud. Al final del procesamiento de la solicitud, el registro se elimina.

Y, en general, me gustó el video de Ivan the Hard worker sobre cerraduras y transacciones, muy informativo.

Características de la sesión en condiciones de carrera


Una de las características de las sesiones puede ser que en sí mismo interfiere con la explotación de la raza. Por ejemplo, en PHP, después de session_start (), un archivo de sesión se bloquea y su desbloqueo ocurre solo al final del script (si no hubo llamada a session_write_close ). Si se llama a otro script que usa una sesión en este momento, esperará.

Para eludir esta característica, puede usar un simple truco: autenticar tantas veces como sea necesario. Si la aplicación web le permite crear muchas sesiones para un usuario, simplemente recopile todo el PHPSESSID y haga que cada solicitud tenga su propio identificador.

Proximidad al servidor


Si el sitio en el que desea operar la condición de carrera está alojado en AWS, tome el automóvil en AWS. Si está en DigitalOcean, llévelo allí.

Cuando la tarea es enviar solicitudes y minimizar el intervalo de envío entre ellas, la proximidad inmediata al servidor web será sin duda una ventaja.

Después de todo, hay una diferencia cuando se hace ping al servidor 200 y 10 ms. Y si tienes suerte, incluso puedes terminar en el mismo servidor físico, entonces será un poco más fácil volar :)

Para resumir


Para una condición de carrera exitosa, puede aplicar varios trucos para aumentar la probabilidad de éxito. Envíe múltiples solicitudes de mantenimiento de vida en una, ralentizando el servidor web. Divida la solicitud en varias partes y cree un retraso antes de enviarla. Reduzca la distancia al servidor y la cantidad de abstracciones a la interfaz de red.

Como resultado de este análisis, junto con Michail Badin, desarrollamos la herramienta RacePWN

Consta de dos componentes:

  • Biblioteca C librace, que envía muchas solicitudes HTTP al servidor en el menor tiempo y utiliza la mayoría de las características del artículo

  • Racepwn de utilidades, que acepta la configuración json y generalmente dirige esta biblioteca

RacePWN puede integrarse en otras utilidades (por ejemplo, en Burp Suite), o puede crear una interfaz web para administrar vuelos (aún no puede alcanzar sus manos). Disfruta

Pero en realidad todavía hay espacio para crecer y puede recordar HTTP / 2 y sus perspectivas de ataque. Pero en este momento HTTP / 2, la mayoría de los recursos tienen solo solicitudes de proxy frontal para el antiguo HTTP / 1.1.

¿Quizás conoces algunas otras sutilezas?

El original

Source: https://habr.com/ru/post/460339/


All Articles