
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 / commit 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 が修正版)。MemoryBIO を使用し、UAF パスには 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、unraisable な LookupError。ネガティブ: 127.0.0.1:18500 でリスンソケットをラップするプロセス存続期間のコンテキスト。ハンドシェイクは機能します。Weakref は生き続けます。
すでに記録された誤った道: ディスパッチと同様に leaf にも P-384 をピン留めし、さらにクライアントを X25519 のみにロックすると、HRR ではなく NO_SUITABLE_KEY_SHARE が発生しました。CPython はディスパッチコンテキストのみをピン留めします。HRR マジックは ServerHello のオフセット 6 にあり、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。ループバック 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 引数の使用を停止します。