
CVE-2026-19445の概念実証ラボ。CPythonのssl SNI SSLContextにおけるuse-after-free脆弱性で、リモートのTLSクライアントがハンドシェイク中にサーバーのディスパッチコンテキストを解放する。
abraxaslabs.tech · github.com/abraxas · @abraxas_null · [email protected] · cve-2026-19445-sni-uaf
CPython ssl - Python Software Foundation
接続ごとに SSLContext を生成し、sni_callback を設定し、sslobj.context = other_ctx を代入する TLS サーバーでは、OpenSSL がまだその元のコンテキストへの借用ポインタを保持している間に、元のコンテキストへの最後の Python 参照を破棄してしまう可能性があります。次の ClientHello(TLS 1.3 の HelloRetryRequest で十分)がそのポインタを参照します。ハンドシェイクは継続します。プロセスは後でクラッシュする可能性があります。クライアントは影響を受けません。リスニングソケットを長期間ラップしたものも影響を受けません。
リモートの TLS クライアントが、C の SNI コールバックがまだそれを指している間に、サーバーのディスパッチ用 SSLContext を解放させることができます。ハンドシェイクは依然として完了します。クラッシュおよびメモリ破壊のウィンドウが存在します。このラボでは 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 |
| Product | CPython ssl |
| Affected | < 3.12.15、3.13.0-3.13.15、3.14.0-3.14.7、3.15.0a1(3.15.0 より前) |
| Auth | 未認証の TLS クライアント。サーバーが SNI 切り替えを行い、元のコンテキストを破棄する必要がある |
| License | GNU Affero GPL v3.0 |
| Lab | 127.0.0.1 のみ |
接続ごとに新しい SSLContext を生成し、sni_callback をインストールし、そのコールバック内で別のコンテキストを代入して元のコンテキストを破棄させる Python サーバーと TLS で通信します。SNI を送信します。2 回目の 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 を投稿しました。予約 issue は python/cpython#156293 です。実際のマップは PR 158504 / コミット 0f63c2aa です。_servername_callback は借用された OpenSSL 引数を使用していました。SNI が sslobj.context を切り替え、元の SSLContext が GC された後も、2 回目の 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 の後、3 回の gc.collect() 呼び出しで、weakref.ref(dispatch_ctx) は None になる。ハンドシェイクは依然として完了する(TLS_AES_256_GCM_SHA384)。HRR は _msg_callback 内にある(ServerHello のランダム値はオフセット 6)。sni_calls=1:2 回目の ClientHello は生きた Python コールバックに到達しなかった。
セカンダリパス(コールバックが切り替え、del ctx、LookupError を送出)は 3.14.7 を中断させなかった。SSLError: CALLBACK_FAILED、未処理の LookupError。ネガティブ:127.0.0.1:18500 でリッスンソケットをラップするプロセスライフタイムのコンテキスト。ハンドシェイクは機能する。Weakref は生き続ける。
すでに記録済みの誤った道:dispatch だけでなく leaf にも P-384 を固定し、さらにクライアントを X25519 のみにロックすると、HRR ではなく NO_SUITABLE_KEY_SHARE が発生した。CPython は dispatch コンテキストのみを固定する。HRR のマジックは ServerHello のオフセット 6 にあり、2 ではない。最初のプローブは、HRR が発火した場合でもそれを見逃した。dispatch_ctx は wrap_bio の直後にはまだ生きている。なぜなら server.context がそれを保持しているからだ。それが回収可能になるのは SNI の切り替え後のみである。茶番:リバースシェル。オラクルは dispatch_ref is None に加えて完了したハンドシェイクである。
cd lab
./run.sh
Image 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 引数の使用を停止します。