Лаборатория 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 (достаточно HelloRetryRequest в TLS 1.3) обращается к этому указателю. Рукопожатие продолжается. Процесс может упасть позже. Клиенты не затронуты. Долгоживущая обёртка слушающего сокета не затронута.
Удалённый 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 закрепляет только dispatch-контекст. Магия HRR находится по смещению 6 в ServerHello, а не 2. Первый зонд пропустил HRR, даже когда он сработал. dispatch_ctx всё ещё жив сразу после wrap_bio, потому что его держит server.context. Он становится собираемым только после переключения SNI. Театр: обратная оболочка. Оракул — это 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.