¿Necesita Cloud Computing? Empiece ahora

Análisis de tres vulnerabilidades de ejecución remota de código en el tiempo de ejecución de RPC

Akamai Wave Blue

escrito por

Ben Barnea

May 26, 2023

Akamai Wave Blue

escrito por

Ben Barnea

Ben Barnea es experto en seguridad en Akamai con interés y experiencia en la realización de investigaciones de seguridad de bajo nivel y de investigaciones de vulnerabilidades en diversas arquitecturas, entre las que se incluyen Windows, Linux, Internet de las cosas y dispositivos móviles. Le gusta descubrir cómo funcionan los mecanismos complejos pero, sobre todo, cómo fallan.

Es importante recordar que incluso las vulnerabilidades más difíciles de aprovechar son una oportunidad para un atacante hábil (y paciente).

Resumen ejecutivo

  • Ben Barnea, investigador de Akamai descubrió tres vulnerabilidades importantes en el tiempo de ejecución de RPC de Microsoft Windows a los que se asignó CVE-2023-24869, CVE-2023-24908y CVE-2023-23405, todos con una puntuación base de 8,1.

  • Las vulnerabilidades pueden llevar a la ejecución remota de código. Esto afectaría a todas las versiones de Windows (tanto en el escritorio como Windows Server), puesto que la biblioteca de tiempo de ejecución RPC se carga en todos los servidores RPC, que suelen utilizar los servicios de Windows.

  • Las vulnerabilidades son desbordamientos de enteros en tres estructuras de datos que se utilizan en el tiempo de ejecución de RPC.

  • Las vulnerabilidades se divulgaron de forma responsable a Microsoft y se solucionaron en el Patch Tuesday de marzo de 2023.

Introducción

MS-RPC es un protocolo muy utilizado en las redes de Windows, en el que se basan muchos servicios y aplicaciones. Por lo tanto, las vulnerabilidades de MS-RPC pueden tener graves consecuencias. El grupo de inteligencia de seguridad de Akamai ha participado en investigaciones en torno a MS-RPC durante el último año. Hemos encontrado y aprovechado vulnerabilidades, hemos creado herramientas de investigación y hemos escrito sobre algunos de los aspectos internos no documentados hasta ahora del protocolo. 

Mientras que en las publicaciones anteriores del blog nos centrábamos en las vulnerabilidades de los servicios, en esta publicación se analizarán las vulnerabilidades del tiempo de ejecución de RPC, el "motor" de MS-RPC. Estas vulnerabilidades son similares a las vulnerabilidad que descubrimos en mayo de 2022.

Patrón de desbordamiento de enteros

Estas tres nuevas vulnerabilidades tienen un elemento común: todas ocurren a causa de un desbordamiento de enteros en la inserción de tres estructuras de datos:

  1. SIMPLE_DICT (un diccionario que solo guarda valores)

  2. SIMPLE_DICT2 (un diccionario que guarda claves y valores)

  3. Cola 

Todas estas estructuras de datos se implementan mediante una matriz dinámica que crece cada vez que esta se llena. Esto ocurre al asignar el doble de la memoria asignada a la matriz actual. Esta asignación puede provocar un desbordamiento de enteros.

En la figura 1 se presenta el código descompilado del tiempo de ejecución RPC. Muestra el proceso de inserción en la estructura SIMPLE_DICT y la línea de código vulnerable (resaltada) donde se podría desencadenar el desbordamiento de enteros.

En la figura 1 se presenta el código descompilado del tiempo de ejecución RPC. Fig. 1: desbordamiento de enteros en la expansión de la estructura SIMPLE_DICT

Análisis de una vulnerabilidad

Para detectar una vulnerabilidad, tenemos que entender el origen de la misma, averiguar si existe un flujo de acceso a la función vulnerable y cuánto tiempo tarda en activarse.

Para abreviar, describiremos una de las tres vulnerabilidades: la de la estructura de datos de cola. Como los otros desbordamientos de enteros son de naturaleza similar, se puede utilizar el análisis de las siguientes secciones indistintamente.

Descripción del desbordamiento de enteros

Una cola es una estructura de datos PEPS (primero en entrar, primero en salir) sencilla. Las colas en el tiempo de ejecución de RPC se implementan mediante una estructura que contiene una matriz de entradas de cola, la capacidad actual y la posición del último elemento de la cola. 

Cuando se añade una nueva entrada a la cola (si hay ranuras disponibles), todos los elementos avanzan en la matriz y el nuevo elemento se agrega al principio. La posición del último elemento de la cola se incrementa.

Cuando se elimina un elemento de la cola, se extrae el último elemento y la posición del último elemento disminuye (Figura 2). 

Captura de pantalla de cuando se elimina de la cola un elemento Fig. 2: estructura de cola durante las operaciones de poner en cola y eliminar de la cola

Como hemos mencionado anteriormente, la vulnerabilidad se produce al insertar una nueva entrada. Si la matriz dinámica está llena, el código hará lo siguiente:

  • Asigna una nueva matriz con el siguiente tamaño:
    CurrentCapacity * 2 * sizeof(QueueEntry).

  • Copia los elementos antiguos en la nueva matriz.

  • Libera la matriz de elementos antiguos.

  • Duplica la capacidad.

Para un sistema de 32 bits, el desbordamiento se producirá en el cálculo del nuevo tamaño de la matriz:

  • Llenamos la cola con 0x10000000 (!) elementos. 

  • Se produce una expansión. Se calcula el tamaño de la nueva asignación: 0x10000000 * 16.  A medida que se produce el desbordamiento, el nuevo tamaño de asignación es 0

  • Se asigna una matriz de longitud cero.

  • El código copia la matriz de elementos antigua en la nueva matriz más pequeña. Esto dará lugar a una copia brutal (una gran copia lineal).

En un sistema de 64 bits, esta vulnerabilidad no se puede aprovechar porque hay una gran asignación que falla. Por lo tanto, el código sale correctamente sin activar ninguna escritura fuera de los límites. A pesar de que los sistemas de 64 bits no son vulnerables a este problema, son vulnerables a los otros desbordamientos de enteros (en SIMPLE_DICT y SIMPLE_DICT2).

Flujo de código

Una conexión de RPC se representa mediante la clase OSF_SCONNECTION. Cada conexión puede gestionar varias llamadas de cliente (OSF_SCALL), pero solo se permite que se ejecute una llamada cada vez en la conexión, mientras que las demás están en cola. 

Por lo tanto, una función interesante que utilizan las colas es OSF_SCONNECTION::MaybeQueueThisCall.  Recibe la llamada como parte de la distribución de una nueva llamada que ha llegado a la conexión. En este caso, la cola se utiliza para "poner en espera" las llamadas entrantes mientras se procesa otra llamada.

Por lo tanto, tenemos una forma controlada por el usuario de llenar una cola (enviando llamadas de cliente una tras otra), pero esta función establece un requisito: la conexión está procesando una llamada. Esto significa que si queremos llenar la cola, necesitamos una llamada que tarde tiempo en completarse. Mientras se procesa la llamada, se envían varias llamadas nuevas para que llenen la cola. 

¿Qué tipo de llamada de función tarda más en completarse? 

  • La mejor candidata es una función en la que se pueda crear un bucle infinito.

  • La segunda mejor opción es una vulnerabilidad de coacción en la autenticación porque entonces el servidor se conectará a nosotros. De esta forma, tendremos el control sobre el tiempo de respuesta.

  • Como último recurso, se podría utilizar una función compleja con una lógica complicada o una función que procese muchos datos y que, por ello, tarde mucho tiempo en completarse.

Decidimos utilizar nuestra propia vulnerabilidad de coacción en la autenticación.

Tiempo de desencadenamiento

Ya sabemos qué hace falta para llenar la cola y cómo puede hacerse. Pero surge una duda importante: ¿es una forma práctica?

El control que tenemos sobre la variable en la que se produce el desbordamiento de enteros es mínimo (solo podemos hacer que aumente de uno en uno), similar a como ocurre con los desbordamientos de refCount (recuento de referencia). Este tipo de desbordamientos de enteros es menos recomendable que los desbordamientos de enteros en los que se suman o multiplican dos variables que controlamos por completo, o cuando el tamaño añadido se puede controlar de alguna manera (por ejemplo, el tamaño de paquete).

Como se ha comentado anteriormente, debemos asignar 0x10000000 (~268M) elementos. Es decir, muchos.

Al intentar desencadenar la vulnerabilidad en mi equipo, se produjo una tasa de entre unas 15 y 20 llamadas en cola por segundo. Esto significa que se tardaría unos 155 días en activarla en una máquina normal. Esperábamos que causara un número mayor de llamadas en cola por segundo. ¿Hay alguna razón por la que el tiempo de ejecución de RPC es tan lento? ¿No es de varios subprocesos? 

Habíamos supuesto que varios subprocesos procesaban y ponían varias llamadas a la misma conexión en cola simultáneamente. Después de darle algunas vueltas, descubrimos que en la práctica el flujo es un poco diferente.

Gestión de paquetes MS-RPC

Justo antes de enviar una llamada, el código inicia un nuevo subproceso (en el caso de que fuera necesario) y llama a OSF_SCONNECTION::TransAsyncReceive. TransAsyncReceive trata de recibir una solicitud en la misma conexión. A continuación, envía la solicitud al nuevo subproceso (mediante una llamada a CO_SubmitRead). 

El otro subproceso recoge la solicitud de TppWorkerThread y finalmente conduce a  ProcessReceiveComplete, que llama a MaybeQueueThisCall para poner SCALL en la cola de distribución. A continuación, se propaga y trata de recibir una nueva solicitud para esta conexión. 

Por lo tanto, aunque podemos tener varios subprocesos en ejecución, en realidad solo se utiliza uno en la conexión. Esto significa que no podemos añadir llamadas a la cola al mismo tiempo desde varios subprocesos.

Paquetes "residuales"

Intentamos encontrar formas de realizar más llamadas por segundo para minimizar el tiempo que se tarda en desencadenar la vulnerabilidad. Al invertir el código de recepción, hemos observado que si la longitud de un paquete es mayor que la solicitud RPC real en el paquete, el tiempo de ejecución RPC acumula el excedente. Más tarde, al comprobar si hay nuevas solicitudes, no se utiliza inmediatamente el socket. Primero, se comprueba si hay "restos" de paquetes y, de ser así, responde a una nueva solicitud de estos restos.

Esto nos permitió enviar muchos menos paquetes, con el número máximo de solicitudes en cada uno. Al intentar esto, el número de llamadas en cola por segundo apenas cambió, así que tampoco funcionó.

Resumen

A pesar de que es poco probable que se aprovechen estas vulnerabilidades, las hemos añadido a la lista de vulnerabilidades importantes que hemos encontrado en este último año de investigación sobre MS-RPC. Es importante recordar que incluso las vulnerabilidades más difíciles de aprovechar son una oportunidad para un atacante hábil (y paciente). 

Aunque MS-RPC existe desde hace varias décadas, aún tiene vulnerabilidades sin descubrir. 

Esperamos que este descubrimiento anime a otros investigadores a analizar MS-RPC y su superficie de ataque. Nos gustaría agradecer a Microsoft por su rápida respuesta y solución de los problemas.

Nuestro repositorio de GitHub está lleno de herramientas y técnicas para ayudarle a empezar.



Akamai Wave Blue

escrito por

Ben Barnea

May 26, 2023

Akamai Wave Blue

escrito por

Ben Barnea

Ben Barnea es experto en seguridad en Akamai con interés y experiencia en la realización de investigaciones de seguridad de bajo nivel y de investigaciones de vulnerabilidades en diversas arquitecturas, entre las que se incluyen Windows, Linux, Internet de las cosas y dispositivos móviles. Le gusta descubrir cómo funcionan los mecanismos complejos pero, sobre todo, cómo fallan.