
CVE-2025-4138 / CVE-2025-4517 — Python tarfile PATH_MAX सिमलिंक फ़िल्टर बाइपास
filter="data" / filter="tar" निष्कर्षण के माध्यम से मनमानी फ़ाइल लेखन
Python के tarfile मॉड्यूल में एक गंभीर भेद्यता हमलावर को निष्कर्षण फ़िल्टर ("data" और "tar") को बायपास करने और इच्छित निष्कर्षण निर्देशिका के बाहर मनमानी फ़ाइलें लिखने की अनुमति देती है। जब कोई विशेषाधिकार प्राप्त प्रक्रिया (जैसे कि रूट-स्तरीय बैकअप स्क्रिप्ट, CI/CD पाइपलाइन, या पैकेज इंस्टॉलर) कथित रूप से सुरक्षित filter="data" पैरामीटर का उपयोग करके हमलावर-नियंत्रित tar संग्रह निकालती है, तो यह शोषण उस विशेषाधिकार प्राप्त उपयोगकर्ता के रूप में पूर्ण मनमानी फ़ाइल लेखन प्राप्त करता है — आमतौर पर रूट तक विशेषाधिकार वृद्धि।
मूल कारण os.path.realpath() में एक व्यवहारिक विचित्रता है: जब पूर्ण रूप से विस्तारित पथ PATH_MAX (लिनक्स पर 4096 बाइट्स, macOS पर 1024) से अधिक हो जाता है, तो यह चुपचाप सिमलिंक का समाधान करना बंद कर देता है। 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 — प्रतिबंधित निर्देशिका में पथनाम की अनुचित सीमा |
| भेद्यता प्रकार | सिमलिंक के माध्यम से पथ ट्रैवर्सल / फ़िल्टर बाईपास |
| प्रभाव | मनमानी फ़ाइल लेखन → विशेषाधिकार वृद्धि, सैंडबॉक्स एस्केप, डेटा छेड़छाड़ |
| हमला वेक्टर | फ़िल्टर के साथ tarfile.extractall() का उपयोग करने वाले किसी भी एप्लिकेशन को दुर्भावनापूर्ण tar संग्रह वितरित करें |
| प्रभावित | 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")