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-3602 — Analyse technique approfondie et anti-POC pour CVE-2022-3602, un dépassement de tampon punycode dans OpenSSL 3.0.x, avec scripts de reproduction, analyse de la pile et évaluation des atténuations du compilateur. | Kitploit
Outils/GitHubGitHub/colmmacc/cve-2022-3602
Analyse des VulnérabilitésExploitationCryptographieAnalyse de BinairesArticles et RechercheApprentissage et Éducation
GitHubcolmmacc/cve-2022-3602

CVE-2022-3602

Analyse technique approfondie et anti-POC pour CVE-2022-3602, un dépassement de tampon punycode dans OpenSSL 3.0.x, avec scripts de reproduction, analyse de la pile et évaluation des atténuations du compilateur.

Voir le dépôt
16930il y a 3 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-3602

Qu'est-ce que c'est ?

Ce document et ce référentiel sont une analyse de CVE−2022-3602, un problème de débordement de tampon punycode dans OpenSSL. Il s'agit d'un « anti-POC » (le problème ne semble pas exploitable) destiné aux personnes qui maintiennent leurs propres builds d'OpenSSL et aux mainteneurs de compilateurs.

Il existe un autre CVE dans la même version, CVE-2022-3786, qui conduit également à des débordements de tampon, mais un attaquant ne peut pas contrôler le contenu dans ce cas. Il n'y a pas de reproduction ici pour ce problème, mais ce problème peut conduire à un déni de service en raison d'un plantage.

Les crashs et les débordements de tampon ne sont jamais bons et si vous utilisez OpenSSL 3.0.x, il est prudent de mettre à jour dès que possible.

N'hésitez pas à signaler toute erreur ou omission via les issues GitHub ou les pull-requests.

Quel est le problème ?

Il y a un problème de décalage d'un dans la façon dont ossl_punycode_decode gère le décodage punycode, ce qui entraîne un débordement de 4 octets. Ce problème n'est raisonnable que lorsqu'OpenSSL traite une chaîne de certificats et nécessite deux conditions. Premièrement, un certificat CA ou Intermédiaire dans une chaîne doit contenir un champ name-constraint qui utilise le punycode.

root@kitploit:~
nameConstraints = permitted;email:xn-maccrthaigh-n7a.com

Deuxièmement, le certificat feuille doit contenir un champ SubjectAlternateName (SAN) otherName qui spécifie une chaîne SmtpUTF8Mailbox.

root@kitploit:~
otherName = 1.3.6.1.5.5.7.8.9;UTF8:[email protected]

Lorsqu'il est déclenché, le punycode dans le champ nameConstraints, mais pas le punycode dans le champ otherName, sera traité par l'analyseur punycode vulnérable d'OpenSSL.

Quelle est la facilité de déclencher ce problème ?

David Benjamin et Matt Caswell ont déterminé que la vérification des nameConstraints se produit après la validation normale de la chaîne de certificats et la vérification des signatures. Pour la plupart des applications, cela signifie que le problème ne peut pas être déclenché avec un certificat auto-signé ou une chaîne invalide.

Notez que les applications s_client et s_server d'openssl sont destinées au débogage et n'arrêtent pas le traitement lorsqu'une chaîne est invalide.

Une CA ou Intermédiaire de confiance devra contenir la charge utile malveillante et devra également avoir signé le certificat feuille qui déclenche le problème.

Il peut y avoir certains environnements où des parties non fiables sont les CA ou Intermédiaires, par exemple un service d'hébergement qui prend en charge les CA privées fournies par le client, mais cela n'est pas courant.

Le problème conduit-il à une exécution de code à distance ?

La réponse pour de nombreuses applications sera « non » en raison de la façon dont le compilateur a disposé la pile et de la présence d'autres protections telles que les canaris de pile / cookies de pile, le padding, le PIE, FORTIFY_SOURCE.

Le problème conduit effectivement à un débordement de 32 bits sur la pile. Cela ne suffit pas pour exécuter directement du code shell, mais cela peut suffire pour modifier le flux de contrôle d'une application. Par exemple, sauter vers du code shell intégré dans une chaîne de certificats X509 peut être possible si ces données sont également copiées sur la pile à un emplacement exécutable.

Sur toutes les plateformes Linux que j'ai testées, le débordement se produit dans le padding et est inoffensif. En théorie, un compilateur peut disposer les variables de sorte que le débordement se produise dans l'une des autres variables de la fonction ossl_a2ulabel.

En fonction de l'inlining, la liste complète des variables présentes est :

root@kitploit:~
outptr, inptr, size, result, tmpptr, delta, seed, utfsize

et aucune ne me semble offrir un chemin évident vers une élévation de privilèges ou un contrôle intéressant.

J'ai joint une archive avec des outils qui peuvent être utilisés pour créer des reproductions et des débordements avec autant de contrôle sur les quatre octets que possible. La chaîne de reproduction de référence (xn--ww90271...aaaa) fait déborder les quatre octets avec les valeurs 0xFF 0x0F 0x0F 0x0F. Si cela ne fait pas planter une application, il est possible (probable ?) que cette application ne soit pas vulnérable.

Comment puis-je reproduire ce problème ?

Le script shell run-poc peut être utilisé pour générer une chaîne de certificats malveillante. Un certificat CA malveillant est généré à partir de ca.cnf et un certificat feuille déclencheur est généré à partir de leaf.cnf.

Le certificat CA utilise la charge utile de référence suivante :

xn--ww902716aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa

Un script Python peut être utilisé pour générer d'autres chaînes punycode pour différentes charges utiles.

Lors de l'exécution, run-poc lancera un client et un serveur openssl et tentera d'exploiter le problème dix fois.

Un OpenSSL vulnérable plantera probablement. Cela ne signifie pas que la version d'OpenSSL est vulnérable à une RCE, car les canaris de pile et les protections par cookie de pile provoquent également généralement un plantage (plus sûr) de l'application. Notez également que cela ne modifie en rien la gravité de l'autre CVE dans la même version.

Comment fonctionne ce problème ?

Il est étonnamment nuancé d'obtenir un contrôle quasi complet des quatre octets de débordement et nécessite d'exploiter le décodeur punycode d'OpenSSL avec du punycode non standard / invalide. L'archive jointe contient un script qui peut construire une chaîne qui gère cette nuance. Ce qui suit est une explication de son fonctionnement.

Mise en place

Le problème de sécurité se trouve dans ossl_punycode_decode()

root@kitploit:~
int ossl_punycode_decode(const char *pEncoded, const size_t enc_len,
                         unsigned int *pDecoded, unsigned int *pout_length)

ossl_punycode_decode est invoqué depuis ossl_a2ulabel. Le tampon pEncoded est un tampon de taille plus ou moins arbitraire provenant d'une chaîne de certificats X509. C'est la partie qui vient après tout "xn--" dans un champ nameConstraint. Voir la [reproduction] pour savoir comment reproduire une telle chaîne de certificats.

pDecoded est un tableau de unsigned int de taille LABEL_BUF_SIZE. LABEL_BUF_SIZE vaut 512, et sur la plupart des plateformes, un unsigned int fera 4 octets de large. Donc sur la plupart des plateformes, pDecoded fait 2048 octets de long.

La scène

À l'intérieur de ossl_punycode_decode(), le cœur du problème est cette vérification de longueur incorrecte :

root@kitploit:~
 if (written_out > max_out)

max_out correspond à *pout_length qui vaut toujours 512. Et written_out garde la trace du nombre de unsigned int écrits dans pDecoded. Parce que written_out est incrémenté plus tard seulement après l'écriture, cette vérification erronée permet d'écrire 513 unsigned int dans pDecoded. Le résultat final ressemble à ceci ...

root@kitploit:~
pDecoded = [ ... , 'X , 'Y' , 'Z' ] 'P'
// Indices         509   510   511

Ici, selon la convention du C, les indices commencent à zéro, donc l'emplacement numéro 511 est le 512e élément du tableau. 'P' est une charge utile de quatre octets qui a été placée hors limites, au-delà de l'espace alloué sur la pile pour le tampon buf dans ossl_a2ulabel(), vers lequel pointe pDecoded.

Quatre octets est un petit débordement, et ne suffit pas pour transporter un nop-sled ou exécuter directement du code shell, mais il suffit pour modifier le flux de contrôle d'une application. Par exemple, sauter vers du code shell intégré dans une chaîne de certificats x509 peut être possible, selon la façon dont ces données (ou des fragments copiés de ces données) sont stockées et si cette mémoire est exécutable. Cependant, il y a encore plus de difficulté pour un éventuel attaquant.

Premièrement, le padding et l'alignement du compilateur de la pile, ou des défenses telles que les canaris de pile, peuvent rendre toute exploitation complètement impossible.

Deuxièmement, il n'y a qu'un seul chemin vers ossl_punycode_decode() et ce chemin utilise un tampon sur la pile. Cela rend peu probable que le problème puisse être utilisé pour des débordements concurrents de 4 octets à différents emplacements mémoire.

Décodage Punycode

Les chaînes Punycode ont essentiellement deux formes. L'une est xn--c1yn36f (點看) et une autre est xn--maccrthaigh-n7a (maccárthaigh). La partie qui vient après le dernier délimiteur - est un codage bootstring en base 36 de tous les points de code unicode qui ne sont pas des ascii de base ordinaires ainsi que la position dans la chaîne pour les insérer. Ce qui est important pour l'instant, c'est que le processus de décodage dans ossl_punycode_decode() produit deux valeurs. L'une est 'n' qui est la valeur unsigned int du point de code à insérer, et l'autre est 'i' qui est la position dans le tampon pour l'insérer.

L'écriture peut se produire de deux manières différentes. Si i se trouve quelque part au milieu de la chaîne, il y a un memmove() qui d'abord « fait de la place » en copiant tout ce qui est à droite d'un emplacement :

root@kitploit:~
memmove(pDecoded + i + 1, pDecoded + i,
       (written_out - i) * sizeof *pDecoded);

puis il écrit n dans l'espace qu'il vient de créer :

root@kitploit:~
 pDecoded[i] = n;

si i est à la fin de la chaîne, alors le memmove() n'a aucun effet car le dernier paramètre sera 0. L'autre ligne devient un simple ajout.

Maintenant, nous allons examiner les trois différentes façons d'obtenir une charge utile 'P' dans la position de débordement et pourquoi les contraintes apparaissent.

Méthode 1 - débordement ascii

La façon la plus simple de déclencher le débordement est de créer une chaîne punycode qui contient 511 caractères ascii et deux caractères non ascii. L'encodage punycode d'une chaîne de 513 caractères comme "ÁÁAAAAAAAA...AAA" ferait l'affaire. Dans ce cas, ce qui se passera lorsque written_out sera 510, nous aurons un tampon disposé comme ...

root@kitploit:~
pDecoded = [ 'A' , 'A' ,  ... , 'A' , 'A',     ]
// Indices    0     1     ...   509   510  511

ce sont juste les caractères ascii de base qui ont été copiés. Ensuite, nous analysons la bootstring punycode et insérons un 'Á' à la position 0. Bien qu'il pourrait s'agir de n'importe quelle position entre 0 et 511 inclus.

root@kitploit:~
pDecoded = [ 'Á' , 'A' ,  ... , 'A' , 'A', 'A' ]
// Indices    0     1     ...   509   510  511

nous répétons ensuite cela :

root@kitploit:~
pDecoded = [ 'Á' , 'Á' ,  ... , 'A' , 'A', 'A' ] 'A'
// Indices    0     1     ...   509   510  511   512

cela fera déborder le 'A' ascii ordinaire lorsqu'il est « déplacé ». La charge utile de quatre octets dans ce cas devient 0x00 0x00 0x00 0x41. Comme nous le verrons, en raison du fonctionnement du punycode, c'est la seule façon dont toute valeur avec un dernier octet dans la plage ascii peut être exprimée.

Nous devons utiliser deux caractères non ascii car il y a une vérification de limite correcte sur le nombre de caractères de base, donc cela doit être inférieur à 512.

Une contrainte supplémentaire est que la valeur du dernier octet ne peut pas être 46 car ossl_punycode_decode() est appelée sur la partie d'une chaîne qui précède un caractère littéral .. Punycode est destiné aux étiquettes de domaine, qui ne peuvent pas contenir de points.

Méthode 2 - débordement non ascii direct

La façon suivante la plus simple de déclencher le débordement est de créer une chaîne de 513 caractères avec un caractère non ascii tout à la fin. Quelque chose comme "AAAAAAAAAA...AAÁ". Dans ce cas, pour nos deux dernières étapes, nous aurons :

pDecoded = [ 'A' , 'A' , ... , 'A' , 'A' ] // Indices 0 1 ... 510 511

et

root@kitploit:~
pDecoded = [ 'A' , 'A' ,  ... , 'A' , 'A' ] 'Á'
// Indices    0     1     ...   510   511

le caractère non ascii ira directement dans la position de débordement. L'analyseur punycode d'OpenSSL n'impose pas que la valeur de débordement ici soit réellement un caractère unicode valide. C'est plus ou moins un processus de décodage binaire. Mais les nuances du décodage punycode font que la méthode 2 n'est pas aussi flexible qu'il y paraît.

En punycode, les valeurs n et i sont toutes deux encodées comme un seul entier de longueur variable qui est ensuite encodé en ascii en utilisant la base36. Il peut sembler impossible d'encoder deux nombres non liés en un seul entier, mais l'astuce ingénieuse du punycode est d'utiliser la longueur de la chaîne (jusqu'à présent) comme un champ caché.

Par exemple, supposons que nous ayons une chaîne punycode avec 4 caractères de base, et un non basique, comme AAÁAA. Cela sera d'abord représenté comme juste les caractères de base ... AAAA. La valeur unicode de 'Á' est 225 et sa position dans la chaîne est 2. L'astuce consiste à multiplier la valeur par la longueur plus un, puis à ajouter la position. Donc cela devient ((225 * (4 +1)) + 2) = 1127, et c'est ainsi qu'il est encodé (en base 36 de longueur variable).

Pour décoder, on fait le chemin inverse. 1127 / 5 = 225 et 1127 % 5 = 2. C'est ainsi que l'on récupère deux nombres à partir d'un. Mais remarquez que plus la chaîne s'allonge, plus vous êtes contraint sur la taille que la valeur peut avoir, sinon le multiple ne tiendra pas dans un unsigned int. En général, si la chaîne a une longueur M caractères, alors vous perdez log M bits de largeur de la valeur.

Au moment où vous manipulez le 512e entier, vous perdez 9 bits de largeur. En utilisant la méthode 2, la valeur la plus élevée qu'une charge utile apparemment de 32 bits pourrait être est en réalité 2^23. Pas même trois octets complets. La méthode 2 est sous-optimale.

Méthode 3 - bourrage

Pour retrouver 4 octets de contrôle, le moyen le plus efficace est de répéter le caractère de la charge utile encore et encore. Jusqu'à présent, j'ai laissé de côté deux autres détails pertinents sur la façon dont le punycode est traité.

Le premier détail est que les caractères non ascii ne sont pas encodés dans l'ordre de la chaîne, mais sont plutôt encodés par ordre croissant de valeur. La chaîne "ÉÁ" finira par être encodée comme "Á à la position 1, É à la position 0" car Á a une valeur inférieure (225) à É (233).

Le deuxième détail est que les caractères non ascii ne sont pas encodés comme leurs valeurs littérales, mais comme un delta par rapport à la valeur la plus récemment décodée. Comme la première valeur n'a pas de valeur précédente à laquelle se référer, il y a un point de départ codé en dur de 128.

Ces petites nuances rendent le punycode très efficace en espace, mais signifient également qu'un caractère non ascii ne peut tout simplement pas être décodé en une valeur inférieure à 128. Le plus petit delta est 0, et il n'y a aucun moyen d'exprimer un delta négatif. Donc, si vous voulez un nombre inférieur à 128, vous devez utiliser la méthode 1.

Cela signifie également que la meilleure stratégie pour obtenir autant de contrôle que possible sur la charge utile est de faire en sorte que la charge utile soit la seule valeur dans la chaîne complète, car de cette façon nous obtenons toute la largeur pour travailler à partir de sa place à la 0e position dans l'encodage. La chaîne que vous encodez finit par ressembler à ;

root@kitploit:~
 [ 'P', 'P', ... 'P', 'P', 'P' ]
    0    1       510  511  512

qui sera décodée par OpenSSL comme ...

root@kitploit:~
pDecoded = [ 'P', 'P', ... 'P', 'P' ] 'P'
              0    1       510  511   512

avec P dans la position de débordement, et capable de représenter toute valeur entre 128 et (2^32 - 1).

Tout cela nécessite un encodeur punycode non standard et j'ai inclus un script qui peut créer une charge utile en utilisant soit la méthode 1 soit la méthode 3 selon les besoins.

Mini-FAQ :

En dehors de la mise à jour d'OpenSSL, existe-t-il d'autres atténuations ?

Les chaînes de certificats sont transmises en texte clair dans la plupart des environnements et une chaîne malveillante pourrait être bloquée en rejetant les connexions TCP qui contiennent un NID 1.3.6.1.5.5.7.8.9 encodé en DER dans un champ SubjectAlternateName OtherName.

Malheureusement, ce champ pourrait être arbitrairement divisé entre deux paquets ou plus et une sorte de dispositif de correspondance de motifs avec état est vraiment nécessaire pour bloquer. Les certificats peuvent également être compressés, mais OpenSSL 3.0.x ne prend pas en charge la compression des certificats pour le moment.

De plus, avec TLS1.3, les chaînes de certificats client sont chiffrées sur le fil, et les versions antérieures de TLS prennent en charge les chaînes de certificats chiffrées lors de la renégociation d'une connexion existante. Cela est parfois fait pour l'authentification par certificat initiée par le serveur. Un filtre réseau ne sera pas efficace dans ces cas.

Comment puis-je savoir si j'utilise openssl 3 dans un binaire lié statiquement ?

root@kitploit:~
 readelf -a [binaire] | grep -i ossl_punycode_decode

cherchera la fonction vulnérable dans un binaire lié statiquement. Seul OpenSSL >= 3.0 contient cette fonction.

Télécharger l’outil