
مختبر إثبات مفهوم يعيد إنتاج CVE-2026-19553، حيث يتخطى CPython ssl.SSLContext.wrap_bio() التحقق من اسم مضيف TLS بصمت عندما يكون server_hostname هو None.
abraxaslabs.tech · github.com/abraxas · @abraxas_null · [email protected] · cve-2026-19553-wrap-bio
CPython ssl - Python Software Foundation
لم يكن ssl.SSLContext.wrap_bio() يشترط أن تكون قيمة server_hostname غير None عند تفعيل check_hostname. يتخطى SSLObject التحقق من اسم المضيف بصمت. يبدو البرنامج وكأنه نجح مع check_hostname=True. لا يوجد ValueError. يتم التحقق من سلسلة الشهادات. لكن لا يتم التحقق من اسم الطرف الآخر. بينما كان wrap_socket() يرفع استثناءً بالفعل. يحوّل asyncio SSLProtocol / start_tls / open_connection القيمة "" إلى None ثم يستدعي wrap_bio.
هجوم رجل في المنتصف (MITM) يمتلك شهادة صالحة من جهة إصدار شهادات (CA) لاسم مضيف مختلف يُكمل المصافحة مع عميل يستخدم wrap_bio / asyncio نسي تمرير server_hostname.
| 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 |
| 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 | MITM / خادم TLS مهاجم؛ الضحية عميل TLS بلغة Python يستخدم wrap_bio أو asyncio دون اسم مضيف |
| License | GNU Affero GPL v3.0 |
| Lab | 127.0.0.1 فقط |
أن يتواجد على المسار (أو أن يكون الخادم الذي كان العميل يقصد الوصول إليه). يقدّم شهادة يثق بها العميل لاسم ليس هو الاسم الذي قصده العميل. إذا استخدم العميل wrap_bio(..., server_hostname=None) أو asyncio مع server_hostname="" بينما check_hostname=True وCERT_REQUIRED، فإن المصافحة تنجح. لم يتم التحقق من الهوية قط. بينما يرفع wrap_socket على السياق نفسه استثناء ValueError قبل انتقال أي بايتات. كما أن تمرير اسم مضيف غير فارغ إلى wrap_bio لا يزال يرفض عدم التطابق.
المنتج نفسه، ثغرة شقيقة متبقية: SNI SSLContext UAF. على الجانب المقابل من TLS. لا تتكاملان معًا.
نشرت PSF CVE-2026-19553. المشكلة python/cpython#156793، PR 158503، الالتزام 6dc0069a. كان _check_sslobject_params يُنفَّذ بالفعل لـ wrap_socket. لكن SSLObject._create كان يتخطاه. NEWS: إكمال مصافحة تحققت من سلسلة الشهادات دون التحقق من هوية الطرف الآخر، دون أي إشارة إلى أن التحقق قد تم تخطيه. بعد الترقيع، يرفع wrap_bio مع check_hostname=True وserver_hostname=None/"" استثناء ValueError("check_hostname requires server_hostname"). بينما يرفع النقل العكسي (backport) إلى 3.12 تحذير DeprecationWarning بدلاً من ذلك.
شغّلت python:3.14.7-slim-bookworm. نفس CA المختبري، وشهادتان طرفيتان: victim.lab وevil.lab. العميل بـ check_hostname=True وCERT_REQUIRED.
INJECT: تم قبول wrap_bio(server_hostname=None) مقابل evil.lab. 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: رفع wrap_bio(server_hostname="victim.lab") مقابل evil.lab استثناء SSLCertVerificationError بسبب عدم تطابق اسم المضيف. NEGATIVE: تم قبول الاستدعاء نفسه مقابل victim.lab. إضافة: تم قبول asyncio.open_connection(..., server_hostname="") على 127.0.0.1:18510 (تتحول "" إلى None، ثم wrap_bio).
المسارات الخاطئة المسجّلة مسبقًا: المسودة الأولى لـ asyncio start_server أسقطت قوس إغلاق؛ التقطها تجميع المضيف قبل compose. أعاد المضيف 3.14.7 (Clang، OpenSSL 3.6.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. Loopback 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
مرّر قيمة غير فارغة لـ server_hostname إلى wrap_bio() أو asyncio.create_connection() أو loop.start_tls(). قم بالترقية إلى 3.12.15 / 3.13.16 / 3.14.8 / 3.15.0. لا تُغيّر الرقعة سوى التخطي الصامت إلى نفس ValueError الذي كان wrap_socket يرفعه بالفعل.