Caché gélida: eludiendo la seguridad de la interfaz de RPC mediante la explotación de la caché
Resumen ejecutivo
Los investigadores de Akamai han descubierto dos vulnerabilidades importantes en los servicios de RPC de Microsoft Windows a las que se ha asignado la CVE-2022-38034 y la CVE-2022-38045 con puntuaciones base de 4,3 y 8,8 respectivamente.
Las vulnerabilidades aprovechan un error de diseño que permite eludir las devoluciones de llamadas de seguridad de MS-RPC a través del almacenamiento en caché.
Hemos confirmado que la vulnerabilidad existe en equipos Windows 10 y Windows 11 a los que no se han aplicado parches.
Las vulnerabilidades se divulgaron de forma responsable a Microsoft y se solucionaron en el Patch Tuesday de octubre.
El proceso de detección de las vulnerabilidades se realiza con la ayuda de una herramienta de automatización y una metodología desarrolladas por los investigadores de Akamai.
Proporcionamos una prueba de concepto de las vulnerabilidades y las herramientas utilizadas en la investigación en el repositorio de kits de herramientas de RPC.
Introducción
MS-RPC es uno de los pilares del sistema operativo Windows. Lanzado en la década de los 90 del siglo pasado, desde entonces se ha integrado profundamente en la mayoría de las partes del sistema. ¿El administrador de servicios? RPC. ¿LSASS? RPC. ¿COM? RPC. Incluso algunas operaciones de dominios en el controlador de dominio emplean RPC. Dado lo habitual que se ha hecho MS-RPC, cabría esperar que se hubiera sometido a escrutinio, documentado e investigado de forma intensiva.
La realidad es que no. Aunque la documentación de Microsoft sobre el uso de RPC es bastante buena, no se ha escrito mucho más sobre el tema, e incluso menos han escrito los investigadores que han analizado RPC, especialmente en cuanto a su seguridad. Esto podría atribuirse probablemente al hecho de que RPC (no solo MS-RPC, aunque no hay duda de que Microsoft se subió al carro) es extremadamente compleja, lo que convierte su investigación y comprensión en tareas abrumadoras.
Pero siempre estamos dispuestos a aceptar el reto, así que decidimos sumergirnos primero en el profundo mar de MS-RPC. No solo porque es un tema de investigación interesante, sino también por sus implicaciones para la seguridad: incluso ahora, las técnicas de ataque habituales emplean RPC (T1021.003 se produce a través de MS-COM, T1053.005 es MS-TSCH, T1543.003 es MS-SCMRpor dar algunos ejemplos). Hay mecanismos de seguridad integrados en MS-RPC, pero ¿qué ocurre si hay vulnerabilidades que pueden permitir que se eludan o exploten, o permitir que un servicio de RPC expuesto se explote para afectar a los equipos de una forma no deseada?
De hecho, conseguimos encontrar una forma de eludir un mecanismo de seguridad a través del almacenamiento en caché. A través de ella, encontramos algunos servicios que se podrían explotar para hacerse con más privilegios en servidores remotos, sin necesidad de muchas condiciones (que analizaremos más adelante en esta publicación). En este momento, podemos compartir información sobre dos ejemplos reales de posible explotación, WksSvc y SrvSvc. Publicaremos las actualizaciones sobre las otras vulnerabilidades que hemos detectado una vez que haya finalizado su proceso de divulgación.
En esta publicación del blog, nos centraremos en el mecanismo de devolución de llamadas de seguridad de los servidores RPC, cómo se puede eludir mediante el almacenamiento en caché y cómo automatizamos nuestra investigación para señalar los servicios de Windows como potencialmente vulnerables. Nuestras herramientas de automatización, además de sus resultados sin procesar, también se pueden encontrar en nuestro kit de herramientas de RPC, que hemos compartido en nuestro repositorio de GitHub. Nuestro repositorio incluye también enlaces a otras referencias útiles y al trabajo realizado por otros investigadores en los que confiamos.
Devoluciones de llamadas de seguridad
Antes de hablar sobre las vulnerabilidades en sí, es importante arrojar algo de luz sobre uno de los mecanismos de seguridad más fundamentales que implementa MS-RPC: las devoluciones de llamadas de seguridad. Las devoluciones de llamadas de seguridad permiten a los desarrolladores de servidores RPC restringir el acceso a una interfaz de RPC. Esto les permite aplicar su propia lógica para permitir el acceso a usuarios específicos, imponer tipos de autenticación o transporte, o evitar el acceso a "opnums" específicos (las funciones expuestas por el servidor se representan mediante "opnums"; es decir, números de operaciones).
Esta devolución de llamada se activa mediante el tiempo de ejecución de RPC cada vez que el cliente invoca una función expuesta en el servidor.
En nuestra investigación, nos centramos en la interacción remota cliente-servidor. Mencionamos esto porque las implementaciones de código del lado del servidor del tiempo de ejecución de RPC difieren entre un terminal ALPC y un terminal remoto, como un canal con nombre.
Almacenamiento en caché
El tiempo de ejecución de RPC implementa el almacenamiento en caché del resultado de la devolución de llamada de seguridad para mejorar el rendimiento y la utilización. Eso significa básicamente que el tiempo de ejecución intentará utilizar una entrada almacenada en caché antes de llamar a la devolución de llamada de seguridad cada vez. Vamos a explicar detalladamente la implementación.
Antes de que RPC_INTERFACE::DoSyncSecurityCallback invoque la devolución de llamada de seguridad, primero comprueba si existe una entrada de caché. Para ello, llama a OSF_SCALL::FindOrCreateCacheEntry.
OSF_SCALL::FindOrCreateCacheEntry realiza las siguientes operaciones:
Obtiene el contexto de seguridad del cliente de SCALL (un objeto que representa una llamada de cliente).
Obtiene el diccionario de almacenamiento en caché del contexto de seguridad del cliente.
Utiliza el puntero de la interfaz como clave para el diccionario. El valor es la entrada de caché.
Si no existe una entrada de caché, crea una.
Una entrada de caché tiene tres campos importantes: el número de procedimientos en la interfaz, un mapa de bits y la generación de la interfaz.
Durante la vida útil de un servidor RPC, la interfaz puede cambiar. Por ejemplo, si el servidor llama a RpcServerRegisterIf3 en una interfaz existente. Esta, a su vez, llama a RPC_INTERFACE::UpdateRpcInterfaceInformation, que actualiza la interfaz e incrementa la generación de esta. De esta manera, el almacenamiento en caché sabe que es necesario "restablecer", ya que las entradas de caché podrían ser de la interfaz antigua.
El mecanismo de almacenamiento en caché puede funcionar en dos modos: en función de la interfaz (que es el comportamiento predeterminado) y en función de la llamada.
Almacenamiento en caché basado en la interfaz
En este modo, el almacenamiento en caché funciona según la interfaz. Esto significa que, desde el punto de vista del almacenamiento en caché, no hay diferencia entre dos llamadas a dos funciones diferentes, ya que están en la misma interfaz.
Para saber si la entrada de caché se puede usar en lugar de llamar a la devolución de llamada de seguridad, el tiempo de ejecución de RPC compara la generación de la interfaz que está guardada en la entrada de caché con la generación de la interfaz real. Debido a que la inicialización de la entrada de caché pone a cero la generación de la interfaz, la primera vez que se realice la comparación, las generaciones de interfaz serán diferentes y, por lo tanto, se llamará a la devolución de llamada de seguridad. Si la devolución de llamada se devuelve correctamente, el tiempo de ejecución de RPC actualizará la generación de la interfaz de la entrada de caché (y, por lo tanto, se "marcará" como entrada de caché correcta, que permite acceder a la interfaz sin llamar de nuevo a la devolución de llamada de seguridad). La próxima vez que el cliente llame a una función en la misma interfaz, se utilizará la entrada de caché.
Almacenamiento en caché basado en la llamada
Este modo se utiliza cuando la interfaz de RPC está registrada con el indicador RPC_IF_SEC_CACHE_PER_PROC. En este modo, el almacenamiento en caché se basa en un mapa de bits que realiza un seguimiento de a qué procedimientos ha permitido acceder la devolución de llamada de seguridad. Por lo tanto, si el cliente ha invocado la función Foo y la devolución de llamada de seguridad se ha realizado correctamente, tendremos una entrada de caché para Foo. Si el cliente invoca Bar, se volverá a llamar a la devolución de llamada de seguridad.
Requisitos de almacenamiento en caché
¿Qué necesitamos para que el almacenamiento en caché funcione? Primero, necesitamos aclarar alguna terminología. MS-RPC representa una conexión lógica entre un cliente y un servidor utilizando un identificador de enlace. El cliente y el servidor pueden manipular los datos de enlace utilizando funciones designadas.
Un enlace se puede autenticar. Esto ocurre cuando el servidor registra la información de autenticación (llamando a RpcServerRegisterAuthInfo) y luego el cliente establece la información de autenticación en el enlace. Esto permite al servidor recuperar la información sobre la identidad del cliente. El resultado de este proceso de autenticación es la creación de un objeto de contexto de seguridad para el cliente.
Todo el mecanismo de almacenamiento en caché se basa en este contexto de seguridad. Esto significa que si el enlace no se autentica, no se crea un contexto de seguridad para el cliente y, por lo tanto, no se habilita el almacenamiento en caché. Para que el almacenamiento en caché funcione, tanto el servidor como el cliente deben registrarse y establecer la información de autenticación.
¿Pero qué ocurre si el servidor no ha registrado la información de autenticación? ¿Podemos seguir habilitando el almacenamiento en caché? Presentamos la multiplexación.
Multiplexación
Hasta Windows 10, versión 1703, un servicio podía compartir el mismo proceso svchost con otros servicios. Este comportamiento afecta a la seguridad de MS-RPC, ya que algunos objetos de tiempo de ejecución de RPC se comparten entre todas las interfaces. Por ejemplo, al registrar un terminal (como el puerto TCP 7777), este terminal se puede usar para acceder a todas las interfaces que se ejecutan en el mismo proceso. Por lo tanto, otros servicios a los que se espera acceder localmente pueden tener ahora acceso remoto. Esto también se describe en esta página de Microsoft.
Aunque el hecho de que los terminales están multiplexados ya es bastante conocido y está documentado, nos gustaría presentar otro comportamiento similar: la multiplexación de SSPI. Como parte del registro de la información de autenticación, el servidor debe especificar el servicio de autenticación que se va a usar. El servicio de autenticación es un proveedor de asistencia de seguridad (SSP), que es un paquete que procesa la información de autenticación recibida del cliente. En la mayoría de los casos, será NTLM SSP, Kerberos SSP o Microsoft Negotiate SSP, que elige la mejor opción disponible entre Kerberos y NTLM.
El tiempo de ejecución de RPC guarda la información de autenticación globalmente. Esto significa que si dos servidores RPC comparten el mismo proceso y uno de ellos registra información de autenticación, el otro servidor también tendrá información de autenticación. Ahora un cliente puede autenticar el enlace al acceder a cada uno de los servidores. Desde la perspectiva de la seguridad, los servidores que no han registrado la información de autenticación y, por lo tanto, puede que no hayan esperado que los clientes autentiquen el enlace o que se realice el almacenamiento en caché, puede que se vean forzados a hacerlo.
CVE-2022-38045 — srvsvc
Equipados con nuestros nuevos conocimientos sobre las devoluciones de llamadas de seguridad y el almacenamiento en caché de RPC, nos dispusimos a comprobar si podíamos explotar realmente el mecanismo en la vida real. Regresamos a srvsvc, donde encontramos una vulnerabilidad por un paso en el pasado.
Srvsvc expone la interfaz de MS-SRVS. El servicio "Servidor" (también llamado LanmanServer) es un servicio de Windows que es responsable de la gestión de los recursos compartidos SMB. Los recursos compartidos son recursos (archivos, impresoras y árboles de directorios) a los que un servidor del sistema de archivos de Internet común (CIFS) puede acceder a través de la red. Básicamente, los recursos compartidos de red permiten a los usuarios utilizar otros dispositivos de la red para realizar varias tareas diarias.
Al analizar la devolución de llamada de seguridad de Srvsvc, observamos que la función podía tener otra vulnerabilidad, diferente de la que ya habíamos encontrado. Vamos a echar un vistazo a la lógica de las devoluciones de llamadas de seguridad:
Como podemos observar arriba, la devolución de llamada de seguridad de srvsvc tiene la siguiente lógica:
Si un cliente remoto intenta acceder a una función en el rango de 64-73 (inclusive), se deniega el acceso
Si un cliente remoto, que no es una cuenta de clúster, intenta acceder a una función en el rango de 58–63 (inclusive), se deniega el acceso
Por lo tanto, en esencia, se impide que los clientes remotos accedan a estas funciones particulares de la interfaz. Esta comprobación de rango indica que las funciones restringidas son sensibles y solo deben ser invocadas por procesos (locales) esperados.
Aunque esta comprobación intenta evitar el acceso remoto a estas funciones, un atacante remoto puede eludirla explotando el almacenamiento en caché. Primero, un atacante remoto invoca una función que no está en este rango, una función disponible de forma remota. Debido a que la función de devolución de llamada de seguridad devuelve RPC_S_OK, el tiempo de ejecución de RPC almacena en caché el resultado como correcto. Como la interfaz no está registrada con el indicador RPC_IF_SEC_CACHE_PER_PROC, el almacenamiento en caché se realizará en función de la interfaz. Como resultado, la próxima vez que el atacante llame a cualquier función en la misma interfaz, se garantizará el uso y acceso a la entrada de caché. Esto significa que el atacante ahora puede invocar funciones locales a las que no debería tener acceso y no se llamará a la devolución de llamada de seguridad.
Srvsvc no registra la información de autenticación y, por lo tanto, en circunstancias normales, los clientes no pueden autenticar el enlace y no se habilita el almacenamiento en caché. Al parecer, Srvsvc comparte el mismo proceso svchost con otros servicios cuando el equipo servidor tiene menos de 3,5 GB de RAM. Los servicios "AD Harvest Sites and Subnets Service" y "Remote Desktop Configuration Service" registran la información de autenticación y, por lo tanto, srvsvc ahora es vulnerable a los ataques a la caché.
En esta situación, un atacante puede acceder a funciones restringidas con los "opnums" 58–74. Una de las cosas que un atacante puede hacer con estas funciones es forzar la autenticación del equipo remoto.
A la búsqueda del tesoro
Tras comprender que explotar el mecanismo de almacenamiento en caché de la devolución de llamada de seguridad puede presentar vulnerabilidades reales, decidimos intentar encontrar otras interfaces que podrían ser vulnerables a un ataque al almacenamiento en caché. Pero encontrar todas las interfaces manualmente sería una tarea ardua y prolongada, así que quisimos encontrar una forma de automatizarla.
Hay dos enfoques que podemos adoptar al buscar interfaces de RPC: a través de los procesos actuales en ejecución o a través del sistema de archivos.
Con los procesos en ejecución, podemos observar los servidores RPC que ya están cargados en la memoria, ya sea en un servidor remoto consultando al asignador de terminales remotos (con rpcmap o rpcdump, por ejemplo) o localmente (con herramientas como RpcView o RpcEnum). Sin embargo, hay un problema con este enfoque: Pasaremos por alto cualquier interfaz que no esté cargada actualmente y no podremos ver las interfaces de cliente, ya que no están registradas.
Como alternativa, podemos explorar el sistema de archivos de Windows y buscar las interfaces de RPC recopiladas dentro de este. Para cada interfaz, analizamos su información de registro mediante el análisis de los argumentos transmitidos a RpcServerRegisterIf. Este es un enfoque similar a lo que se realiza en RpcEnum, pero exploramos el sistema de archivos en lugar de la memoria.
En nuestra investigación, elegimos el método de sistema de archivos para incluir las interfaces que no estaban cargadas necesariamente en la memoria. Escribimos varios scripts y herramientas para automatizar el proceso, que están disponibles en nuestro repositorio de kits de herramientas de RPC.
Para buscar las interfaces con el almacenamiento en caché habilitado, no necesitamos realmente analizar la interfaz de RPC en sí. Toda la información necesaria se puede extraer de la llamada de registro del servidor RPC. La función de registro acepta la estructura de interfaz de RPC, los indicadores de registro y el puntero de devoluciones de llamadas de seguridad. Aun así, el análisis de la estructura de interfaz de RPC puede proporcionar información útil, como las funciones expuestas por la interfaz o si un servidor o cliente RPC la han utilizado. Aunque nos interesan principalmente los servidores RPC (en los que puede existir una vulnerabilidad), los clientes RPC proporcionan buena información sobre la llamada al servidor, a la que podemos hacer referencia para la explotación.
La estructura de interfaz de servidor RPC está documentada, por lo que no tenemos que adivinar sus campos. Además, el campo de tamaño y la sintaxis de transferencia son constantes (realmente hay dos sintaxis de transferencia posibles, DCE NDR y NDR64, pero solo nos encontramos con DCE NDR).
Es una cuestión trivial encontrar todas las estructuras de interfaz de RPC buscando esas dos constantes (con expresiones Yara o regulares). Una vez encontradas, podemos usar el campo de información del intérprete para ver qué funcionalidad implementa el servidor.
Sin embargo, todavía nos falta información sobre la devolución de llamada de seguridad de la interfaz (si existe) y si se ha almacenado en caché. Por eso, debemos recurrir a nuestros amigos de confianza, los desensambladores. Cada desensamblador que se precie tendrá una funcionalidad xref, por lo que es trivial encontrar todas las llamadas a funciones de registro de la interfaz en un servidor RPC. Desde aquí, solo necesitamos analizar los argumentos de las llamadas a funciones para extraer la dirección de la estructura de interfaz (para poder hacer referencia cruzada de esta con nuestros datos del servidor RPC extraídos), la dirección de la devolución de llamada de seguridad (si existe) y los indicadores de la interfaz de RPC.
Hemos publicado nuestros scripts de extracción, que hacen exactamente eso, y están disponibles en nuestro kit de herramientas de RPC, junto con sus resultados de Windows Server 2012 y Server 2022.
CVE o no ha sucedido
Todas estas metodologías y teorías están bien, pero ¿generan realmente resultados?
La respuesta es que sí. Hay más de 120 interfaces con devolución de llamadas de seguridad y almacenamiento en caché, muchas de ellas no documentadas. Esto, por sí solo, no es motivo para entrar en pánico, ya que la mayoría de las veces la devolución de llamada de seguridad no se verá afectada mucho por el almacenamiento en caché. Habitualmente, las comprobaciones realizadas por la devolución de llamada de seguridad se realizan en valores que no se pueden almacenar en caché, como la secuencia de protocolos de transporte (por ejemplo, TCP) o el nivel de autenticación. Cualquier cambio en este sentido requiere un nuevo contexto de seguridad, ya que se debe establecer una nueva conexión, que restablece la caché y anula cualquier elusión de almacenamiento en caché posible.
Encontramos unas cuantas vulnerabilidades a través de este enfoque de investigación. Solo podemos hablar sobre estas por el momento, ya que el resto siguen en proceso de revelación.
WksSvc
Puntuación CVSS de CVE-2022-38034: 4,3
WksSvc expone la interfaz de MS-WKST. El servicio es responsable de gestionar las suscripciones a dominios, los nombres de equipos y las conexiones a redirectores de red SMB, como los servidores de impresoras SMB. Al analizar la devolución de llamada de seguridad de la interfaz, podemos ver que unas pocas funciones reciben un tratamiento diferente al resto.
Las funciones cuyo "opnum" está entre 8 y 11 también se comprueban para su invocación por parte de un cliente local, lo que significa que la llamada remota no se permite. Pero como tenemos almacenamiento en caché, ¿qué ocurriría si primero llamamos a una función diferente, que está permitida de forma remota, y luego llamamos a una de las funciones restringidas?
Lo ha adivinado: podríamos llamar a las funciones restringidas localmente gracias al almacenamiento en caché del resultado de la primera llamada. La pregunta ahora es: ¿esas funciones son lo suficientemente importantes para garantizar que se restrinjan solo a los clientes locales?
Las funciones expuestas son NetrUseAdd, NetrUseGetInfo, NetrUseDely NetrUseEnum. Si le suenan familiares, es porque son accesibles a través de netapi32.dll (véase NetUseAdd, por ejemplo).
Esto es bueno, porque nos da una pista de lo que podemos hacer con este ataque. Es decir, podemos conectar el servidor remoto a una carpeta de red compartida de nuestra elección e incluso asignarlo a una letra de unidad lógica, de forma similar a net use. (¿Coincidencia? Probablemente no).
Eso nos presenta dos situaciones de ataque:
1. Podemos requerir autenticación en nuestra carpeta compartida; podemos retransmitirlo a un servidor diferente para un ataque de retransmisión de NTLM o almacenar los tokens y descifrar la contraseña offline.
2. O podemos enmascarar un servidor de archivos existente (o pretender que es uno nuevo) con archivos interesantes o útiles. Dado que estos archivos están bajo nuestro control, podemos usarlos como arma como estimemos oportuno, con la esperanza de que nos permitan infectar al usuario objetivo.
Estas dos situaciones, y poder llamar de forma remota a las funciones restringidas localmente, fueron suficientes para que Microsoft categorizara esta vulnerabilidad como EoP, con una puntuación de 4,3.
Sin embargo, la historia no acaba ahí. Sigue habiendo algunas advertencias que debemos atender.
Contexto de seguridad
El servidor RPC en WksSvc no realiza ningún registro de autenticación por sí solo. Si el servicio se ejecuta por su cuenta, no será posible la autenticación del lado del cliente (generará el error RPC_S_UNKNOWN_AUTHN_SERVICE). Por lo tanto, necesitamos que el servicio se ejecute con otros servicios para explotar también la multiplexación de SSPI. Esto limita nuestras versiones de Windows afectadas a las anteriores a Windows 10, versión 1703o versiones más recientes que se ejecuten con menos de 3,5 GB de RAM.
Sesiones de inicio de sesión
Otro problema, que está integrado en cómo funcionan las carpetas de red asignadas, es que se limitan a la sesión de inicio de sesión del usuario que las crea. Debido a que necesitamos iniciar sesión en primer lugar para obtener el enlace de seguridad y el almacenamiento en caché, tendremos que crear siempre una sesión de inicio de sesión diferente a la sesión existente (interactiva) en el equipo de destino. Para todos los propósitos y fines, significa que nuestra vulnerabilidad no tiene efecto. La asignación de red que creamos se encuentra en nuestra sesión de inicio de sesión de corta duración y no en la que crea un usuario regular cuando inicia sesión en el equipo, por lo que no será visible.
Para solucionarlo, tuvimos que profundizar un poco más en el código de NetrUseAdd. Al parecer, hay indicadores que podemos transmitir a NetrUseAdd que lo dirigen para crear la asignación en el espacio de nombres Global, que afecta a todos los usuarios. Estos indicadores se encuentran incluso en el archivo de encabezado disponible LMUse.h:
Armado con los indicadores, nuestro código ahora crea correctamente una asignación global, que afectará a la sesión interactiva, terminando con nuestro intento de explotación.
Resumen
MS-RPC es un protocolo grande y complejo. También presta servicio a algunas de las funciones principales de Windows. Aunque cuenta con funciones de seguridad que los desarrolladores pueden usar para proteger sus servidores RPC, es un tema interesante para los investigadores de seguridad precisamente porque contiene una vulnerabilidad que puede afectar a la seguridad.
A pesar de ello, no se han realizado muchas investigaciones públicas al respecto. En esta publicación del blog, abordamos un gran mecanismo de seguridad de MS-RPC (las devoluciones de llamadas de seguridad) y encontramos una elusión en la forma de almacenar en caché los resultados de las devoluciones de llamadas de seguridad. También detallamos nuestra metodología de investigación para encontrar servidores RPC vulnerables y demostramos algunos de los hallazgos con reseñas de vulnerabilidades.
Esperamos que esta publicación y el repositorio de kits de herramientas de RPCque la acompaña ayuden a otros a investigar los mecanismos de seguridad y los servidores RPC.