
Prova de conceito em laboratório para CVE-2026-19445, um use-after-free do SSLContext de SNI ssl do CPython, onde um cliente TLS remoto libera o contexto de despacho do servidor durante o handshake.
abraxaslabs.tech · github.com/abraxas · @abraxas_null · [email protected] · cve-2026-19445-sni-uaf
CPython ssl - Python Software Foundation
Um servidor TLS que cria um SSLContext por conexão, define sni_callback e atribui sslobj.context = other_ctx pode descartar a última referência Python ao contexto original enquanto o OpenSSL ainda mantém um ponteiro emprestado para ele. O próximo ClientHello (um HelloRetryRequest do TLS 1.3 já basta) consulta esse ponteiro. O handshake continua. O processo pode travar mais tarde. Os clientes não são afetados. Um wrap de longa duração do socket de escuta não é afetado.
Um cliente TLS remoto pode liberar o SSLContext de despacho do servidor enquanto o callback SNI em C ainda aponta para ele. O handshake ainda é concluído. Janela de crash e corrupção de memória. Sem RCE neste laboratório.
| ID | CVE-2026-19445 |
| CWE | CWE-416 |
| CVSS | Crítico: 9.2 CVSS:4.0/AV:N/AC:H/AT:P/PR:N/UI:N/VC:H/VI:H/VA:L |
| Produto | CPython ssl |
| Afetado | < 3.12.15, 3.13.0–3.13.15, 3.14.0–3.14.7, 3.15.0a1 antes de 3.15.0 |
| Autenticação | cliente TLS não autenticado, o servidor deve fazer SNI-switch e descartar o contexto original |
| Licença | GNU Affero GPL v3.0 |
| Laboratório | apenas 127.0.0.1 |
Falar TLS com um servidor Python que cria um SSLContext novo para a conexão, instala sni_callback e, nesse callback, atribui um contexto diferente e deixa o original morrer. Enviar SNI. Forçar um segundo ClientHello (restrinja a curva do servidor para P-384 para que um cliente X25519 padrão receba HelloRetryRequest). Após o GC, o objeto Python desapareceu. O callback C tlsext_servername ainda tem o ponteiro args emprestado de SSL_CTX_set_tlsext_servername_arg. O handshake é concluído neste pin. Sob ASan isso é um use-after-free. Em produção é uma janela de crash / corrupção na próxima vez que essa memória for reutilizada.
Servidores que fazem wrap do socket de escuta uma vez com um contexto de tempo de vida do processo mantêm a referência. Esses estão fora do bug. Clientes wrap_socket estão fora do bug.
Mesmo produto, sobra irmã: wrap_bio ignora a verificação de hostname. Lado TLS oposto. Não se compõem.
A PSF publicou CVE-2026-19445. A issue de reserva é python/cpython#156293. O mapa real é o PR 158504 / commit 0f63c2aa. _servername_callback usava o arg emprestado do OpenSSL. Depois que o SNI troca sslobj.context e o SSLContext original é coletado pelo GC, o segundo ClientHello ainda chama para ele. O patch busca o contexto a partir de SSL_get_app_data → ssl->ctx e limpa o callback em context_dealloc.
Executei os próprios testes do CPython como laboratório contra python:3.14.7-slim-bookworm (3.14.7 está no intervalo afetado; 3.14.8 é a correção). MemoryBIO, sem TCP para o caminho do UAF.
dispatch_ctx.set_ecdh_curve("secp384r1"), cliente padrão X25519. sni_callback atribui sslobj.context = leaf_ctx. Após o primeiro SNI, três chamadas gc.collect(), weakref.ref(dispatch_ctx) é None. O handshake ainda é concluído (TLS_AES_256_GCM_SHA384). O HRR está em _msg_callback (random do ServerHello no offset 6). sni_calls=1: o segundo ClientHello nunca chegou a um callback Python vivo.
Caminho secundário (callback troca, del ctx, levanta LookupError) não abortou o 3.14.7. SSLError: CALLBACK_FAILED, LookupError não levantável. Negativo: contexto de tempo de vida do processo fazendo wrap de um socket de escuta em 127.0.0.1:18500. O handshake funciona. O weakref permanece vivo.
Caminhos errados já registrados: fixar P-384 na leaf assim como no dispatch, além de travar o cliente para apenas X25519, produziu NO_SUITABLE_KEY_SHARE em vez de HRR. O CPython só fixa o contexto de dispatch. A mágica do HRR está no offset 6 do ServerHello, não no 2. A primeira sonda perdeu o HRR mesmo quando ele disparou. dispatch_ctx ainda está vivo logo após wrap_bio porque server.context o mantém. Ele se torna coletável apenas após a troca de SNI. Teatro: um reverse shell. O oráculo é dispatch_ref is None mais um handshake concluído.
cd lab
./run.sh
Imagem python:3.14.7-slim-bookworm. Projeto Compose cve-2026-19445. Loopback 127.0.0.1:18500 é o wrap de escuta negativo. Os oráculos primário/secundário estão em MemoryBIO em memória.
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
Mantenha uma referência a todo SSLContext que define sni_callback durante o tempo de vida do servidor. Atualize para 3.12.15 / 3.13.16 / 3.14.8 / 3.15.0. O patch em C para de usar o arg emprestado do OpenSSL.