Welcome back to my walkthrough for OverTheWire’s Bandit wargame. This is the fifth post in the series and will be covering levels 20 through 24. If you’ve not already completed the previous levels, I recommend that you go back to the beginning of this blog series and follow the walkthrough from the start, as this will enable you to get up to speed with the command-line to be able to feel confident in tackling these next five levels.
As you’ll remember, in the last post, we learnt how TCP works, effectively covering the basics of secure networking as we now understand both TLS and TCP. We completed level 19, our first time using setuid binaries, and level 20 will see us continuing this one step further before moving on to cron
jobs.
Let’s begin.
Level 20 - Level 21
In this level, there is a setuid binary in the home directory that connects to localhost on a specified port, reads a line of text from the connection, and compares it to the password of the previous level. If the password is correct, it transmits the password to the next level. This requires us to create our own service listening on a port. We then tell the setuid binary to connect to this port, where we will provide the password.
First, let’s list the contents of the home directory to see the setuid binary:
$ ls -l
total 16
-rwsr-x--- 1 bandit21 bandit20 15604 Jun 20 04:07 suconnect
You’ll recall from the previous level that the setuid permission allows a file to be executed with the privileges of the file owner rather than the user running the file. In this case, suconnect is owned by bandit21, so when you run it, it executes with the permissions of bandit21.
We’ll use nc
(netcat
) to set up a local server. You’ll remember in level 14 - 15, we used netcat
to connect to a service, but this time, we’ll use it to create our own listening service, which we’ll need to bind to a port. You can choose any port number for this up to the maximum possible port number, 65535. Note that port numbers below 1024 are protected and require root access to listen on. We’ll use a higher port number for this task to avoid needing root privileges. I’ve chosen 12345.
As we’ll need to run two commands simultaneously (netcat
and the setuid binary), we will use job control to manage the processes within a single terminal. Job control allows you to manage multiple processes (jobs) from a single terminal session.
Let’s start a local server using netcat
on our chosen port, 12345. We’ll pipe the current password in using the /etc/bandit_pass directory as the setuid binary is expecting it.
$ cat /etc/bandit_pass/bandit20 | nc -lp 12345 &
Here, -l tells netcat
to listen for an incoming connection and -p specifies the port. The & on the end of the command tells the shell to run the process in the background, allowing it to continue listening while we’re free to run the setuid binary, specifying our port as the first argument:
$ ./suconnect 12345
Connection received on localhost 46212
Read: <current_password>
Password matches, sending next password
<password>
Level 21 - Level 22
In this level, we need to discover and understand a program running automatically at regular intervals from cron
. By inspecting /etc/cron.d/, we should be able to find the configuration, find out what command is being executed and, from that, determine the password.
cron
is a UNIX time-based job scheduler that allows tasks to be automatically run at regular intervals. The name “cron” comes from the ancient Greek personification of time, “Chronos”. cron
jobs are typically used for system maintenance, backups and other repetitive administrative tasks.
Based on the level description, let’s look in /etc/cron.d/, which is a directory used by cron
to store “crontab” snippets. Crontabs (cron
tables) are configuration files that cron
reads to determine which command to run and at what interval. Let’s list the contents of that directory by passing the name of the directory to ls
:
$ ls -l /etc/cron.d/
total 24
-rw-r--r-- 1 root root 120 Jun 20 04:07 cronjob_bandit22
-rw-r--r-- 1 root root 122 Jun 20 04:07 cronjob_bandit23
-rw-r--r-- 1 root root 120 Jun 20 04:07 cronjob_bandit24
-rw-r--r-- 1 root root 201 Apr 8 14:38 e2scrub_all
-rwx------ 1 root root 52 Jun 20 04:08 otw-tmp-dir
-rw-r--r-- 1 root root 396 Jan 9 20:31 sysstat
The most relevant file for us here is “cronjob_bandit22”. Let’s have a look at it’s contents:
$ cat /etc/cron.d/cronjob_bandit22
@reboot bandit22 /usr/bin/cronjob_bandit22.sh &> /dev/null
* * * * * bandit22 /usr/bin/cronjob_bandit22.sh &> /dev/null
We can see that this cron
job file has two entries, one on each line. The first line instructs cron
to run the script /usr/bin/cronjob_bandit22.sh as the user bandit22 each time the system boots. The &> /dev/null
part redirects standard output (stdout) and standard error (stderr) to /dev/null, effectively silencing any output. The second line is almost identical, but instead of starting with @reboot
, it begins with * * * * *
. These five asterisks represent the minute, hour, day of the month, month, and day of the week, respectively. When all five are *
like this, it means the command should be run every minute.
Next, let’s look at the contents of the script, /usr/bin/cronjob_bandit22.sh to see what it does:
$ cat /usr/bin/cronjob_bandit22.sh
#!/bin/bash
chmod 644 /tmp/t7O6lds9S0RqQh9aMcz6ShpAoZKF7fgv
cat /etc/bandit_pass/bandit22 > /tmp/t7O6lds9S0RqQh9aMcz6ShpAoZKF7fgv
Let’s break down this script line by line:
#!/bin/bash
: This line is known as a “shebang”. It specifies which interpreter should be used to execute the script. In this case, thebash
shell.chmod 644 /tmp/t7O6lds9S0RqQh9aMcz6ShpAoZKF7fgv
: This is changing the permissions of the file /tmp/t7O6lds9S0RqQh9aMcz6ShpAoZKF7fgv to octal 644, meaning the owner can read and write the file, group members and others can read it.cat /etc/bandit_pass/bandit22 > /tmp/t7O6lds9S0RqQh9aMcz6ShpAoZKF7fgv
: This is usingcat
to read the contents of the file /etc/bandit_pass/bandit22 (which contains the password for the next level) and writes it to /tmp/t7O6lds9S0RqQh9aMcz6ShpAoZKF7fgv.
Finally, now we know the file, /tmp/t7O6lds9S0RqQh9aMcz6ShpAoZKF7fgv will contain the password for the next level and we have permission to read it; let’s retrieve its contents:
$ cat /tmp/t7O6lds9S0RqQh9aMcz6ShpAoZKF7fgv
<password>
Level 22 - Level 23
This level is very similar to the last one. A program is running automatically at regular intervals from cron
. Our task is to look in /etc/cron.d/ for the configuration, see what command is being executed and use it to find the password.
Let’s start by listing the contents of /etc/cron.d/ again:
$ ls -l /etc/cron.d/
total 24
-rw-r--r-- 1 root root 120 Jun 20 04:07 cronjob_bandit22
-rw-r--r-- 1 root root 122 Jun 20 04:07 cronjob_bandit23
-rw-r--r-- 1 root root 120 Jun 20 04:07 cronjob_bandit24
-rw-r--r-- 1 root root 201 Apr 8 14:38 e2scrub_all
-rwx------ 1 root root 52 Jun 20 04:08 otw-tmp-dir
-rw-r--r-- 1 root root 396 Jan 9 20:31 sysstat
This time, the relevant file for us is “cronjob_bandit23”. Let’s look at its contents:
$ cat /etc/cron.d/cronjob_bandit23
@reboot bandit23 /usr/bin/cronjob_bandit23.sh &> /dev/null
* * * * * bandit23 /usr/bin/cronjob_bandit23.sh &> /dev/null
This is a very similar cron job file to the last level. Let’s have a look at the script file, /usr/bin/cronjob_bandit23.sh and see what it does:
$ cat /usr/bin/cronjob_bandit23.sh
#!/bin/bash
myname=$(whoami)
mytarget=$(echo I am user $myname | md5sum | cut -d ' ' -f 1)
echo "Copying passwordfile /etc/bandit_pass/$myname to /tmp/$mytarget"
cat /etc/bandit_pass/$myname > /tmp/$mytarget
We can see that this is a much more complicated script than last time. Let’s break this one down line-by-line again:
#!/bin/bash
: Here, we have a shebang again, telling us the interpreter is thebash
shell.myname=$(whoami)
: This is the first time we’ve seen a variable in this series. Variables store values, with the name of the variable on the left side of the “=” and the value on the right. Here, the value is the output of thewhoami
command, which prints the username of the user who executed it. You may think that this would be “bandit22” as that’s who we’re logged in as, but remember that this script is run bycron
, and thecron
job specified that this command should be run as bandit23, hencemyname
will equal “bandit23”.mytarget=$(echo I am user $myname | md5sum | cut -d ' ' -f 1)
: Here, we have another variable, but this time the value is a long, piped command. The command begins by printing “I am user”, with the value stored inmyname
appended; therefore, “I am user bandit23”. This gets piped intomd5sum
, a command which calculates and prints the MD5 hash of its input. This finally gets piped into acut
command, removing unwanted output frommd5sum
, leaving us only with the hash itself.echo "Copying passwordfile /etc/bandit_pass/$myname to /tmp/$mytarget"
: This simply prints a message to the terminal, telling us where the file is being copied to. This is the debug message referenced in the level description and is useful to help you understand how this script works if you’d like to execute the script manually.cat /etc/bandit_pass/$myname > /tmp/$mytarget
: This is very similar to the last line of the script in the previous level, except this time, using variables. This means it will not always be bandit23’s password copied when this script is run; it will depend on who is running it.
Let’s read the file’s contents in /tmp/ to get the password for the next level. We need to compute the target filename using the same logic as in the script:
$ echo I am user bandit23 | md5sum | cut -d ' ' -f 1
8ca319486bfbbc3663ea0fbe81326349
This will be the name of the file in /tmp/. Let’s retrieve the contents of that file to get the password for the next level:
cat /tmp/8ca319486bfbbc3663ea0fbe81326349
<password>
Level 23 - Level 24
In this level, we need to take a big step by creating our own shell script to exploit a cron
job running at regular intervals. This is a crucial skill, and successfully completing this level is a big step!
First, let’s list the contents of /etc/cron.d/ again:
$ ls -l /etc/cron.d/
total 24
-rw-r--r-- 1 root root 120 Jun 20 04:07 cronjob_bandit22
-rw-r--r-- 1 root root 122 Jun 20 04:07 cronjob_bandit23
-rw-r--r-- 1 root root 120 Jun 20 04:07 cronjob_bandit24
-rw-r--r-- 1 root root 201 Apr 8 14:38 e2scrub_all
-rwx------ 1 root root 52 Jun 20 04:08 otw-tmp-dir
-rw-r--r-- 1 root root 396 Jan 9 20:31 sysstat
This time, the relevant file for us is “cronjob_bandit24”. Let’s look at its contents:
$ cat /etc/cron.d/cronjob_bandit24
@reboot bandit24 /usr/bin/cronjob_bandit24.sh &> /dev/null
* * * * * bandit24 /usr/bin/cronjob_bandit24.sh &> /dev/null
Once again, we see a similar cron
job file. Let’s cat
/usr/bin/cronjob_bandit24.sh to see what this script does:
$ cat /usr/bin/cronjob_bandit24.sh
#!/bin/bash
myname=$(whoami)
cd /var/spool/$myname/foo
echo "Executing and deleting all scripts in /var/spool/$myname/foo:"
for i in * .*;
do
if [ "$i" != "." -a "$i" != ".." ];
then
echo "Handling $i"
owner="$(stat --format "%U" ./$i)"
if [ "${owner}" = "bandit23" ]; then
timeout -s 9 60 ./$i
fi
rm -f ./$i
fi
done
This script is a bit complicated to break down line by line, so let’s review what it generally does.
The script starts by determining the current user’s username (bandit24 when run via the cron
job) and then navigates to a specific directory based on the username, for example, /var/spool/bandit24/foo. It then loops through all files in the directory, including hidden ones, and for each file, it checks if the file owner is bandit23. If the owner is bandit23, the script executes that file with a timeout of 60 seconds (if the owner is not bandit23, it does nothing). Finally, it deletes each file after attempting to execute it.
Let’s check the permissions of the /var/spool/bandit24/foo directory:
$ ls -ld /var/spool/bandit24/foo
drwxrwx-wx 12 root bandit24 4096 Jun 29 20:52 /var/spool/bandit24/foo
Since the owner is root and the group is bandit24, we fall under “others”. This means we do not have permission to read (list its contents), but we can write to it (create a file). As the cron
job script executes all files in /var/spool/bandit24/foo and we have write permission to that directory, we can create our own script in that directory, which reads the password for bandit24 and places it in a location we can access.
Choose your favourite text editor (I’d recommend nano
for beginners) and write the following script to a file in /tmp. Feel free to name it anything you like. We’re using the /tmp directory for now so that the cron
job script does not delete our file before we finish preparing it.
#!/bin/bash
cat /etc/bandit_pass/bandit24 > /tmp/bandit24_password
That’s it. It’s a very simple script that copies the content of the /etc/bandit_pass/bandit24 file (which we know contains bandit24’s password) into /tmp/bandit24_password, where we can read it. We know we’ll have permission to read /etc/bandit_pass/bandit24 as the cron
job runs as bandit24.
Next, let’s add the execute permission to our script so that the cron
job script can execute it. We can use chmod
for this. We have only ever used octal permissions with chmod
so far, but it also allows you to add or remove specific permissions using the “string” syntax. For example, to add the execute permission to our script, we can do the following:
$ chmod +x /tmp/my_script.sh
Of course, make sure you replace “my_script.sh” with the name you chose for your script.
Finally, let’s move the prepared script to the /var/spool/bandit24/foo directory so that the cron
job script can see it:
$ mv /tmp/my_script.sh /var/spool/bandit24/foo
Now that everything is in place let’s wait for the cron
job to run (remember that it runs every minute, so you won’t have long to wait). After a minute, check the /tmp directory for the password file:
$ cat /tmp/bandit24_password
<password>
Level 24 - Level 25
In this level, a daemon is listening on port 30002 and will give you the password for bandit25 if given the password for bandit24 and a secret numeric 4-digit pincode. There is no way to retrieve the pincode except by going through all 10,000 combinations, known as brute-forcing.
Before starting any brute-forcing, let’s test the connection to the daemon and see what happens when we send a single pincode.
First, let’s store the password for the current level in a variable so that we can easily reuse it later:
$ current_password=$(cat /etc/bandit_pass/bandit24)
Now, let’s send the password and the pincode 0000 to the daemon and see its response. We’ll use netcat
to talk to the daemon as we’ve done before:
$ echo "$current_password 0000" | nc localhost 30002
I am the pincode checker for user bandit25. Please enter the password for user bandit24 and the secret pincode on a single line, separated by a space.
Wrong! Please enter the correct current password and pincode. Try again.
You’ll have to kill the connection manually with Ctrl-C, as the daemon keeps the connection open if the password/pincode combination is incorrect.
We need to send the bandit24 password and all possible 4-digit pincodes to the daemon. Since there are 10,000 possible combinations (0000 to 9999), we need to try each combination until we find the correct pincode. We will use a single connection to the daemon to send all possible pincode combinations, as suggested by the level description. We can do this using brace expansion {0000..9999}
to generate the pincodes and feed them to netcat
using a bash
loop:
for pincode in {0000..9999}; do
echo "$current_password $pincode"
done | nc localhost 30002
Let’s make sure we understand what each line of this command is doing.
for pincode in {0000..9999}; do
: This sets up a loop iterating over all 4-digit combinations from 0000 to 9999, storing each pincode in a variable named “pincode”.echo "$current_password $pincode"
: For each iteration, this echoes the bandit24 password that we stored earlier, followed by the current pincode.done | nc localhost 30002
: This ends the loop. The loop output is piped tonc
, which maintains the connection to the daemon on port 30002.
Let’s run this command now, formatting it on a single line for ease. The correct pincode will trigger the daemon to respond with the password for bandit25 and terminate the connection:
$ for pincode in {0000..9999}; do echo $current_password $pincode; done | nc localhost 30002
...
Wrong! Please enter the correct current password and pincode. Try again.
Wrong! Please enter the correct current password and pincode. Try again.
Wrong! Please enter the correct current password and pincode. Try again.
Wrong! Please enter the correct current password and pincode. Try again.
Correct!
The password of user bandit25 is <password>
Thank you once again for taking the time to read this post and follow along as we walkthrough the Bandit wargame. By now you should be feeling much more confident in your command-line skills and have a broader knowledge of command-line operations. If you don’t, don’t worry; you can always re-complete the levels and/or re-read the previous posts until you become more familiar with the tools we’ve used so far.
In the next post, we’ll cover the following five levels - 25 through 29, marking the penultimate blog post of the series. See you there!