
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.
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.
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.
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.
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.
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.
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 è:
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.
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.
È 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.
Il problema di sicurezza è in ossl_punycode_decode()
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.
All'interno di ossl_punycode_decode() il fulcro del problema è questo controllo di lunghezza errato:
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 ...
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.