
CVE-2026-19553を再現するProof-of-Conceptラボ。CPythonの`ssl.SSLContext.wrap_bio()`が`server_hostname`がNoneの場合にTLSホスト名検証をサイレントにスキップする問題。
abraxaslabs.tech · github.com/abraxas · @abraxas_null · [email protected] · cve-2026-19553-wrap-bio
CPython ssl - Python Software Foundation
ssl.SSLContext.wrap_bio() は、check_hostname が設定されている場合に server_hostname が非 None であることを要求していませんでした。SSLObject はホスト名の検証を暗黙のうちにスキップします。プログラムは check_hostname=True で成功したように見えます。ValueError は発生しません。証明書チェーンは検証されます。しかしピアの名前は検証されません。wrap_socket() はすでに例外を送出していました。asyncio の SSLProtocol / start_tls / open_connection は "" を None に変換したうえで wrap_bio を呼び出します。
別のホスト名に対する CA 有効な証明書を持つ MITM は、server_hostname の受け渡しを忘れた wrap_bio / asyncio クライアントに対してハンドシェイクを完了させることができます。
| ID | CVE-2026-19553 |
| CWE | CWE-297 |
| CVSS | High: 7.6 CVSS:4.0/AV:N/AC:L/AT:P/PR:N/UI:P/VC:H/VI:H/VA:N |
| 製品 | CPython ssl |
| 影響範囲 | < 3.12.15、3.13.0-3.13.15、3.14.0-3.14.7、3.15.0a1(3.15.0 より前) |
| 認証 | MITM / 攻撃者の TLS サーバー。被害者はホスト名なしで wrap_bio または asyncio を使用する Python TLS クライアント |
| ライセンス | GNU Affero GPL v3.0 |
| ラボ | 127.0.0.1 のみ |
通信経路上に位置する(またはクライアントが接続しようとしたサーバーになる)ことができます。クライアントが意図した名前とは異なる名前に対して、クライアントが信頼する証明書を提示します。クライアントが check_hostname=True かつ CERT_REQUIRED の状態で wrap_bio(..., server_hostname=None) または server_hostname="" を指定した asyncio を使用していた場合、ハンドシェイクは成功します。識別情報は一切検証されませんでした。同じコンテキストで wrap_socket を使用すると、バイトが一切流れる前に ValueError を送出します。wrap_bio に空でないホスト名を渡した場合は、不一致が依然として拒否されます。
同じ製品における、もう一つの類似の残存問題: SNI SSLContext UAF。TLS の反対側です。これらは組み合わせて悪用することはできません。
PSF は CVE-2026-19553 を公開しました。Issue python/cpython#156793、PR 158503、コミット 6dc0069a。_check_sslobject_params は wrap_socket に対してはすでに実行されていました。SSLObject._create はそれをスキップしていました。NEWS: 証明書チェーンを検証しつつピアの識別情報を検証せずにハンドシェイクを完了させ、しかもチェックがスキップされたことを示す表示が一切ないこと。パッチ適用後、check_hostname=True かつ server_hostname=None/"" を指定した wrap_bio は ValueError("check_hostname requires server_hostname") を送出します。3.12 へのバックポートでは代わりに DeprecationWarning を送出します。
python:3.14.7-slim-bookworm を用意しました。同じラボ CA、2 つのリーフ証明書: victim.lab と evil.lab。クライアントは check_hostname=True、CERT_REQUIRED。
INJECT: evil.lab に対する wrap_bio(server_hostname=None) が受け入れられました。ピア SAN は evil.lab。SNI は None。TLS_AES_256_GCM_SHA384。CONTROL A: wrap_socket(server_hostname=None) は ValueError: check_hostname requires server_hostname を送出しました。CONTROL B: evil.lab に対する wrap_bio(server_hostname="victim.lab") は SSLCertVerificationError のホスト名不一致を送出しました。NEGATIVE: victim.lab に対する同じ呼び出しは受け入れられました。追加: 127.0.0.1:18510 に対する asyncio.open_connection(..., server_hostname="") が受け入れられました("" が None になり、その後 wrap_bio が呼び出される)。
誤ったターンはすでに記録済み: 最初の asyncio start_server ドラフトは閉じ括弧を落としていた; ホスト側のコンパイルが compose の前にそれを捕捉した。ホスト 3.14.7 (Clang, OpenSSL 3.6.4) でも同じ4つのオラクルが再現された; ラボ記録はコンテナのピン (GCC, OpenSSL 3.0.22) である。演目: リバースシェル。オラクルは accept-wrong-name に加え、wrap_socket が依然として例外を送出することである。
cd lab
./run.sh
Image python:3.14.7-slim-bookworm。Compose プロジェクト cve-2026-19553。ループバック 127.0.0.1:18510 は asyncio の追加分です。MemoryBIO はポートを必要としません。
INJECT wrap_bio_none vs evil.lab: accept peer=evil.lab
CONTROL_A wrap_socket_none: ValueError:check_hostname requires server_hostname
CONTROL_B wrap_bio_name='victim.lab' vs evil.lab: mismatch-reject
SUCCESS CVE-2026-19553 wrap_bio_none=accept wrap_socket_none=ValueError wrap_bio_name=mismatch-reject CVE-2026-19553-WRAPBIO-HOST-WITNESS
wrap_bio()、asyncio.create_connection()、または loop.start_tls() に空でない server_hostname を渡してください。3.12.15 / 3.13.16 / 3.14.8 / 3.15.0 にアップグレードしてください。このパッチは、サイレントスキップを、wrap_socket がすでに発生させていたのと同じ ValueError に変更するだけです。