Aprovechamiento de una vulnerabilidad crítica de suplantación en CryptoAPI de Windows
Edición y contribuciones adicionales de Tricia Howard
Resumen ejecutivo
El grupo de inteligencia sobre seguridad de Akamai ha analizado recientemente una vulnerabilidad crítica en CryptoAPI de Windows, revelada a Microsoft por la Agencia Nacional de Seguridad (NSA) y el Centro Nacional de Ciberseguridad (NCSC) de EE. UU.
La vulnerabilidad, que ha recibido el código CVE-2022-34689, obtuvo una puntuación de CVSS de 7,5. Se aplicó el parche en agosto de 2022, pero se anunció públicamente en el Patch Tuesday de octubre de 2022.
Según Microsoft, la vulnerabilidad permite que un atacante se haga pasar por una entidad legítima.
La causa principal del error es la suposición de que la clave de índice de caché de certificados, que está basada en MD5, no tiene colisiones. Desde 2009, se sabe que la resistencia a la colisión de MD5 está rota.
El flujo de ataque es doble. La primera fase requiere seleccionar un certificado legítimo, modificarlo y entregar la versión modificada a la víctima. La segunda fase implica la creación de un nuevo certificado cuyo MD5 colisione con el certificado legítimo modificado, y el uso del nuevo certificado para falsificar la identidad del sujeto del certificado original.
Hemos buscado aplicaciones en circulación que utilizan CryptoAPI de una forma vulnerable a este ataque de suplantación. Hasta ahora, hemos descubierto que se pueden usar para efectuar ataques en versiones antiguas de Chrome (v48 y anteriores) y aplicaciones basadas en Chromium. Creemos que existen objetivos más vulnerables en circulación y nuestra investigación sigue en curso.
Hemos descubierto que menos del 1 % de los dispositivos visibles de los centros de datos tienen parches, por lo que el resto queda desprotegido de la explotación de esta vulnerabilidad.
En esta entrada del blog, ofrecemos una explicación detallada del flujo y las consecuencias potenciales del ataque, así como una prueba de concepto (PoC) que demuestra el ataque completo. También proporcionamos una consulta de OSQuery para detectar versiones vulnerables de la biblioteca de CryptoAPI.
Antecedentes
Hace tres meses, en nuestro análisis de Patch Tuesday de 2022, compartimos una descripción básica de una vulnerabilidad de suplantación crítica en CryptoAPI de Windows: CVE-2022-34689. Según Microsoft, esta vulnerabilidad permite a un atacante “falsificar su identidad y realizar acciones, incluidas la autenticación o firma de código, como si se tratara del certificado de destino”.
CryptoAPI es la API estándar de Windows para gestionar todo lo relacionado con la criptografía. En concreto, gestiona los certificados, desde su lectura y análisis hasta su validación frente a las entidades de certificación (CA) verificadas. Los navegadores también utilizan CryptoAPI para la validación de certificados TLS, un proceso que da como resultado el icono de candado que todo el mundo debe comprobar.
Sin embargo, la verificación de certificados no es exclusiva de los exploradores y también la utilizan otros clientes de TLS, como la autenticación web de PowerShell, curl, wget, administradores de FTP, EDR y muchas otras aplicaciones. Además, los certificados de firma de código se verifican en ejecutables y bibliotecas, y los certificados de firma de controladores se verifican al cargar controladores. Como se puede imaginar, una vulnerabilidad en el proceso de verificación de certificados es muy lucrativa para los atacantes, ya que les permite enmascarar su identidad y eludir las protecciones de seguridad críticas.
Esta no es la primera vez que la Agencia de Seguridad Nacional de EE. UU. informa de una vulnerabilidad en CryptoAPI. En 2020, se detectó CurveBall (CVE-2020-0601) y se informó sobre ello. Al explotar CurveBall o CVE-2022-34689 se puede suplantar la identidad pero, si bien CurveBall afecta a muchas aplicaciones, CVE-2022-34689 tiene más requisitos previos y, por lo tanto, tiene un alcance más limitado de objetivos vulnerables.
Detalles de la vulnerabilidad
Para analizar la vulnerabilidad, primero intentamos localizar el código revisado. Utilizamos BinDiff, una conocida herramienta de difusión binaria, para observar los diversos cambios de código en CryptoAPI. En crypt32.dll, solo ha cambiado una función: CreateChainContextFromPathGraph. Como parte de esta función, se comparan dos certificados: uno que se recibe como entrada y otro que reside en la caché de certificados de la aplicación receptora (más adelante mencionaremos en detalle esta caché).
La inspección de los cambios revela que las comprobaciones de mincmp se añadieron a la función en dos ubicaciones (figura 1).
Antes del parche, la función determinaba si un certificado recibido ya estaba en la caché (y, por lo tanto, estaba verificado) únicamente basándose en su huella digital de MD5. Después del parche, la adición de mincmp requiere que el contenido real de los dos certificados coincida completamente.
En este punto, nuestra teoría se basaba en que si un atacante pudiera entregar un certificado malicioso cuyo MD5 colisiona con uno que ya estuviera en la caché de certificados de la víctima, podría omitir la comprobación de vulnerabilidad y hacer que su certificado malicioso pasara por uno de confianza (figura 2).
Caché de certificados de CryptoAPI
CryptoAPI puede utilizar una caché para los certificados finales recibidos con el fin de mejorar el rendimiento y la eficiencia. Este mecanismo está desactivado de forma predeterminada. Para activarlo, el desarrollador de la aplicación debe pasar determinados parámetros a CertGetCertificateChain, la función de la API de Windows que finalmente conduce al código vulnerable (figura 3).
CertGetCertificateChain recibe varios parámetros interesantes:
hChainEngine : objeto configurable utilizado para controlar la forma en que se validan los certificados
pCertContext : contexto del certificado de entrada, una estructura de datos creada con el certificado de entrada por la función CertCreateCertificateContext de WinAPI
dwFlags : indicadores que especifican una configuración adicional
ppChainContext : objeto de salida que contiene (entre otros campos) el estado de confianza; es decir, el veredicto de verificación de la cadena
Para activar el mecanismo de almacenamiento en caché para los certificados finales, el desarrollador debe definir el indicador CERT_CHAIN_CACHE_END_CERT en dwFlags, o crear un motor de cadena personalizado y establecer el indicador CERT_CHAIN_CACHE_END_CERT en su campo dwFlags .
Para comprender cómo se implementa y utiliza la caché, echemos un vistazo a la función FindIssuerObject que extrae el certificado de la caché. En términos generales, la función se comporta de la siguiente manera:
Calcula el índice de depósito del certificado de entrada en la caché basándose en los cuatro bytes menos significativos de su huella digital de MD5.
Si existe en la caché, la función compara la huella digital de MD5 completa del certificado almacenado en caché y el certificado de entrada.
Si las huellas digitales coinciden (se produce un acierto de caché), el certificado de entrada se considera de confianza y se devuelve. Desde ese momento, la aplicación utiliza los atributos del certificado de entrada (como la clave pública, el emisor, etc.) y no el certificado almacenado en caché.
Si las huellas digitales no coinciden (se produce un fallo de caché), se pasa al siguiente certificado del depósito, se compara su huella digital se MD5 y se repite.
Microsoft confía inherentemente en la validez de los certificados almacenados en caché y no realiza ninguna comprobación de validez adicional tras haber encontrado un certificado final en la caché. Esto, por sí mismo, es un supuesto de trabajo razonable. Sin embargo, el código asume que dos certificados son idénticos si sus huellas digitales de MD5 coinciden. Esta es una suposición incorrecta que se puede aprovechar con fines maliciosos, y fue la génesis del parche.
Para apoyar nuestra hipótesis, escribimos una pequeña aplicación que utiliza CertGetCertificateChain y depuramos el flujo de verificación de certificados en crypt32.dll. Con WinDbg, simulamos un escenario en el que la huella digital de MD5 de nuestro propio certificado (autofirmado) coincide con un certificado legítimo que ya estaba en la caché. Como se muestra en la figura 4, el certificado que creamos se consideró de confianza.
Al omitir una comprobación, podríamos hacer que Windows creyera que nuestro certificado malicioso era legítimo.
Cómo se puede aprovechar la vulnerabilidad
La construcción de un certificado con una huella digital de MD5 que coincide exactamente con un valor de MD5 dado se denomina ataque de preimagen, y esto es inviable desde el punto de vista informático incluso hoy en día. Sin embargo, es posible generar de forma eficaz dos certificados con dos prefijos seleccionados que acabarán teniendo las mismas huellas digitales de MD5; este tipo de ataque se denomina colisión de prefijos elegidos.
Al elegir esta ruta, tendremos que proporcionar de alguna manera dos certificados a la aplicación de la víctima. Un certificado se firmará, verificará y almacenará en caché correctamente (lo denominaremos “certificado de destino modificado”). Se generará de forma que facilite un ataque de colisión de prefijo elegido. El segundo certificado (que denominaremos “certificado malicioso”) contendrá la identidad falsificada. Colisionará con la huella digital de MD5 del primer certificado (figura 5).
Suplantación de certificados mediante colisiones de MD5
Las colisiones de MD5 nos llevan unos 14 años atrás, a un momento en el que Beyoncé publicó “Single Ladies”, Obama fue elegido presidente por primera vez y las colisiones de MD5 colisiones se utilizaron inicialmente para falsificar certificados SSL. Hay una diferencia importante entre ese primer ataque y el escenario que tratamos hoy: el escenario anterior atacaba firmas de MD5 , pero en la vulnerabilidad actual estamos tratando con huellas digitales de MD5 . Comprendamos la diferencia.
Según RFC 5280, sección 4.1, un certificado es una secuencia ASN.1 con dos secciones (figura 6):
tbsCertificate (o certificado “a firmar”, TBS): es la parte que contiene todos los detalles relacionados con la identidad (asunto, clave pública, número de serie, EKU, etc.). Esta es la parte que se firma.
signatureAlgorithm y signatureValue : estos campos comprenden la firma del TBS.
Certificate ::= SEQUENCE {
tbsCertificate TBSCertificate,
signatureAlgorithm AlgorithmIdentifier,
signatureValue BIT STRING }
Fig. 6: Secuencia ASN.1 que define los certificados
Una firma de certificado es por tanto una estructura integrada dentro del certificado, que únicamente firma la parte TBS del certificado. Por otro lado, una huella digital de certificado es un hash de todo el certificado (incluida la firma).
Por lo tanto, si pudiéramos modificar cualquier parte del certificado que esté fuera del TBS sin invalidar el certificado, a continuación, modificaríamos la huella digital sin cambiar la firma. Si el analizador analiza la firma correctamente y el TBS no cambia, el certificado se considerará válido y firmado, aunque haya cambiado la estructura completa del certificado (figura 7).
Colisiones MD5 de prefijo elegido: Una breve descripción general
Supongamos que tiene dos cadenas arbitrarias, A y B, de la misma longitud. Entonces, dos cadenas, C y D, se podrían calcular de manera eficiente, así:
MD5(A || C) = MD5(B || D), |
donde || indica la concatenación de cadenas.
El resultado final de MD5 sería el mismo, al igual que el interno de MD5 tras adjuntar C o D. Por lo tanto, si toma cualquier sufijo E, entonces tendría
MD5(A || C || E) = MD5(B || D || E) |
(siempre que se añada el mismo sufijo E en ambos lados).
Espacio para bloques de colisión
Como atacantes, necesitaremos generar un certificado que parezca válido pero que también contenga espacio para bloques de colisión (las cadenas C y D en la explicación anterior). Esto nos permitirá crear nuestro certificado malicioso (con la misma huella digital de MD5), que le proporcionaremos a continuación.
Según RFC 5280, sección 4.1.1.2, la estructura de signatureAlgorithm es
AlgorithmIdentifier ::= SEQUENCE {
algorithm OBJECT IDENTIFIER,
parameters ANY DEFINED BY algorithm OPTIONAL }
El campo de parámetros para el algoritmo RSA (basado en RFC 3279) “SHALL be the ASN.1 type NULL”. En otras palabras: RSA no utiliza parámetros de firma, sino que toma NULL como valor. ¿Es posible que CryptoAPI ignore este campo para las firmas RSA?
Para insertar bytes de marcador de posición en este campo (como preparación para bloques de colisión), hemos intentado cambiar su tipo ASN.1 de NULL a BIT STRING. Si probamos esto con CryptoAPI y OpenSSL, funciona : el certificado sigue considerándose válido. La firma no cambia ni se rompe porque no modificamos el TBS. (Por supuesto, la huella digital de MD5 cambia).
Colisiones de huellas digitales con el certificado de MD5
Ahora, podemos combinarlo todo y proporcionar una receta para manipular un certificado existente, ya firmado, para colisionar con la huella digital de MD5 de un certificado malicioso.
Seleccione un certificado final firmado por RSA legítimo, como el certificado TLS de un sitio web (nuestro “certificado de destino”).
Modifique cualquier campo interesante (asunto, extensiones, EKU, clave pública, etc.) en la parte TBS del certificado para crear el certificado malicioso. Nota: No tocamos la firma, por lo que el certificado malicioso está firmado incorrectamente. La modificación de la clave pública es importante aquí, ya que permite al atacante firmar como certificado malicioso.
Modifique el campo de parámetros signatureAlgorithm de ambos certificados, de modo que haya suficiente espacio para colocar los bloques de colisión de MD5 (C y D en la explicación anterior) comenzando en la misma desviación de ambos certificados.
Trunque los dos certificados en la posición en la que se colocarán los bloques de colisión de MD5.
Realice un cálculo de colisión de prefijo elegido de MD5 y copie el resultado en los certificados.
Concatene el valor de firma del certificado legítimo (sufijo E en la explicación anterior) a ambos certificados incompletos.
Un ejemplo real
Con nuestra comprensión de las colisiones de MD5, ahora podemos intentar aprovechar esta CVE con un objetivo real. Entre las numerosas aplicaciones que comprobamos, encontramos un objetivo vulnerable: Chrome v48. (Esta aplicación es vulnerable simplemente porque pasa el indicador CERT_CHAIN_CACHE_END_CERT a CertGetCertificateChain). Otras aplicaciones basadas en Chromium de ese momento también son vulnerables a esta CVE.
Para aprovechar esta vulnerabilidad, primero tuvimos que crear dos certificados con la misma huella digital de MD5, y utilizamos HashClash (figura 8).
Entonces tuvimos que dar con una forma de inyectar nuestro certificado de destino modificado en la caché de Chrome. No fue tarea fácil, ya que es imposible servir un certificado sin conocer su clave privada.
En TLS 1.2, hay dos fases de verificación relevantes:
El mensaje Server Key Exchange : este mensaje solo lo puede crear alguien que conozca la clave privada del certificado, ya que está firmado por el certificado
El mensaje Server Handshake Finished : este mensaje incluye una verificación antimanipulación de todos los mensajes de protocolo de enlace anteriores
(TLS 1.3 es diferente y no nos hemos centrado en ello).
Recuerde que en la primera fase del ataque queremos inyectar el certificado modificado en la caché de certificados final de Chrome.
Mediante un script Python como proxy, realizamos un ataque de máquina intermediaria (MITM):
Nuestro servidor MITM malicioso se comunica con el servidor real y refleja los primeros mensajes del protocolo de enlace TLS a la víctima.
En el mensaje Server Certificate, nuestro servidor MITM malicioso modifica el mensaje del servidor real y sustituye el certificado de destino real por el certificado modificado.
El mensaje de Server Key Exchange se puede reflejar sin cambios.
Nuestro servidor malicioso no puede reenviar simplemente el mensaje de Server Handshake Finished, ya que el protocolo de enlace se ha manipulado. Por lo tanto, terminamos la conexión.
Para comprobar el mensaje de Server Key Exchange, Chrome debe cargar el certificado modificado con CryptoAPI y, por lo tanto, se inyectará en la caché. Chrome no trata la interrupción de la conexión como un problema de seguridad TLS; podría tratarse de un problema de red aleatorio. Chrome intenta volver a conectarse, y esta vez, en lugar de reflejar los mensajes del sitio web real, el servidor malicioso proporcionará un sitio web con el certificado malicioso. Chrome omitirá el proceso de verificación completo porque creerá que el certificado ya está en la caché. El resultado será una visita perfecta a un sitio web de Microsoft aparentemente legítimo (figuras 9 y 10). El flujo de explotación completo se puede ver en nuestro vídeo.
Detección
Proporcionamos una OSQuery para detectar versiones vulnerables de crypt32.dll, la biblioteca vulnerable (figura 11). Los clientes de Akamai Guardicore Segmentation pueden utilizar la función Insight junto con esta consulta para buscar activos vulnerables.
Tenga en cuenta que para que un activo sea vulnerable necesita tener una versión sin parches de crypt32.dll y ejecutar una aplicación vulnerable. (Por ahora, solo hemos detectado que Chrome v48 sea vulnerable).
WITH product_version AS (
WITH os_minor AS (
WITH os_major AS (
SELECT substr(product_version, 0, instr(product_version, ".")) as os_major, substr(product_version, instr(product_version, ".")+1) as no_os_major_substr
FROM file
WHERE path = "c:\windows\system32\crypt32.dll"
)
SELECT substr(no_os_major_substr, instr(no_os_major_substr, ".")+1) as no_os_minor_substr, substr(no_os_major_substr, 0, instr(no_os_major_substr, ".")) as os_minor, os_major
FROM os_major
)
SELECT
CAST(substr(no_os_minor_substr, instr(no_os_minor_substr, ".")+1) AS INTEGER) AS product_minor,
CAST(substr(no_os_minor_substr, 0, instr(no_os_minor_substr, ".")) AS INTEGER) AS product_major,
CAST(os_minor AS INTEGER) AS os_minor,
CAST(os_major AS INTEGER) AS os_major
FROM os_minor
)
SELECT
CASE
WHEN os_major = 6 AND os_minor = 3 THEN "not supported"
WHEN (
(product_major = 20348 AND product_minor >= 887)
OR
(product_major = 17763 AND product_minor >= 3287)
OR
(product_major = 14393 AND product_minor >= 5291)
OR
(product_major >= 19041 AND product_minor >= 1889)
)
THEN
"patched"
ELSE
"not patched"
END is_patched
FROM product_version
Conclusión
Los certificados desempeñan un papel fundamental en la verificación de identidad online, lo que hace que esta vulnerabilidad resulte lucrativa para los atacantes. Sin embargo, aunque fue marcada como crítica, a la vulnerabilidad solo se le dio una puntuación CVSS de 7,5. Creemos que esto se debe al alcance limitado de las aplicaciones vulnerables y los componentes de Windows en los que se cumplen los requisitos previos para la vulnerabilidad.
Dicho esto, todavía hay mucho código que utiliza esta API y que podría estar expuesto a esta vulnerabilidad, lo que garantiza un parche incluso para versiones descontinuadas de Windows, como Windows 7.
Le aconsejamos que aplique a sus servidores y terminales Windows el parche de seguridad más reciente publicado por Microsoft. Para los desarrolladores, otra opción para mitigar esta vulnerabilidad es utilizar otras WinAPI para comprobar la validez de un certificado antes de utilizarlo, como CertVerifyCertificateChainPolicy. Tenga en cuenta que las aplicaciones que no utilizan el almacenamiento en caché de certificados finales no son vulnerables.
Nuestro código de validación técnica se puede encontrar en nuestro repositorio de GitHub. También puede estar al día de todas las publicaciones del grupo de inteligencia sobre seguridad de Akamai a través de nuestra cuenta de Twitter.