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 客户端可以在 C 层 SNI 回调仍指向服务器的调度 SSLContext 时释放它。握手仍会完成。存在崩溃和内存破坏窗口。本实验中没有 RCE。
| ID | CVE-2026-19445 |
| CWE | CWE-416 |
| CVSS | 严重: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 |
与一个 Python 服务器进行 TLS 通信,该服务器为连接创建新的 SSLContext,安装 sni_callback,并在该回调中赋值一个不同的上下文,然后让原始上下文被销毁。发送 SNI。强制产生第二个 ClientHello(将服务器曲线限制为 P-384,这样默认的 X25519 客户端会收到 HelloRetryRequest)。GC 之后,Python 对象已消失。C 层的 tlsext_servername 回调仍然持有来自 SSL_CTX_set_tlsext_servername_arg 的借用 args 指针。握手在此固定版本上完成。在 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 是修复版本)。UAF 路径使用 MemoryBIO,不使用 TCP。
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 中(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,并将客户端锁定为仅 X25519,会产生 NO_SUITABLE_KEY_SHARE 而不是 HRR。CPython 只固定调度上下文。HRR 魔数位于 ServerHello 偏移 6,而不是 2。第一次探测即使 HRR 触发也错过了它。dispatch_ctx 在 wrap_bio 之后仍然存活,因为 server.context 持有它。只有在 SNI 切换之后它才变得可回收。花架子:反向 shell。判定依据是 dispatch_ref is None 加上完成的握手。
cd lab
./run.sh
镜像 python:3.14.7-slim-bookworm。Compose 项目 cve-2026-19445。回环 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
为每个设置了 sni_callback 的 SSLContext 在服务器生命周期内保留引用。升级到 3.12.15 / 3.13.16 / 3.14.8 / 3.15.0。C 补丁停止使用借用的 OpenSSL 参数。