Exploitation d'une vulnérabilité d'usurpation critique dans CryptoAPI de Windows
Contributions éditoriales et additionnelles de Tricia Howard
Synthèse
Récemment, le groupe Security Intelligence d'Akamai a analysé une vulnérabilité critique dans l'interface CryptoAPI de Windows. La NSA (National Security Agency) et le NCSC (National Cyber Security Center) ont signalé cette vulnérabilité à Microsoft.
Cette vulnérabilité dénommée CVE-2022-34689a obtenu un score CVSS de 7,5. Corrigée en août 2022, elle a été annoncée au grand public dans le Patch Tuesday d'octobre 2022.
Selon Microsoft, cette vulnérabilité permettait à un pirate de se faire passer pour une entité légitime.
La cause première du bogue est liée à l'hypothèse selon laquelle la clé d'index du cache de certificat, basée sur le MD5, est exempte de collisions. Depuis 2009, la résistance aux collisions du MD5 est défaillante.
Le flux d'attaque est donc doublé. La première phase exige de prendre un certificat légitime, de le modifier et de transmettre la version modifiée à la victime. La deuxième phase consiste à créer un nouveau certificat dont le MD5 entre en collision avec le certificat légitime modifié avant d'utiliser le nouveau certificat pour usurper l'identité de l'objet du certificat original.
Nous avons recherché des applications utilisées au quotidien qui utilisent CryptoAPI d'une manière qui les rend vulnérables à cette attaque par usurpation d'identité. Nous avons constaté que jusqu'à présent, les anciennes versions de Chrome (v48 et versions antérieures) ainsi que les applications basées sur Chromium peuvent être exploitées à cette fin. Nous sommes persuadés que certaines applications de notre quotidien sont plus vulnérables et nos recherches sont toujours en cours.
En effet, moins de 1 % des terminaux visibles dans les centres de données sont corrigés. Le reste de ces terminaux reste donc exposé à l'exploitation de cette vulnérabilité.
Dans cet article de blog, nous vous fournissons une explication détaillée du flux d'attaque potentiel et de ses conséquences. Nous vous présenterons également une version de démonstration qui simule une attaque complète. Nous mettons aussi OSQuery à disposition pour détecter les versions vulnérables de la bibliothèque CryptoAPI.
Contexte
Il y a trois mois de cela, dans l'analyse de notre Patch Tuesday d'octobre 2022, nous vous avons partagé une description très succincte d'une vulnérabilité d'usurpation critique de CryptoAPI dans Windows (CVE-2022-34689). Microsoft affirme que cette vulnérabilité permet à un pirate « d'usurper leur identité pour authentifier ou signer un code en tant que certificat visé ».
CryptoAPI est l'API utilisée dans Windows pour la gestion de tout ce qui a trait à la cryptographie. Cette API traite surtout des certificats : de leur lecture à leur analyse, en passant par leur approbation par les autorités de certification. Les navigateurs utilisent également la validation de certificat CryptoAPI pour TLS. Ce processus entraîne la vérification de l'icône de verrouillage.
Cependant, les navigateurs ne sont pas les seuls à exploiter la vérification du certificat. Cette dernière est aussi utilisée par d'autres clients TLS tels que PowerShell qui utilise l'authentification Web, les méthodes curl et wget, les gestionnaires FTP, les EDR et de nombreuses autres applications. Les certificats de signature de code sont vérifiés grâce à des exécutables et à des bibliothèques. Les certificats de signature de pilote, eux, sont vérifiés lors du chargement des pilotes. Vous vous en doutez, la moindre vulnérabilité dans le processus de vérification des certificats profite aux pirates puisqu'elle leur permet de masquer leur identité pour contourner les protections de sécurité critiques.
Ce n'est pas la première fois que la NSA identifie une vulnérabilité dans CryptoAPI. En 2020, l'agence a découvert et signalé la vulnérabilité CurveBall (CVE-2020-0601). Que ce soit avec l'exploitation de la vulnérabilité CurveBall ou CVE-2022-34689, l'objectif est le même : usurper une identité. Cependant, là où l'exploitation de CurveBall affecte de nombreuses applications, celle de la CVE-2022-34689 est bien plus exigeante puisqu'elle nécessite un grand nombre de conditions préalables. Il y a donc moins de cibles vulnérables à disposition.
Informations sur cette vulnérabilité
Pour analyser cette vulnérabilité, nous avons commencé par localiser le code corrigé. Nous avons utilisé l'outil de comparaison binaire BinDiff pour observer les différentes modifications de code apportées à CryptoAPI. Dans crypt32.dll, une seule fonction a changé : CreateChainContextFromPathGraph. Cette fonction sert à comparer deux certificats : un certificat reçu comme entrée et un autre certificat situé dans le cache de certificat de l'application réceptrice. Plus tard, nous évoquerons ce cache plus en détail.
Suite à notre analyse des modifications, nous avons constaté que des vérifications memcmp avaient été ajoutées à la fonction au niveau de deux emplacements (Figure 1).
Avant le déploiement du correctif, la fonction pouvait déterminer si un certificat reçu se trouvait déjà dans le cache (et donc s'il était approuvé) en se basant uniquement sur son empreinte MD5. Depuis le déploiement du correctif, l'ajout de memcmp impose une correspondance totale entre le contenu réel des deux certificats.
À ce stade, nous avons commencé à émettre l'hypothèse suivante : si un pirate parvient à utiliser un certificat malveillant dont le MD5 correspond au certificat déjà présent dans le cache de la victime, il pourrait être en mesure de contourner le contrôle des vulnérabilités et, par conséquent, de faire approuver son certificat malveillant (Figure 2).
Cache de certificat de CryptoAPI
Pour améliorer ses performances et son efficacité, CryptoAPI a la possibilité d'utiliser un cache pour les certificats finaux reçus. Par défaut, ce mécanisme est désactivé. Pour l'activer, le développeur de l'application doit transmettre certains paramètres à la fonction de l'API Windows CertGetCertificateChainafin d'espérer obtenir le code vulnérable (Figure 3).
CertGetCertificateChain reçoit plusieurs paramètres intéressants :
hChainEngine — un objet configurable destiné à contrôler la méthode de validation des certificats
pCertContext — le contexte du certificat d'entrée et structure de données construite à l'aide du certificat d'entrée par la fonction CertCreateCertificateContext de WinAPI
dwFlags — l'indicateur exigeant une configuration supplémentaire
ppChainContext — l'objet de sortie qui inclut, sans s'y limiter, l'indice de fiabilité, à savoir la méthode de vérification de la chaîne
Pour activer le mécanisme de mise en cache des certificats finaux, deux solutions s'offrent au développeur : définir l'indicateur CERT_CHAIN_CACHE_END_CERT dans dwFlags, ou créer un moteur de chaîne personnalisé avant de définir l'indicateur CERT_CHAIN_CACHE_END_CERT dans le champ dwFlags .
Pour mieux comprendre comment implémenter et utiliser le système de cache, analysons la fonction FindIssuerObject destinée à extraire le certificat du cache. De manière générale, la fonction se comporte comme suit :
Elle se base sur les quatre octets les moins significatifs de son empreinte MD5 pour calculer l'indice de compartiment du certificat d'entrée situé dans le cache.
Si elle trouve une correspondance dans le cache, la fonction compare l'ensemble de l'empreinte MD5 du certificat mis en cache à celle du certificat d'entrée.
Si les empreintes correspondent (correspondance dans le cache), le certificat d'entrée est approuvé et renvoyé. Désormais, l'application utilise les attributs des certificats d'entrée (comme la clé publique, l'émetteur, etc.) à la place du certificat mis en cache.
Si les empreintes ne correspondent pas (échec d'accès au cache), la fonction passe au prochain certificat du compartiment, compare les empreintes MD5 et répète le processus.
Les certificats mis en cache sont considérés comme fiables et sont, de ce fait, approuvés par Microsoft qui n'effectue aucun contrôle de validité supplémentaire lorsqu'un certificat final a été trouvé dans le cache. En soi, cette hypothèse est raisonnable sur le plan opérationnel. Cependant, le code va plus loin en indiquant que deux certificats sont considérés comme identiques lorsque leurs empreintes MD5 correspondent. Il s'agit donc d'une hypothèse incorrecte qui peut être exploitée, et qui est à l'origine du correctif.
Pour étayer notre hypothèse, nous avons développé une petite application qui utilise la fonction CertGetCertificateChain pour déboguer le flux de vérification du certificat avec le module crypt32.dll. Grâce à WinDbg, nous avons simulé un scénario dans lequel l'empreinte MD5 de notre certificat auto-signé correspond à un certificat légitime déjà présent dans le cache. Comme l'illustre la Figure 4, notre faux certificat a été approuvé.
Il nous suffirait de contourner un seul contrôle pour faire croire à Windows que notre certificat malveillant est légitime.
Exploitation de la vulnérabilité
La conception d'un certificat doté d'une empreinte MD5 correspondant exactement à une valeur MD5 donnée est considérée comme une attaque de préimage. Encore aujourd'hui, ces attaques ne sont pas réalisables d'un point de vue informatique. Cependant, il est possible de générer efficacement deux certificats avec deux préfixes choisis qui auront les mêmes empreintes MD5. Ce type d'attaque s'apparente à une collision à préfixes choisis.
Pour orchestrer cette attaque, nous aurons besoin de fournir deux certificats à l'application victime. Un certificat sera signé, vérifié et mis en cache dans les règles de l'art (appelons-le « certificat cible modifié »). Il sera généré de manière à faciliter une attaque de collision à préfixes choisis. Le deuxième certificat (que nous appellerons « certificat malveillant ») contiendra l'identité usurpée. Il entrera en collision avec l'empreinte MD5 du premier certificat (Figure 5).
Usurpation de certificat par collision MD5
Les collisions MD5 nous font remonter le temps de 14 ans, à l'époque où Beyoncé a sorti « Single Ladies », où Obama a été élu président pour la première fois et où les collisions MD5 ont été utilisées pour la première fois pour usurper des certificats SSL. Il existe une différence majeure entre cette première attaque et le scénario que nous traitons aujourd'hui : dans le scénario précédent, nous nous attaquions au MD5 et à ses signatures, mais avec la vulnérabilité actuelle, nous nous attaquons aux empreintes MD5. Apprenons à faire la différence.
Selon le RFC 5280, à la section 4.1, un certificat est une séquence ASN.1 composée de deux sections (Figure 6) :
tbsCertificate (aussi appelé certificat « to-be-signed » ou « à signer »). Il s'agit de la partie contenant tous les détails relatifs à l'identité (sujet, clé publique, numéro de série, EKU, etc.). Cette partie est signée.
signatureAlgorithm et signatureValue . Ces champs comprennent la signature du certificat à signer.
Certificate ::= SEQUENCE {
tbsCertificate TBSCertificate,
signatureAlgorithm AlgorithmIdentifier,
signatureValue BIT STRING }
Figure 6 : séquence ASN.1 qui définit les certificats
La signature d'un certificat est donc une structure intégrée à l'intérieur du certificat. Cette partie ne signe que la partie « à signer » du certificat. D'un autre côté, l'empreinte d'un certificat, est un hachage de l'ensemble du certificat (signature incluse).
De fait, si nous pouvons modifier une partie du certificat située à l'extérieur de la partie « à signer » sans invalider ledit certificat, nous pouvons, par la suite, modifier l'empreinte sans modifier la signature. Si l'analyseur examine la signature correctement et que la partie à signer n'est pas modifiée, le certificat sera toujours considéré comme valide et signé, même si la structure complète du certificat vient à changer (Figure 7).
Courte présentation des collisions à préfixe choisi
Imaginez-vous avec deux chaînes arbitraires (A et B) de la même longueur. Ensuite, visualisez deux autres chaînes (C et D) calculables efficacement en suivant la formule :
MD5(A || C) = MD5(B || D) |
dans laquelle || marque la concaténation de chaînes.
Gardez à l'esprit que le résultat final de MD5 ne sera pas le seul à présenter des similitudes, l'état interne de MD5 sera, lui aussi, similaire après l'ajout de C ou D. Par conséquent, si vous prenez un suffixe E, vous obtiendrez la formule :
MD5(A || C || E) = MD5(B || D || E) |
(à condition que le même suffixe E soit ajouté des deux côtés).
Libération de l'espace pour les blocs de collision
Maintenant, mettons-nous dans la peau de pirates et générons un certificat qui semble valide sans oublier de laisser de la place pour les blocs de collision (équivalent des chaînes C et D dans l'explication ci-dessus). Ainsi, nous pourrons créer le certificat malveillant (avec la même empreinte MD5) que nous transmettrons.
Selon le RFC 5280, à la la section 4.1.1.2, la structure de signatureAlgorithm est
AlgorithmIdentifier ::= SEQUENCE {
algorithm OBJECT IDENTIFIER,
parameters ANY DEFINED BY algorithm OPTIONAL }
Le champ de paramètres de l'algorithme RSA (basé sur le le RFC 3279) « SHALL be the ASN.1 type NULL » (DOIT être le type ASN.1 NULL). En d'autres termes : l'algorithme RSA n'utilise pas de paramètres de signature et prend NULL pour une valeur. Est-il possible que CryptoAPI ignore ce champ lors des signatures RSA ?
Pour insérer des octets de paramètres fictifs dans ce champ en vue du déploiement de blocs de collision, nous avons essayé de changer son type d'ASN.1 de NULL à BIT STRING. Nous avons testé cette méthode dans CryptoAPI et dans OpenSSL, et il s'avère qu'elle fonctionne — le certificat est toujours considéré comme étant valide. Nous n'avons pas modifié la partie « à signer », la signature reste donc inchangée et ininterrompue. (L'empreinte MD5, elle, change.)
Collisions d'empreinte MD5 du certificat
Nous pouvons désormais assembler ce grand puzzle pour commencer à manipuler un certificat existant déjà signé. Le but de cette manipulation est d'entrer en collision avec l'empreinte MD5 d'un certificat malveillant.
Prenez un certificat final légitime signé par l'algorithme RSA tel que le certificat TLS d'un site Web (notre « certificat cible »).
Pour créer votre certificat malveillant, modifiez les champs pertinents (sujet, extensions, EKU, clé publique, etc.) de la partie « à signer » du certificat. Remarque : ne touchez pas à la signature pour que le certificat malveillant soit mal signé. Dans notre démonstration, la modification de la clé publique est importante puisqu'elle permet au pirate de signer en tant que certificat malveillant.
Modifiez le champ de paramètres de la fonction signatureAlgorithm des deux certificats. Laissez suffisamment d'espace pour placer des blocs de collision MD5 (équivalent de C et D dans l'explication ci-dessus) en compensant de la même manière pour les deux certificats.
Tronquez les deux certificats au niveau du futur emplacement des blocs de collision MD5.
Calculez la collision à préfixes choisis et copiez le résultat dans les certificats.
Concaténez la valeur de signature du certificat légitime (équivalent du suffixe E dans l'explication ci-dessus) aux deux certificats incomplets.
Un exemple adapté aux besoins réels
Maintenant que nous maîtrisons les collisions du MD5, nous pouvons tenter d'exploiter cette CVE sur une cible réelle. Nous avons cherché parmi de nombreuses applications et avons trouvé une cible vulnérable : Chrome v48. (Cette application est exposée à cause de la transmission de l'indicateur CERT_CHAIN_CACHE_END_CERT à la fonction CertGetCertificateChain.) D'autres applications basées sur Chromium de l'époque sont tout aussi vulnérables à cette CVE.
Pour exploiter cette vulnérabilité, nous devions d'abord créer deux certificats à l'empreinte MD5 similaire comme nous l'avions fait pour HashClash (Figure 8).
Nous avons ensuite dû trouver un moyen d'injecter notre certificat cible modifié dans le cache de Chrome. Ce n'était pas une étape facile puisqu'il est impossible de transmettre un certificat sans connaître sa clé privée.
Dans TLS 1.2, il existe deux étapes de vérification pertinentes :
Le message Échange de clé de serveur — ce message signé par le certificat ne peut être émis que par une personne qui connaît la clé privée dudit certificat, puisqu'il est signé par le certificat.
Le message Fin de la liaison du serveur — ce message inclut une vérification anti-falsification de tous les messages de liaison précédents
(dans TLS 1.3, le message est différent, mais nous ne nous sommes pas attardés dessus).
Gardez à l'esprit que notre première étape est l'injection du certificat modifié dans le cache du certificat final de Chrome.
En utilisant un script Python comme proxy, nous menons une attaque MITM (Machine-in-the-middle) :
notre serveur MITM malveillant communique avec le vrai serveur pour transmettre les premiers messages de la liaison TLS à la victime.
Selon le message du certificat de serveur, notre serveur MITM malveillant modifie le message du serveur réel et remplace le certificat cible réel par le certificat modifié.
Le message Échange de clé de serveur peut être transmis sans modification.
Notre serveur malveillant ne peut pas se contenter de transférer le message Fin de la liaison du serveur puisque la liaison a été falsifiée. C'est ainsi que nous achevons la connexion.
Pour vérifier le message Échange de clé de serveur, Chrome doit charger le certificat modifié avec CryptoAPI qui, à terme, l'injectera dans le cache. Pour Chrome, une connexion interrompue ne s'apparente pas à un problème de sécurité TLS : il peut s'agir d'un simple problème de réseau aléatoire. Chrome tente de se reconnecter, et cette fois, le serveur malveillant prendra le relais et au lieu de transmettre les messages du vrai site Web, il transmettra ceux du site Web contenant le certificat malveillant. Chrome pense que le certificat est déjà dans le cache, il ignore donc le processus de vérification complet. Cette opération résulte en une visite fluide d'un site Web Microsoft qui semble légitime (Figures 9 et 10). Retrouvez l'intégralité du flux d'exploitation dans notre vidéo.
Détection
Nous vous mettons un OSQuery à disposition pour détecter les versions vulnérables de crypt32.dll, la bibliothèque vulnérable (Figure 11). Pour identifier les ressources vulnérables, les clients dotés de la technologie Guardicore Segmentation d'Akamai peuvent utiliser la fonction d'information avec cette requête.
Rappelez-vous qu'une ressource est considérée comme vulnérable dès qu'elle dispose d'une version non corrigée de crypt32.dll et dès qu'elle exécute une application vulnérable. (À ce jour, Chrome v48 est la seule version vulnérable que nous avons identifiée.)
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
Conclusion
Les certificats jouent un rôle majeur dans la vérification d'identité en ligne, ils profitent donc largement aux pirates. Malgré l'importance capitale de ce certificat, la vulnérabilité a obtenu un score CVSS de 7,5. Selon nous, son score n'est pas plus élevé en raison de la portée limitée des applications vulnérables et des composants Windows remplissant les conditions préalables à la vulnérabilité.
Cela étant dit, de nombreux codes utilisent encore cette API : ils pourraient donc être exposés à cette vulnérabilité. Cette exposition justifie donc le déploiement de correctifs pour les anciennes versions de Windows comme Windows 7.
Nous vous conseillons de corriger vos serveurs et points de terminaison Windows grâce au dernier correctif de sécurité publié par Microsoft. Pour atténuer cette vulnérabilité, les développeurs ont une autre option à leur disposition : ces derniers peuvent contrôler la validité d'un certificat avant de l'utiliser grâce à d'autres WinAPI tels que CertVerifyCertificateChainPolicy. Pour rappel, les applications qui n'utilisent pas la mise en cache de certificat final ne sont pas vulnérables.
Retrouvez la version de démonstration de notre code dans notre référentiel GitHub. Suivez-nous sur Twitter pour ne manquer aucune des publications du groupe Security Intelligence d'Akamai.