
Technischer Deep-Dive und Anti-PoC für CVE-2022-3602, einen Punycode-Pufferüberlauf in OpenSSL 3.0.x, mit Reproduktionsskripten, Stack-Analyse und Compiler-Mitigation-Bewertung.
Dieses Dokument und Repository ist eine Aufarbeitung von CVE‑2022‑3602, einem Punycode‑Pufferüberlauf‑Problem in OpenSSL. Es ist ein "Anti‑POC" (das Problem scheint nicht ausnutzbar zu sein) und richtet sich an Leute, die ihre eigenen OpenSSL‑Builds pflegen, und an Compiler‑Entwickler.
Es gibt eine separate CVE in derselben Version, CVE‑2022‑3786, die ebenfalls zu Pufferüberläufen führt, aber ein Angreifer kann in diesem Fall den Inhalt nicht kontrollieren. Es gibt hier keine Reproduktion für dieses Problem, aber dieses Problem kann aufgrund eines Absturzes zu einem Denial‑of‑Service führen.
Abstürze und Pufferüberläufe sind nie gut, und wenn Sie OpenSSL 3.0.x verwenden, ist es ratsam, so schnell wie möglich zu aktualisieren.
Melden Sie etwaige Fehler oder Auslassungen bitte über GitHub‑Issues oder Pull‑Requests.
Es gibt einen Off‑by‑one‑Fehler in der Art und Weise, wie ossl_punycode_decode
die Punycode‑Dekodierung behandelt, der zu einem 4‑Byte‑Überlauf führt. Dieses
Problem ist nur dann sinnvoll, wenn OpenSSL eine Zertifikatskette verarbeitet,
und erfordert zwei Bedingungen. Erstens muss ein CA‑ oder Zwischenzertifikat in
einer Kette ein Namenseinschränkungsfeld enthalten, das Punycode verwendet.
nameConstraints = permitted;email:xn‑maccrthaigh‑n7a.com
Zweitens muss das Blattzertifikat ein SubjectAlternateName‑(SAN‑)otherName‑Feld enthalten, das eine SmtpUTF8Mailbox‑Zeichenkette angibt.
otherName = 1.3.6.1.5.5.7.8.9;UTF8:admin@xn‑maccrthaigh‑n7a.com
Wenn ausgelöst, wird der Punycode im nameConstraints‑Feld, aber nicht der Punycode im otherName‑Feld, von der anfälligen OpenSSL‑Punycode‑Analyse behandelt.
David Benjamin und Matt Caswell haben festgestellt, dass die Prüfung der Namenseinschränkungen nach der gewöhnlichen Zertifikatsketten‑Validierung und Signaturverifikation erfolgt. Für die meisten Anwendungen bedeutet dies, dass das Problem nicht mit einem selbstsignierten Zertifikat oder einer ungültigen Kette ausgelöst werden kann.
Beachten Sie, dass die s_client‑ und s_server‑Anwendungen von openssl für
Debugging‑Zwecke gedacht sind und die Verarbeitung nicht stoppen, wenn eine
Kette ungültig ist.
Eine vertrauenswürdige CA oder ein vertrauenswürdiges Zwischenzertifikat muss die schädliche Nutzlast enthalten und muss außerdem das Blattzertifikat signiert haben, das das Problem auslöst.
Es kann einige Umgebungen geben, in denen nicht vertrauenswürdige Parteien die CAs oder Zwischenstellen sind, z. B. ein Hosting‑Dienst, der kundenbereitgestellte private CAs unterstützt, aber das ist nicht üblich.
Die Antwort für viele Anwendungen wird "nein" sein, aufgrund der Art und Weise, wie der Compiler den Stack angeordnet hat und aufgrund des Vorhandenseins anderer Schutzmaßnahmen wie Stack‑Canaries / Stack‑Cookies, Padding, PIE, FORTIFY_SOURCE.
Das Problem führt tatsächlich zu einem Überlauf von 32 Bit auf dem Stack. Dies reicht nicht aus, um direkt Shell‑Code auszuführen, aber es könnte ausreichen, um den Kontrollfluss einer Anwendung zu ändern. Zum Beispiel könnte ein Sprung zu Shell‑Code, der in eine X509‑Zertifikatskette eingebettet ist, möglich sein, wenn diese Daten auch auf den Stack an eine ausführbare Stelle kopiert werden.
Auf jeder von mir getesteten Linux‑Plattform erfolgt der Überlauf in Padding
und ist harmlos. Theoretisch könnte ein Compiler Variablen so anordnen, dass der
Überlauf in eine der anderen Variablen in der Funktion ossl_a2ulabel erfolgt.
Abhängig von Inlining ist die vollständige Liste der vorhandenen Variablen:
outptr, inptr, size, result, tmpptr, delta, seed, utfsize
und keine davon scheint mir einen offensichtlichen Weg zur Privilegieneskalation oder zu interessanter Kontrolle zu bieten.
Ich habe ein Tarball mit Werkzeugen angehängt, die verwendet werden können, um
Reproduktionen und Überläufe mit so viel Kontrolle über alle vier Bytes wie
möglich zu erstellen. Der Referenz‑Reproduktionsstring (xn--ww90271...aaaa)
überläuft die vier Bytes mit den Werten 0xFF 0x0F 0x0F 0x0F. Wenn dies keine
Anwendung zum Absturz bringt, ist es möglich (wahrscheinlich?), dass diese
Anwendung nicht anfällig ist.
Das Shell‑Skript run-poc kann verwendet werden, um eine schädliche
Zertifikatskette zu erzeugen. Ein bösartiges CA‑Zertifikat wird aus ca.cnf
generiert, und ein auslösendes Blattzertifikat wird aus leaf.cnf generiert.
Das CA‑Zertifikat verwendet die folgende Referenz‑Nutzlast:
xn--ww902716aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
Ein Python‑Skript kann verwendet werden, um andere Punycode‑Strings für verschiedene Nutzlasten zu erzeugen.
Wenn run-poc ausgeführt wird, startet es einen OpenSSL‑Client und -Server und
versucht, das Problem zehnmal auszunutzen.
Ein anfälliges OpenSSL wird wahrscheinlich abstürzen. Das bedeutet nicht, dass die Version von OpenSSL anfällig für eine RCE ist, da Stack‑Canaries und Stack‑Cookie‑Schutzmaßnahmen normalerweise ebenfalls einen (sichereren) Anwendungsabsturz verursachen. Beachten Sie auch, dass dies die Schwere der anderen CVE in derselben Version in keiner Weise ändert.
Es ist überraschend nuanciert, nahezu vollständige Kontrolle über alle vier Überlaufbytes zu erlangen, und erfordert die Ausnutzung des Punycode‑Decoders von OpenSSL mit nicht standardgemäßem / ungültigem Punycode. Das beigefügte Tarball enthält ein Skript, das einen String konstruieren kann, der die Nuance bewältigt. Im Folgenden wird erklärt, wie es funktioniert.
Das Sicherheitsproblem liegt 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 wird von ossl_a2ulabel aufgerufen. Der Puffer
pEncoded ist ein mehr oder weniger beliebig großer Puffer, der aus einer
X509‑Zertifikatskette stammt. Es ist der Teil, der nach einem "xn--" in einem
nameConstraint‑Feld kommt. Siehe die [Reproduktion] für die Reproduktion einer
solchen Zertifikatskette.
pDecoded ist ein Array von unsigned ints der Größe LABEL_BUF_SIZE.
LABEL_BUF_SIZE ist 512, und auf den meisten Plattformen ist ein unsigned int
4 Byte breit. Auf den meisten Plattformen ist pDecoded also 2048 Byte lang.
Innerhalb von ossl_punycode_decode() ist der Kern des Problems diese
falsche Längenprüfung:
if (written_out > max_out)
max_out entspricht *pout_length, das immer 512 ist. Und written_out
verfolgt, wie viele unsigned ints in pDecoded geschrieben wurden. Da
written_out erst später nach dem Schreiben erhöht wird, erlaubt diese
fehlerhafte Prüfung, dass 513 unsigned ints in pDecoded geschrieben werden.
Das Endergebnis sieht ungefähr so aus ...
pDecoded = [ ... , 'X', 'Y' , 'Z' ] 'P'
// Indizes 509 510 511
Hier sind die Indizes gemäß der C‑Konvention nullbasiert, also ist Slot
Nummer 511 das 512. Element im Array. 'P' ist eine vier Byte große Nutzlast,
die außerhalb der Grenzen, jenseits des für den Puffer buf in
ossl_a2ulabel() reservierten Speicherplatzes, abgelegt wurde – auf den
pDecoded zeigt.