Skip to content
KitploitKITPLOIT
StrumentiBlog
Invia
StrumentiBlog
Invia

Strumenti di Hacking, PenTest e Cybersecurity per il tuo Arsenale di Sicurezza!

Kitploit è una directory di strumenti di hacking, cybersecurity e pentesting. Scopri gli ultimi aggiornamenti dei progetti per trovare vulnerabilità, analizzare sistemi, automatizzare i test e rafforzare la tua sicurezza.

··Feed·Contatto·Privacy·© 2026 Kitploit

Directory degli strumenti

Categorie

Vedi tutte le categorie
Loading categories
CVE-2022-3602 — Approfondimento tecnico e anti-POC per CVE-2022-3602, un buffer overflow di punycode in OpenSSL 3.0.x, con script di riproduzione, analisi dello stack e valutazione delle mitigazioni del compilatore. | Kitploit
Strumenti/GitHubGitHub/colmmacc/cve-2022-3602
Analisi delle VulnerabilitàExploitCrittografiaAnalisi di BinariPaper e RicercaApprendimento e Formazione
GitHubcolmmacc/cve-2022-3602

CVE-2022-3602

Approfondimento tecnico e anti-POC per CVE-2022-3602, un buffer overflow di punycode in OpenSSL 3.0.x, con script di riproduzione, analisi dello stack e valutazione delle mitigazioni del compilatore.

Vedi Repository
169303 anni faRevisionato da Kitploit

Più Popolari

Vedi tutti →

Scopri gli strumenti più utilizzati dalla nostra community.

Esplora tutti gli strumenti

Sfoglia la nostra collezione di strumenti

Vedi tutti gli strumenti →
Condividi

CVE−2022-3602

Cos'è questo?

Questo documento e repository sono un write-up di CVE−2022-3602, un problema di buffer overflow in punycode in OpenSSL. È un "anti-POC" (il problema non sembra sfruttabile) destinato a chi mantiene le proprie build di OpenSSL e ai manutentori di compilatori.

C'è un CVE separato nella stessa release, CVE-2022-3786, che porta anch'esso a buffer overflow ma in quel caso un attaccante non può controllare il contenuto. Qui non c'è una riproduzione per quel problema, ma quel problema può portare a un Denial of Service a causa di crash.

I crash e i buffer overflow non sono mai buoni e se stai usando OpenSSL 3.0.x, è prudente aggiornare il prima possibile.

Sentiti libero di segnalare errori o omissioni tramite issue GitHub o pull-requests.

Qual è il problema?

C'è un errore off-by-one nel modo in cui ossl_punycode_decode gestisce la decodifica punycode che risulta in un overflow di 4 byte. Questo problema è ragionevole solo quando OpenSSL elabora una catena di certificati e richiede due condizioni. In primo luogo, un certificato CA o Intermediario in una catena deve contenere un campo name-constraint che utilizza punycode.

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

In secondo luogo, il certificato leaf deve contenere un campo SubjectAlternateName (SAN) otherName che specifica una stringa SmtpUTF8Mailbox.

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

Quando attivato, il punycode nel campo nameConstraints, ma non il punycode nel campo otherName, verrà gestito dal vulnerabile parsing punycode di OpenSSL.

Quanto è facile attivare questo problema?

David Benjamin e Matt Caswell hanno determinato che il controllo nameConstraint avviene dopo la normale validazione della catena di certificati e la verifica della firma. Per la maggior parte delle applicazioni questo significa che il problema non può essere attivato con un certificato self-signed o una catena non valida.

Nota che le applicazioni s_client e s_server di openssl sono destinate al debug e non interrompono l'elaborazione quando una catena è non valida.

Una CA o Intermediario fidato dovrà contenere il payload malevolo, e dovrà anche aver firmato il certificato leaf che attiva il problema.

Potrebbero esserci alcuni ambienti in cui parti non fidate sono le CA o gli Intermediari, ad esempio un servizio di hosting che supporta CA private fornite dal cliente, ma non è comune.

Il problema porta a esecuzione di codice remoto?

La risposta per molte applicazioni sarà "no" a causa di come il compilatore ha disposto lo stack e a causa della presenza di altre protezioni come stack canaries / stack cookie, padding, PIE, FORTIFY_SOURCE.

Il problema porta effettivamente a un overflow di 32 bit nello stack. Questo non è sufficiente per eseguire direttamente shell code, ma potrebbe essere sufficiente per alterare il flusso di controllo di un'applicazione. Ad esempio, saltare a shell code che è stato incorporato in una catena di certificati X509 potrebbe essere possibile se questi dati vengono anche copiati nello stack in una posizione eseguibile.

Su tutte le piattaforme Linux che ho testato, l'overflow avviene nel padding ed è innocuo. In teoria, un compilatore potrebbe disporre le variabili in modo tale che l'overflow si verifichi in una delle altre variabili nella funzione ossl_a2ulabel.

A seconda dell'inlining, la lista completa delle variabili presenti è:

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

e nessuna mi sembra fornire un percorso ovvio per l'escalation dei privilegi o un controllo interessante.

Ho allegato un archivio tar con strumenti che possono essere usati per creare riproduzioni e overflow con il massimo controllo possibile su tutti e quattro i byte. La stringa di riproduzione di riferimento ( xn--ww90271...aaaa) fa overflow sui quattro byte con i valori 0xFF 0x0F 0x0F 0x0F. Se questo non crasha un'applicazione, è possibile (probabile?) che quell'applicazione non sia vulnerabile.

Come posso riprodurre questo problema?

Lo script shell run-poc può essere usato per generare una catena di certificati malevola. Un certificato CA malevolo viene generato da ca.cnf e un certificato leaf attivante viene generato da leaf.cnf.

Il certificato CA usa il seguente payload di riferimento:

xn--ww902716aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa

Uno script Python può essere usato per generare altre stringhe punycode per payload diversi.

Quando eseguito, run-poc avvierà un client e un server openssl e tenterà di sfruttare il problema dieci volte.

Un OpenSSL vulnerabile probabilmente crasha. Questo non significa che la versione di OpenSSL sia vulnerabile a un RCE, poiché stack canaries e protezioni stack cookie tipicamente causano anche un crash (più sicuro) dell'applicazione. Nota anche che questo non cambia la gravità dell'altro CVE nella stessa release.

Come funziona questo problema?

È sorprendentemente complesso ottenere un controllo quasi completo di tutti e quattro i byte di overflow e richiede lo sfruttamento del decoder punycode di OpenSSL con punycode non standard / non valido. L'archivio tar allegato contiene uno script che può costruire una stringa che gestisce la complessità. Quello che segue è una spiegazione di come funziona.

Preparazione

Il problema di sicurezza è in 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 viene chiamato da ossl_a2ulabel. Il buffer pEncoded è un buffer di dimensione più o meno arbitraria che proviene da una catena di certificati X509. È la parte che viene dopo ogni "xn--" in un campo nameConstraint. Vedi la [riproduzione] per come riprodurre una tale catena di certificati.

pDecoded è un array di unsigned int di dimensione LABEL_BUF_SIZE. LABEL_BUF_SIZE è 512, e sulla maggior parte delle piattaforme un unsigned int ha larghezza 4 byte. Quindi sulla maggior parte delle piattaforme pDecoded è lungo 2048 byte.

La scena

All'interno di ossl_punycode_decode() il fulcro del problema è questo controllo di lunghezza errato:

root@kitploit:~
 if (written_out > max_out)

max_out corrisponde a *pout_length che è sempre 512. E written_out tiene traccia di quanti unsigned int sono stati scritti in pDecoded. Poiché written_out viene incrementato solo dopo la scrittura, questo controllo errato permette di scrivere 513 unsigned int in pDecoded. Il risultato finale assomiglia a questo ...

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

Qui, per convenzione del C, gli indici sono zero-based, quindi lo slot numero 511 è il 512° elemento dell'array. 'P' è un payload di quattro byte che è stato messo fuori dai limiti, oltre lo spazio allocato nello stack per il buffer buf in ossl_a2ulabel() a cui punta pDecoded.

Quattro byte sono un piccolo overflow, e non sono sufficienti per trasportare un nop-sled o eseguire direttamente shell code, ma sono sufficienti per alterare il flusso di controllo di un'applicazione. Ad esempio, saltare a shell code che è stato incorporato in una catena di certificati x509 potrebbe essere possibile, a seconda di come questi dati (o frammenti copiati di questi dati) vengono memorizzati e se quella memoria è eseguibile. Tuttavia c'è ancora più difficoltà per un potenziale attaccante.

In primo luogo, il padding e l'allineamento dello stack da parte del compilatore, o difese come stack canaries, possono rendere qualsiasi sfruttamento completamente impossibile.

In secondo luogo, c'è solo un percorso per ossl_punycode_decode() e questo percorso utilizza un buffer nello stack. Questo rende improbabile che il problema possa essere usato per overflow concorrenti di 4 byte in diverse posizioni di memoria.

Decodifica Punycode

Le stringhe punycode hanno fondamentalmente due forme. Una è xn--c1yn36f (點看) e un'altra è xn--maccrthaigh-n7a (maccárthaigh). La parte che viene dopo l'ultimo delimitatore - è una codifica bootstring in base 36 di qualsiasi code-point unicode che non sia ascii ordinario di base insieme alla posizione nella stringa per inserirli. Quello che è importante ora è che il processo di decodifica in ossl_punycode_decode() produce due valori. Uno è 'n' che è il valore unsigned int del code-point da inserire, e l'altro è 'i' che è la posizione nel buffer in cui inserirlo.

La scrittura può avvenire in due modi diversi. Se i è da qualche parte nel mezzo della stringa allora c'è un memmove() che prima "fa spazio" copiando tutto a destra di uno slot:

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

e poi scrive n nello spazio appena creato:

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

se i è alla fine della stringa, allora memmove() non ha effetto perché il parametro finale sarà 0. L'altra linea diventa un semplice append.

Ora esamineremo i tre diversi modi per ottenere un payload 'P' nella posizione di overflow e perché sorgono i vincoli.

Metodo 1 - overflow ascii

Il modo più semplice per attivare l'overflow è creare una stringa punycode che ha 511 caratteri ascii, e due caratteri non ascii. La codifica punycode di una stringa lunga 513 caratteri come "ÁÁAAAAAAAA...AAA" sarebbe sufficiente. In questo caso ciò che accadrà è che quando written_out è 510 avremo un buffer disposto come ...

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

questi sono solo i caratteri ascii di base che sono stati copiati. Poi analizziamo il bootstring punycode e inseriamo un 'Á' alla posizione 0. Anche se potrebbe essere qualsiasi posizione tra 0 e 511 inclusa.

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

poi ripetiamo questo:

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

questo farà sì che l'ordinario 'A' ascii overflow mentre viene "spostato". Il payload di quattro byte in questo caso diventa 0x00 0x00 0x00 0x41. Come vedremo, a causa di come funziona punycode, questo è l'unico modo in cui qualsiasi valore con un byte finale nell'intervallo ascii può essere espresso.

Abbiamo bisogno di usare due caratteri non ascii perché c'è un controllo corretto dei limiti sul numero di caratteri di base, quindi questo deve essere inferiore a 512.

Un vincolo aggiuntivo che il valore del byte finale non può essere 46 sorge perché ossl_punycode_decode() viene chiamato sulla porzione di una stringa che precede un carattere . letterale. Punycode è pensato per etichette di dominio, che non possono contenere punti.

Metodo 2 - overflow non ascii diretto

Il modo successivo più semplice per attivare l'overflow è creare una stringa di 513 caratteri con un carattere non ascii alla fine. Qualcosa come "AAAAAAAAAA...AAÁ". In questo caso per i nostri ultimi due passaggi avremo:

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

e

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

il carattere non ascii andrà direttamente nella posizione di overflow. Il parser punycode di OpenSSL non impone che il valore di overflow qui sia effettivamente un carattere unicode valido. È più o meno un processo di decodifica binaria. Ma le sfumature della decodifica punycode fanno sì che il metodo 2 non sia così flessibile come potrebbe sembrare.

In punycode i valori n e i sono entrambi codificati come un singolo intero a lunghezza variabile che viene poi codificato in ascii usando base36. Potrebbe sembrare impossibile codificare due numeri non correlati come un singolo intero, ma il trucco intelligente che punycode ha è di usare la lunghezza della stringa (fino a quel momento) come campo nascosto.

Ad esempio, supponiamo di avere una stringa punycode con 4 caratteri di base, e uno non di base, come AAÁAA. Questa sarà prima rappresentata solo dai caratteri di base ... AAAA. Il valore unicode di 'Á' è 225 e la sua posizione nella stringa è 2. Il trucco è moltiplicare il valore per la lunghezza più uno, e poi aggiungere la posizione. Quindi diventa ((225 * (4 +1)) + 2) che è 1127, e così viene codificato (in base 36 a lunghezza variabile).

Per decodificare, si fa il contrario. 1127 / 5 è 225 e 1127 % 5 è 2. Ecco come si recuperano due numeri da uno. Ma nota che più la stringa diventa lunga, più sei vincolato su quanto grande possa essere il valore, altrimenti il multiplo non ci starà in un unsigned int. In generale, se la stringa è lunga M caratteri, perdi log M bit di larghezza dal valore.

Quando gestisci il 512° intero, perdi 9 bit di larghezza. Usando il metodo 2, il valore massimo che un payload apparentemente a 32 bit potrebbe essere è in realtà 2^23. Nemmeno tre byte interi. Il metodo 2 è subottimale.

Metodo 3 - stuffing

Per recuperare 4 byte di controllo, il mezzo più efficiente è ripetere il carattere del payload ancora e ancora. Finora ho tralasciato altri due dettagli rilevanti su come viene gestito punycode.

Il primo dettaglio è che i caratteri non ascii non sono codificati in ordine di stringa, ma sono invece codificati in ordine crescente di valore. La stringa "ÉÁ" finirà per essere codificata come "Á alla posizione 1, É alla posizione 0" perché Á ha un valore inferiore (225) rispetto a É (233).

Il secondo dettaglio è che i caratteri non ascii non sono codificati come valori letterali, ma come un delta relativo al valore decodificato più recentemente. Poiché il primo valore non ha un valore precedente a cui essere relativo, c'è un punto di partenza hard-coded di 128.

Questa piccola sfumatura rende punycode molto efficiente in termini di spazio, ma significa anche che un carattere non ascii semplicemente non può essere decodificato a un valore inferiore a 128. Il delta più piccolo è 0, e non c'è modo di esprimere un delta negativo. Quindi se vuoi un numero inferiore a 128, devi usare il metodo 1.

Significa anche che la migliore strategia per il massimo controllo sul payload è rendere il payload l'unico valore nell'intera stringa, perché in questo modo otteniamo la piena larghezza con cui lavorare dalla sua posizione nella 0° posizione nella codifica. La stringa che codifichi finisce per assomigliare a;

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

che sarà decodificata da OpenSSL come ...

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

con P nella posizione di overflow, e in grado di rappresentare qualsiasi valore tra 128 e (2^32 - 1).

Tutto questo richiede un codificatore punycode non standard e ho incluso uno script che può creare un payload usando il metodo 1 o il metodo 3 secondo necessità.

Mini-FAQ:

A parte l'aggiornamento di OpenSSL, ci sono altre mitigazioni?

Le catene di certificati vengono passate in chiaro nella maggior parte degli ambienti e una catena malevola potrebbe essere bloccata rifiutando connessioni TCP che contengono un NID 1.3.6.1.5.5.7.8.9 codificato DER in un campo SubjectAlternateName OtherName.

Sfortunatamente, questo campo potrebbe essere suddiviso arbitrariamente tra due o più pacchetti e in realtà è necessario una sorta di pattern matcher stateful per bloccare. I certificati possono anche essere compressi, ma OpenSSL 3.0.x non supporta la compressione dei certificati al momento.

Inoltre, con TLS1.3 le catene di certificati client sono crittografate in transito, e versioni precedenti di TLS supportano catene di certificati crittografate quando si rinegozia una connessione esistente. Questo a volte viene fatto per l'autenticazione del certificato avviata dal server. Un filtro di rete non sarà efficace in questi casi.

Come posso sapere se sto usando openssl 3 in un binario collegato staticamente?

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

cercherà la funzione vulnerabile in un binario collegato staticamente. Solo OpenSSL >= 3.0 contiene questa funzione.

Scarica lo strumento