
Proof-of-concept lab per CVE-2026-19445, un caso di use-after-free in CPython ssl SNI SSLContext in cui un client TLS remoto libera il contesto di dispatch del server durante l'handshake.
abraxaslabs.tech · github.com/abraxas · @abraxas_null · [email protected] · cve-2026-19445-sni-uaf
CPython ssl - Python Software Foundation
Un server TLS che crea un SSLContext per ogni connessione, imposta sni_callback e assegna sslobj.context = other_ctx può eliminare l'ultimo riferimento Python al contesto originale mentre OpenSSL ne detiene ancora un puntatore preso in prestito. Il successivo ClientHello (basta un HelloRetryRequest TLS 1.3) consulta quel puntatore. L'handshake prosegue. Il processo può andare in crash in seguito. I client non sono interessati. Un wrap di lunga durata del socket in ascolto non è interessato.
Un client TLS remoto può liberare l'SSLContext di dispatch del server mentre il callback SNI in C punta ancora ad esso. L'handshake si completa comunque. Finestra di crash e corruzione della memoria. Nessun RCE in questo lab.
| ID | CVE-2026-19445 |
| CWE | CWE-416 |
| CVSS | Critico: 9.2 CVSS:4.0/AV:N/AC:H/AT:P/PR:N/UI:N/VC:H/VI:H/VA:L |
| Prodotto | CPython ssl |
| Interessati | < 3.12.15, 3.13.0-3.13.15, 3.14.0-3.14.7, 3.15.0a1 prima di 3.15.0 |
| Auth | client TLS non autenticato, il server deve effettuare lo switch SNI e rilasciare il contesto originale |
| Licenza | GNU Affero GPL v3.0 |
| Lab | solo 127.0.0.1 |
Parlare TLS con un server Python che crea un nuovo SSLContext per la connessione, installa sni_callback e in quel callback assegna un contesto diverso lasciando morire quello originale. Inviare SNI. Forzare un secondo ClientHello (limitare la curva del server a P-384 in modo che un client predefinito X25519 riceva un HelloRetryRequest). Dopo il GC, l'oggetto Python non c'è più. Il callback C tlsext_servername ha ancora il puntatore args preso in prestito da SSL_CTX_set_tlsext_servername_arg. L'handshake si completa su questo pin. Sotto ASan si tratta di un use-after-free. In produzione è una finestra di crash / corruzione la prossima volta che quella memoria viene riutilizzata.
I server che avvolgono il socket in ascolto una sola volta con un contesto dalla durata pari a quella del processo mantengono il riferimento. Questi sono fuori dal bug. I client wrap_socket sono fuori dal bug.
Stesso prodotto, residuo affine: wrap_bio salta la verifica dell'hostname. Lato TLS opposto. Non si compongono.
La PSF ha pubblicato CVE-2026-19445. La issue di riserva è python/cpython#156293. La mappa reale è la PR 158504 / commit 0f63c2aa. _servername_callback usava l'argomento OpenSSL preso in prestito. Dopo che SNI cambia sslobj.context e l'SSLContext originale viene raccolto dal GC, il secondo ClientHello chiama ancora dentro di esso. La patch recupera il contesto da SSL_get_app_data -> ssl->ctx e azzera il callback in context_dealloc.
Ho eseguito i test di CPython stessi come lab contro python:3.14.7-slim-bookworm (3.14.7 rientra nell'intervallo interessato; 3.14.8 è la correzione). MemoryBIO, nessun TCP per il percorso UAF.
dispatch_ctx.set_ecdh_curve("secp384r1"), il client usa X25519 per impostazione predefinita. sni_callback assegna sslobj.context = leaf_ctx. Dopo il primo SNI, tre chiamate a gc.collect(), weakref.ref(dispatch_ctx) è None. L'handshake si completa comunque (TLS_AES_256_GCM_SHA384). L'HRR è in _msg_callback (random del ServerHello all'offset 6). sni_calls=1: il secondo ClientHello non ha mai raggiunto un callback Python attivo.
Il percorso secondario (il callback cambia contesto, del ctx, solleva LookupError) non ha interrotto la 3.14.7. SSLError: CALLBACK_FAILED, LookupError non sollevabile. Caso negativo: contesto con durata pari a quella del processo che avvolge un socket in ascolto su 127.0.0.1:18500. L'handshake funziona. Il weakref resta attivo.
Vicoli ciechi già registrati: fissare P-384 sia sul leaf che sul dispatch, più il blocco del client su solo X25519, ha prodotto NO_SUITABLE_KEY_SHARE invece dell'HRR. CPython fissa solo il contesto di dispatch. Il magic dell'HRR è all'offset 6 del ServerHello, non 2. La prima sonda ha mancato l'HRR anche quando è scattato. dispatch_ctx è ancora attivo subito dopo wrap_bio perché server.context lo mantiene. Diventa collezionabile solo dopo il cambio SNI. Teatro: una reverse shell. L'oracolo è dispatch_ref is None più un handshake completato.
cd lab
./run.sh
Immagine python:3.14.7-slim-bookworm. Progetto Compose cve-2026-19445. Il loopback 127.0.0.1:18500 è il wrapper di ascolto negativo. Gli oracoli primario/secondario sono MemoryBIO in memoria.
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
Mantieni un riferimento a ogni SSLContext che imposta sni_callback per tutta la durata del server. Aggiorna a 3.12.15 / 3.13.16 / 3.14.8 / 3.15.0. La patch C smette di usare l'argomento OpenSSL preso in prestito.