
Proof-of-Concept-Lab für CVE-2026-19445, einen CPython-ssl-SNI-SSLContext-Use-after-free, bei dem ein entfernter TLS-Client den Dispatch-Kontext des Servers während des Handshakes freigibt.
abraxaslabs.tech · github.com/abraxas · @abraxas_null · [email protected] · cve-2026-19445-sni-uaf
CPython ssl - Python Software Foundation
Ein TLS-Server, der pro Verbindung einen SSLContext erzeugt, sni_callback setzt und sslobj.context = other_ctx zuweist, kann die letzte Python-Referenz auf den ursprünglichen Kontext verlieren, während OpenSSL noch einen geliehenen Zeiger darauf hält. Das nächste ClientHello (ein TLS 1.3 HelloRetryRequest genügt) konsultiert diesen Zeiger. Der Handshake läuft weiter. Der Prozess kann später abstürzen. Clients sind nicht betroffen. Ein langlebiger Wrapper des Listening-Sockets ist nicht betroffen.
Ein entfernter TLS-Client kann den Dispatch-SSLContext des Servers freigeben, während der C-SNI-Callback noch darauf zeigt. Der Handshake wird dennoch abgeschlossen. Absturz- und Speicherkorruptionsfenster. Kein RCE in diesem Lab.
| ID | CVE-2026-19445 |
| CWE | CWE-416 |
| CVSS | Kritisch: 9.2 CVSS:4.0/AV:N/AC:H/AT:P/PR:N/UI:N/VC:H/VI:H/VA:L |
| Produkt | CPython ssl |
| Betroffen | < 3.12.15, 3.13.0-3.13.15, 3.14.0-3.14.7, 3.15.0a1 vor 3.15.0 |
| Auth | nicht authentifizierter TLS-Client, der Server muss per SNI umschalten und den ursprünglichen Kontext verwerfen |
| Lizenz | GNU Affero GPL v3.0 |
| Lab | nur 127.0.0.1 |
TLS mit einem Python-Server sprechen, der für die Verbindung einen frischen SSLContext erzeugt, sni_callback installiert und in diesem Callback einen anderen Kontext zuweist und dann den ursprünglichen sterben lässt. SNI senden. Ein zweites ClientHello erzwingen (beschränke die Server-Kurve auf P-384, sodass ein Standard-X25519-Client einen HelloRetryRequest erhält). Nach der GC ist das Python-Objekt verschwunden. Der C-Callback tlsext_servername hält noch den geliehenen args-Zeiger aus SSL_CTX_set_tlsext_servername_arg. Der Handshake wird bei diesem Pin abgeschlossen. Unter ASan ist das ein Use-after-Free. In der Produktion ist es ein Absturz- / Korruptionsfenster, sobald dieser Speicher das nächste Mal wiederverwendet wird.
Server, die den Listening-Socket einmal mit einem Kontext mit Prozesslebensdauer umhüllen, behalten die Referenz. Diese liegen außerhalb des Bugs. wrap_socket-Clients liegen außerhalb des Bugs.
Gleiches Produkt, verwandtes Überbleibsel: wrap_bio überspringt die Hostnamen-Verifikation. Entgegengesetzte TLS-Seite. Sie lassen sich nicht kombinieren.
Die PSF veröffentlichte CVE-2026-19445. Das Reservierungs-Issue ist python/cpython#156293. Die eigentliche Karte ist PR 158504 / Commit 0f63c2aa. _servername_callback verwendete das geliehene OpenSSL-Argument. Nachdem SNI sslobj.context umschaltet und der ursprüngliche SSLContext per GC freigegeben wurde, ruft das zweite ClientHello weiterhin hinein. Der Patch schlägt den Kontext über SSL_get_app_data -> ssl->ctx nach und löscht den Callback in context_dealloc.
Ich habe die CPython-eigenen Tests als Lab gegen python:3.14.7-slim-bookworm ausgeführt (3.14.7 liegt im betroffenen Bereich; 3.14.8 ist der Fix). MemoryBIO, kein TCP für den UAF-Pfad.
dispatch_ctx.set_ecdh_curve("secp384r1"), Client-Standard X25519. sni_callback weist sslobj.context = leaf_ctx zu. Nach dem ersten SNI, drei gc.collect()-Aufrufe, ist weakref.ref(dispatch_ctx) None. Der Handshake wird dennoch abgeschlossen (TLS_AES_256_GCM_SHA384). HRR befindet sich in _msg_callback (ServerHello random bei Offset 6). sni_calls=1: Das zweite ClientHello erreichte nie einen aktiven Python-Callback.
Der sekundäre Pfad (Callback wechselt, del ctx, wirft LookupError) brach 3.14.7 nicht ab. SSLError: CALLBACK_FAILED, nicht werfbares LookupError. Negativ: prozesslebenslanger Kontext, der einen Listen-Socket auf 127.0.0.1:18500 umschließt. Handshake funktioniert. Weakref bleibt am Leben.
Bereits festgehaltene Fehlversuche: Das Pinning von P-384 sowohl auf dem Leaf als auch auf Dispatch sowie das Festlegen des Clients auf X25519-only erzeugte NO_SUITABLE_KEY_SHARE statt HRR. CPython pinnt nur den Dispatch-Kontext. Die HRR-Magic befindet sich bei ServerHello-Offset 6, nicht bei 2. Die erste Probe verpasste HRR selbst dann, wenn es ausgelöst wurde. dispatch_ctx ist direkt nach wrap_bio noch am Leben, weil server.context es hält. Es wird erst nach dem SNI-Wechsel einsammelbar. Theater: eine Reverse Shell. Das Orakel ist dispatch_ref is None plus ein abgeschlossener Handshake.
cd lab
./run.sh
Image python:3.14.7-slim-bookworm. Compose-Projekt cve-2026-19445. Loopback 127.0.0.1:18500 ist der negative Listen-Wrap. Primäre/sekundäre Oracles sind In-Memory-MemoryBIO.
primary_dispatch_ref_after_sni_gc='none'
primary_hrr='yes'
primary_handshake='complete'
SUCCESS CVE-2026-19445 dispatch_ctx_gc=yes handshake=complete hrr=yes secondary_crash=no CVE-2026-19445-SNI-UAF-WITNESS
Behalten Sie eine Referenz auf jeden SSLContext, der sni_callback setzt, für die gesamte Lebensdauer des Servers. Führen Sie ein Upgrade auf 3.12.15 / 3.13.16 / 3.14.8 / 3.15.0 durch. Der C-Patch verwendet das geliehene OpenSSL-Argument nicht mehr.