
CVE-2025-4138 / CVE-2025-4517 — تجاوز مرشح الروابط الرمزية PATH_MAX في Python tarfile
كتابة ملفات تعسفية عبر استخراج filter="data" / filter="tar"
ثغرة خطيرة في وحدة tarfile في Python تسمح للمهاجم بـ تجاوز مرشحات الاستخراج ("data" و "tar") وكتابة ملفات تعسفية خارج دليل الاستخراج المقصود. عندما تقوم عملية مميزة (مثل سكربت نسخ احتياطي بصلاحيات الجذر، أو خط أنابيب CI/CD، أو مثبّت حزم) باستخراج أرشيف tar يتحكم فيه المهاجم باستخدام المعامل الآمن المفترض filter="data"، يحقق هذا الاستغلال كتابة ملفات تعسفية كاملة بصلاحيات ذلك المستخدم المميز — وعادةً ما يؤدي إلى تصعيد الصلاحيات إلى الجذر.
السبب الجذري هو سلوك غريب في os.path.realpath(): إذ يتوقف بصمت عن حل الروابط الرمزية بمجرد أن يتجاوز المسار المُوسَّع بالكامل PATH_MAX (4096 بايت على لينكس، و1024 على macOS). يعتمد مرشح tarfile على realpath() للتحقق الأمني، لكن النواة تحل الروابط الرمزية بشكل مستقل أثناء الاستخراج — مما يخلق فجوة TOCTOU (وقت الفحص مقابل وقت الاستخدام) تتيح الهروب من الدليل.
| الحقل | القيمة |
|---|---|
| معرّفات CVE | CVE-2025-4138، CVE-2025-4517 |
| CVSS v3.1 | 9.4 (حرجة) — AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:H/A:L |
| CWE | CWE-22 — تقييد غير سليم لاسم المسار داخل دليل مقيد |
| نوع الثغرة | تجاوز المسار عبر الروابط الرمزية / تجاوز المرشح |
| التأثير | كتابة ملفات تعسفية → تصعيد الصلاحيات، الهروب من صندوق الحماية، العبث بالبيانات |
| متجه الهجوم | تسليم أرشيف tar خبيث إلى أي تطبيق يستخدم tarfile.extractall() مع المرشحات |
| الإصدارات المتأثرة | Python 3.12.0 – 3.12.10, 3.13.0 – 3.13.3 |
| تم الإصلاح في | Python 3.9.23, 3.10.18, 3.11.13, 3.12.11, 3.13.4 |
| التصحيح | CPython PR #135037 |
| الإشعار الأمني | GHSA-hgqp-3mmf-7h8f |
| المُبلِّغ | Caleb Brown — Google Security Research |
┌───────────────────────────────────────────┐
│ Malicious Tar Structure │
└───────────────────────────────────────────┘
Stage 1 ── Build symlink chain that inflates the resolved path past PATH_MAX
ddd...ddd/ (directory, 247 chars)
a → ddd...ddd (symlink, 1 char name → 247 char dir)
ddd...ddd/ddd...ddd/ (nested directory)
b → ddd...ddd (symlink)
... ×16 levels
Short path (symlinks): a/b/c/d/e/f/g/h/i/j/k/l/m/n/o/p ~31 chars
Resolved path (dirs): ddd…/ddd…/ddd…/ddd…/ddd…/ddd…/… ~3968 chars
↑ nearing PATH_MAX
Stage 2 ── Final symlink exceeds PATH_MAX → realpath() stops resolving
a/b/c/…/p/lll…lll → ../../../../../../../../../../../../../../../../..
(16 levels of ".." — traverses back to extraction root)
┌─────────────────────────────────────────────────────────────────┐
│ os.path.realpath() CANNOT expand this → filter says "OK" ✓ │
│ Linux kernel DOES follow chain → actually escapes ✗ │
└─────────────────────────────────────────────────────────────────┘
Stage 3 ── Escape symlink resolves to arbitrary filesystem path
escape → <overflow_link>/../../../../../../../root
Stage 4 ── Create intermediate directories through the escape
escape/.ssh/ (directory, mode 0700 — created by tar extraction)
Stage 5 ── Write payload through the escaped symlink
escape/.ssh/authorized_keys → writes to /root/.ssh/authorized_keys 🔓
---
## الإصدارات المتأثرة
| فرع Python | النطاق المعرض للخطر | الإصدار المُصحَّح | الحالة |
|:--|:--|:--|:--|
| 3.13 | 3.13.0 – 3.13.3 | **3.13.4** | ✅ تم التصحيح |
| 3.12 | 3.12.0 – 3.12.10 | **3.12.11** | ✅ تم التصحيح |
| 3.11 | 3.11.4 – 3.11.12 | **3.11.13** | ✅ تم التصحيح |
| 3.10 | 3.10.12 – 3.10.17 | **3.10.18** | ✅ تم التصحيح |
| 3.9 | 3.9.17 – 3.9.22 | **3.9.23** | ✅ تم التصحيح |
| 3.8 | 3.8.17 – 3.8.20 | — | ❌ نهاية الدعم |
| 3.14+ | تم تغيير الفلتر الافتراضي إلى `"data"` | تحقق من الأحدث | ⚠️ تعرض أعلى |
> **ملاحظة:** غيّرت Python 3.14+ المعامل الافتراضي `filter` من عدم التصفية إلى `"data"`، مما يعني أن التطبيقات التي لم يكن لديها فلتر سابقًا (وبالتالي كانت غير آمنة بالفعل) تستخدم الآن الفلتر المعرض للخطر افتراضيًا.
---
## نمط الكود المتأثر
أي تطبيق يفعل ذلك على إصدار Python معرض للخطر يكون قابلاً للاستغلال:```python
import tarfile
# VULNERABLE — filter="data" can be bypassed
with tarfile.open("untrusted_archive.tar", "r") as tar:
tar.extractall(path="/some/directory", filter="data")
# ALSO VULNERABLE — filter="tar" has the same flaw
with tarfile.open("untrusted_archive.tar", "r") as tar:
tar.extractall(path="/some/directory", filter="tar")
حالات شائعة في العالم الحقيقي:
.targit clone https://github.com/DesertDemons/CVE-2025-4138-4517-POC.git cd CVE-2025-4138-4517-POC