Relevant – CTF Walkthrough

It’s been a while since I’ve done a CTF from VulnHub. Life has been busy but I’ve also been doing a few boxes on Offensive Security Proving Grounds. I’ve cancelled my subscription for now though as I still think it needs a bit of work unfortunately before it fully competes with Hack The Box or Try Hack Me, but I’ll give it another go in the future.

Anyway, here is my experience rooting the Relevant CTF.

Initial Enumeration

I started off with an NMAP scan. This identified two open ports.

nmap -p-

The open ports were SSH (22), and HTTP (80).

I figured the web server running on port 80 was probably more interesting than the SSH server, so I loaded up my browser and visited the main page.

Main page for Relevant CTF

This showed a database connection error with a message indicating that the website had been compromised already. As you can see, there were three links. I visited the first link, and was directed to a YouTube video which was a cover for “Never Gonna Give You Up” by Rick Astley. Two minutes into the CTF challenge and I’ve already been Rickrolled. Thanks for that.

Never Gonna Give you Up – By Rick Astley (a cappella cover)

After being rickrolled, I visited the second link which appeared to be a dump of credentials on PasteBin.

I saved the users from this dump into /tmp/users.txt, and the passwords in /tmp/passwd.txt. Given we know the SSH service is running on port 22, I decided to use Hydra to brute force SSH.

hydra -l /tmp/users.txt -p /tmp/passwd.txt ssh://

Unfortunately, this returned no valid SSH credentials.

I moved onto the final link, which appeared to be a QR code.

I found a website where you can decode QR codes. This showed that the raw text contained within the QR code started with “otpauth://”.

I recognised this prefix – this is a One Time Passcode key used to generate one time passcodes. You can therefore scan this QR code using an app such as Google Authenticator which will then generate a one time passcode every 30 seconds or so. I didn’t know what I needed this OTP key for, but kept it for reference in case I needed it further into the CTF challenge.


As mentioned in one of my previous CTF walk-through articles, I’ve created my own script (called OTT) which rather simply runs GoBuster commands against a set of wordlists. This saves me time writing out the GoBuster commands and checks against various wordlists saving a lot of time. To run the command below, you will need to download the OTT script or instead use GoBuster.

ott 50

This revealed a few directories such as wp-admin and wp-content revealing this is probably a WordPress website. We know we can use a tool called WPScan to check for vulnerable WordPress versions, plugins, and themes. I suspected that I may run into challenges with this given the front page of the website is showing a database connection error.

wpscan --url -e at,ap,u --api-token REDACTED

I ran this command which immediately halted the scan as it did not detect a WordPress installation. I suspected this may be due to the database being offline or the modified front page.

A quick check of the man pages for wpscan (man wpscan) identified you can use the –force option to skip wpscan checking whether we are targeting a WordPress website or not. I appended this option to the command, and the scan started running. It identified the WordPress version quite quickly and started scanning for themes and plugins, but it did not find anything vulnerable.

I noticed that WPScan only appeared to be using ‘passive methods’ for plugin enumeration, even though ‘themes’ were being scanned aggressively. I looked at the man pages for WPScan again and worked out that you can add an option to your command to scan plugins aggressively (–plugins-detection aggressive). I added this option to my command and ran it again; after letting the scan run for some time, it identified a vulnerable plugin called File Manager. The vulnerability type was Remote Code Execution.

I recall seeing about this vulnerability in recent news. It’s a very popular WordPress plugin so it gained a lot of attention when this vulnerability was identified.

I visited the first URL WPScan suggested for information.

This page had a proof of concept script. It appears the script uploads a file called ‘payload.php’ to the web server. I therefore downloaded the Pentestmonkey PHP Reverse Shell and renamed it to payload.php to upload my own payload.


I executed the script which uploaded the file successfully.

The file path it gave me though returned a 404 error, so I had to do a bit of digging to find out where the files were actually saved. The correct path is /wp-content/plugins/wp-file-manager/lib/files/payload.php.

I span up my listener using Netcat, visited the payload.php file, and had a shell. As always, I spawned a tty shell using python.

python3.8 -c 'import pty; pty.spawn("/bin/bash")'

We have a shell

Now I had a shell, I needed to work out how to escalate my privileges. Sometimes, you can escalate directly to root. Other times, you have to pivot to another user on the system before being able to get root access.

At this stage, I didn’t know what I would need to escalate my privileges. I started to run a few basic checks.

  • Sudo commands (sudo -l) – this revealed no commands I could run
  • SUID Checks (find / -perm -u=s -type f 2>/dev/null) – this revealed no interesting binaries with the SUID bit set.
  • Loading pspy on the system to check if any cron jobs were running

These checks revealed nothing of use, so I proceeded to check /etc/passwd and review the /home directory to see what users were on the system.

The home directory showed a few user folders. I had a look around each one. I observed two things:

  • The ‘patsy’ user had a file called .google_authenticator – this was interesting given we already had a key for generating OTP’s. Perhaps we need to pivot to this user?
  • The ‘relevant’ user had a file called .sudo_as_admin_successful – could this indicate this user has sudo access? Perhaps this was the user we needed to pivot to?

I needed to find a way to pivot to one of these users. I decided to check for files the three users owned to see if that could assist me with the pivoting.

find / -user h4x0r

This identified something of interest which I didn’t spot when enumerating this user folder initially.

Can you spot it? The user has a file called ‘note.txt’ which seems to be well hidden.

I output the file using my shell, which showed a user and password hash. I didn’t spot the ‘news’ user in my initial checks but I double checked the passwd file on the system and confirmed the user ‘news’ did indeed exist.

I input the hash into which revealed the password. I was then able to switch user using the su command and the newly unhashed password.

Getting root

Once I successfully switched to the ‘news’ user, I decided to run a few standard checks again. I started by checking which commands the user could run using sudo (sudo -l).

This revealed /usr/bin/node.

I’ve not seen this binary before, but a quick check of GTFOBins revealed I could run a simple command to escalate to root.

sudo node -e 'require("child_process").spawn("/bin/sh", {stdio: [0, 1, 2]});'

I ran this command, which elevated my permissions straight to the root user.

I enjoyed this CTF – I didn’t find it too challenging but it was fun to see a few different things I haven’t come across before. Thanks to @iamv1nc3nt for the CTF!

By the way, I didn’t end up using the One Time Passcode, which makes me wonder if this box has alternative routes? Who knows…

Phishing Scambaiting Security

My small war with scammers

Since the World Health Organisation declared COVID19 a pandemic on the 30th January 2020, there appears to have been a large increase of scams. People have invested a lot more time sat at their computer screens since communities across the world entered lockdown.

Never have we been more attractive to scammers.

A big part of the problem is Phishing websites. For those of you who have not yet seen, I recently wrote an article about how a lot of scammers appear to use a company called Namecheap to host and register their illegal websites. My article focused on how slow Namecheap are to take down these websites. I also tweeted the Namecheap CEO about this issue, but I received no response.

There will inevitably be a handful of users who fall for these scams, especially when the websites and domain names are hosted with companies who take days to take them offline. This got me wondering; what can we do to help (aside from reporting the websites and waiting for the hosting company to take them offline)?

Messing with Phishers

If you ask me, the best way to annoy a scammer is to waste their time. How can we do this? In the case of Phishing websites, why don’t we fill out their web form which they have solicited us to fill out? Though, we will fill it out several thousand times with random data. The idea being that when they go to review the form data, there will be so much fake data in their database that they won’t be able to distinguish the fake data from the legitimate data provided by victims.

Is this legal? I don’t know. Is it the right thing to do? Definitely.


  • Phishing websites often use shared web hosting services. It is therefore very important not to stress the server and cause a Denial of Service attack. Otherwise, this will more than certainly put you on the wrong side of the law, and inconvenience legitimate users of the hosting service.
  • The fake data generated will need to look real so the scammers find it impossible (or at least very difficult and time consuming) to identify.
  • The data will need to be submitted from a number of different IP addresses to further reduce the risks of scammers differentiating the data.

Python Script 1 – Form Scraper

Before we start, I need to mention that my Python programming skills are not at all good. Please do not judge me by my terrible code. 🙂

I decided to split my code into two parts. The first script would be responsible for identifying HTML forms on a URL. I would simply need to specify the Phishing website as a parameter to my script. The form information would then be saved to a configuration file which would contain a list of all input fields detected on the HTML form. There will usually be a variety of fields such as usernames, passwords, e-mail addresses, names etc.

Usage: ./ "" "/path-to-phishing-page"
# I am not responsible for what you do with this script. Run at your own risk.
import sys
from bs4 import BeautifulSoup
from requests_html import HTMLSession
import time
import os

session = HTMLSession()

def get_form_details(form):
    details = {}
    action = form.attrs.get("action")
    method = form.attrs.get("method", "get").lower()
    inputs = []
    for input_tag in form.find_all("input"):
        input_type = input_tag.attrs.get("type", "text")
        input_name = input_tag.attrs.get("name")
        input_value =input_tag.attrs.get("value", "")
        inputs.append({"type": input_type, "name": input_name, "value": input_value})

    details["action"] = action
    details["method"] = method
    details["inputs"] = inputs
    return details

def get_all_forms(url):
    res = session.get(url)
    soup = BeautifulSoup(res.html.html, "html.parser")
    return soup.find_all("form")

url = sys.argv[1] + sys.argv[2]
baseurl = sys.argv[1]
forms = get_all_forms(url)

for i, form in enumerate(forms, start=1):
    form_details = get_form_details(form)
    print("="*50, f"form #{i}", "="*50)
    print("Do you want to use this form? Y/N\n\n\n\n\n\n")
    answer = input()
    if answer == 'Y':
        method = form.attrs.get("method", "get").lower()
        submit = form.attrs.get("action", "get").lower()
        if not os.path.exists(".fwp"):
        if url[:7] == "http://":
            http = True
            c = 7
        elif url[:8] == "https://":
            https = True
            c = 8
            print("Invalid URL. Please remember to include http:// or https://")
        pos = url.find("/",c + 1)
        folder = url[c:pos]
        print("Okay! Saving form to config file...")
        if not os.path.exists(".fwp/" + folder):
            os.mkdir(".fwp/" + folder)
        f = open(".fwp/" + folder + "/.config", "w")
        f.write(method + "," + baseurl + submit + "\n")
        for input_tag in form_details["inputs"]:
            f.write(input_tag["name"] + "," + input_tag["type"] + "\n")

We can test this script against this fake Halifax website:

As we can see here, the script has found a login form and asked us if this is the form we intend to target.

When we answer yes to this script, the following file is saved:


This is a list of all the input fields identified (with the POST destination URL at the top), followed by their input types. The next step is to modify this file slightly to specify the type of data that needs to be generated.


What we have essentially done here is add another value on the end of each line which determines the data which should be generated. For example, the first field called ‘ip’ should have a random IP address generated, so ‘ipaddress’ has been added to the end. The script is programmed to look out for certain data types such as ‘ipaddress’ so the correct data can be generated.

This is where the second script comes into play.

Python Script 2 – Data Generator and Form Submission

The second script is responsible for two things.

A) Reading the config file output from the first script, and generating the random data.
B) Submitting that data to the target in mass.

Usage: / config-file amount-of-times-to-hit-target
# I am not responsible for what you do with this script. Run at your own risk.
import sys
import requests
import random
from faker import Faker
import time
from ipaddress import IPv4Address
faker = Faker('en_GB')

postdata = {}

config = sys.argv[1]
config = open(config, "r")

i = 0

maxreq = int(sys.argv[2])

while i < maxreq:
    n = 1 
    ua = faker.user_agent()
    for lines in config:
        line = lines.split()
        if n == 1:
            if line[0].lower()[:5] != "post,":
                print("This form does not use POST and is currently not supported. Good bye.")
            url = line[0][-(len(line[0])-5):]
            expl = line[0].split(",")
            consideration = expl[1]
            datatype = expl[2]
            name = expl[0]
            profile = faker.profile()
            if consideration == "fixed":
                # Fixed value
                val = datatype
                if datatype == "username":
                    val = profile["username"]
                elif datatype == "password":
                    passwords = open("/usr/share/wordlists/passwords/10-million-password-list-top-1000000.txt")
                    with passwords as f:
                        lines = [line.rstrip('\n') for line in f]
                        val = random.choice(lines)
                elif datatype == "useragent":
                    val = ua
                elif datatype == "blank":
                    val = ""
                elif datatype == "checked":
                    val = "1"
                elif datatype == "ipaddress":
                    val = str(IPv4Address(random.getrandbits(32)))
                    val = "Unknown"

            postdata.update({name: val})
        n += 1, 0)
    i += 1
    headers = {"User-Agent":ua}
    response =, data = postdata, headers = headers)

Without drawing attention to the terrible quality of my Python code, we can see that this script loads the config file generated by the first script, followed by the amount of times it should submit data to the target.

The script utilised the Python library Faker, which is a really handy library that generates all sorts of fake data. Currently, the script only supports the generation of data for user agents, IP addresses, usernames, and passwords, though, these can very easily be extended based on the target. I suppose you could even modify the script to auto-identify the data required for some of the fields.

Eventually, once the script covers a number of common data items, it can quickly be used to target a number of different Phishing websites within minutes. When used against a fake Halifax website, we can see it generating the data required:

Before you know it, the scammers database will have thousands of entries hopefully rendering their data completely useless. To mitigate against scammers filtering requests by source IP address, look into using this script with Proxychains.

This was just my (very small) way of annoying a scammer. Hopefully I can make this script more powerful in the weeks to come to start targeting these scammers in mass.