Лаборатория proof-of-concept для CVE-2026-19445 — use-after-free в CPython ssl SNI SSLContext, при котором удалённый TLS-клиент освобождает контекст диспетчеризации сервера во время рукопожатия.
abraxaslabs.tech · github.com/abraxas · @abraxas_null · [email protected] · cve-2026-19445-sni-uaf
CPython ssl — Python Software Foundation
TLS-сервер, который создаёт SSLContext на каждое соединение, устанавливает sni_callback и присваивает sslobj.context = other_ctx, может потерять последнюю Python-ссылку на исходный контекст, в то время как OpenSSL всё ещё держит заимствованный указатель на него. Следующий ClientHello (достаточно TLS 1.3 HelloRetryRequest) обращается к этому указателю. Рукопожатие продолжается. Процесс может упасть позже. Клиенты не затронуты. Долгоживущая обёртка слушающего сокета не затронута.
Удалённый TLS-клиент может освободить диспетчерский SSLContext сервера, пока C-колбэк SNI всё ещё указывает на него. Рукопожатие всё равно завершается. Окно краха и повреждения памяти. RCE в этой лаборатории нет.
| ID | CVE-2026-19445 |
| CWE | CWE-416 |
| CVSS | Critical: 9.2 CVSS:4.0/AV:N/AC:H/AT:P/PR:N/UI:N/VC:H/VI:H/VA:L |
| Продукт | CPython ssl |
| Затронуто | < 3.12.15, 3.13.0–3.13.15, 3.14.0–3.14.7, 3.15.0a1 до 3.15.0 |
| Аутентификация | неаутентифицированный TLS-клиент, сервер должен переключить SNI и отбросить исходный контекст |
| Лицензия | GNU Affero GPL v3.0 |
| Лаборатория | только 127.0.0.1 |
Общаться по TLS с Python-сервером, который создаёт свежий SSLContext для соединения, устанавливает sni_callback и в этом колбэке присваивает другой контекст, а затем позволяет исходному умереть. Отправить SNI. Форсировать второй ClientHello (ограничить кривую сервера до P-384, чтобы клиент по умолчанию с X25519 получил HelloRetryRequest). После GC Python-объект исчезает. C-колбэк tlsext_servername всё ещё имеет заимствованный указатель args из SSL_CTX_set_tlsext_servername_arg. Рукопожатие завершается на этом пине. Под ASan это use-after-free. В продакшене это окно краха / повреждения при следующем переиспользовании этой памяти.
Серверы, которые оборачивают слушающий сокет один раз контекстом времени жизни процесса, сохраняют ссылку. Они вне бага. Клиенты wrap_socket вне бага.
Тот же продукт, родственный остаток: wrap_bio пропускает проверку имени хоста. Противоположная сторона TLS. Они не сочетаются.
PSF опубликовал CVE-2026-19445. Проблема резервирования — python/cpython#156293. Настоящая карта — PR 158504 / коммит 0f63c2aa. _servername_callback использовал заимствованный аргумент OpenSSL. После того как SNI переключает sslobj.context и исходный SSLContext собирается GC, второй ClientHello всё ещё вызывает его. Патч ищет контекст через SSL_get_app_data → ssl->ctx и очищает колбэк в context_dealloc.
Я запустил собственные тесты CPython как лабораторию против python:3.14.7-slim-bookworm (3.14.7 входит в затронутый диапазон; 3.14.8 — исправление). MemoryBIO, без TCP для пути UAF.
dispatch_ctx.set_ecdh_curve("secp384r1"), клиент по умолчанию X25519. sni_callback присваивает sslobj.context = leaf_ctx. После первого SNI три вызова gc.collect(), weakref.ref(dispatch_ctx) — None. Рукопожатие всё равно завершается (TLS_AES_256_GCM_SHA384). HRR находится в _msg_callback (random ServerHello по смещению 6). sni_calls=1: второй ClientHello так и не достиг живого Python-колбэка.
Вторичный путь (колбэк переключает, del ctx, вызывает LookupError) не прервал 3.14.7. SSLError: CALLBACK_FAILED, необрабатываемый LookupError. Отрицательный: контекст времени жизни процесса, оборачивающий слушающий сокет на 127.0.0.1:18500. Рукопожатие работает. Weakref остаётся живым.
Уже зафиксированные неверные повороты: пиннинг P-384 на leaf, а также на dispatch, плюс блокировка клиента только на X25519, дали NO_SUITABLE_KEY_SHARE вместо HRR. CPython пиннит только диспетчерский контекст. Магия HRR находится по смещению 6 в ServerHello, а не 2. Первый зонд пропустил HRR, даже когда он сработал. dispatch_ctx всё ещё жив сразу после wrap_bio, потому что server.context держит его. Он становится собираемым только после переключения SNI. Театр: reverse shell. Оракул — dispatch_ref is None плюс завершённое рукопожатие.
cd lab
./run.sh
Образ python:3.14.7-slim-bookworm. Compose-проект cve-2026-19445. Loopback 127.0.0.1:18500 — отрицательная обёртка слушателя. Первичный/вторичный оракулы находятся в памяти 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
Держите ссылку на каждый SSLContext, который устанавливает sni_callback, на протяжении времени жизни сервера. Обновитесь до 3.12.15 / 3.13.16 / 3.14.8 / 3.15.0. C-патч перестаёт использовать заимствованный аргумент OpenSSL.