The aim of this walkthrough is to provide help with the Bandit wargame on the OverTheWire website. Please note that no flags or passwords are directly provided here. Only one of the many ways to solve the challenges.

The wargame is available under Bandit wargame.

Note: Throughout this tutorial/walkthrough the words password and flag are used interchangeably.

Quick intro and start config

Basic idea: finish level x to get level x+1. The general connection via ssh can look like this, where x stands for the current level:

ssh banditx@bandit.labs.overthewire.org -p 2220
SSH connection
Hostbandit.labs.overthewire.org
Port2220

Level 0

Level Goal: The goal of this level is for you to log into the game using SSH. The host to which you need to connect is bandit.labs.overthewire.org, on port 2220. The username is bandit0 and the password is bandit0. Once logged in, go to the Level 1 page to find out how to beat Level 1.

Solution: After we log in with the provided password, we are greeted with a welcome message providing us with the general game rules and other tips.

# connect to the lab
ssh bandit0@bandit.labs.overthewire.org -p 2220

Level 00 -> Level 01

Level Goal: The password for the next level is stored in a file called readme located in the home directory. Use this password to log into bandit1 using SSH. Whenever you find a password for a level, use SSH (on port 2220) to log into that level and continue the game.

Solution: Copy the flag and exit the shell. Use the flag as a password to log into the next level with the appropriate username, bandit1.

# print the file to std out
cat readme
...
# exit the shell
exit

Level 01 -> Level 02

Level Goal: The password for the next level is stored in a file called - located in the home directory.

Solution: Use the dot (.) to specify that the file is located in the current directory.

# print the file to std out
cat ./-

Level 02 -> Level 03

Level Goal: The password for the next level is stored in a file called spaces in this filename located in the home directory.

Solution: Surround the file name with " or in order to enable spaces.

# print the file to std out
cat "spaces in this filename"

Level 03 -> Level 04

Level Goal: The password for the next level is stored in a hidden file in the inhere directory.

Solution: List out the files in the current directory. Use the -a option to include hidden files.

# list the files in the current directory (hidden files included)
ls -hla
...
# change directory
cd inhere/
# list the files in the current directory (hidden files included)
ls -hla
...
# print contents of the hidden file to std out
cat .hidden
...
# copy the flag - flag format: (flag)[0-9a-zA-Z]{32}

Level 04 -> Level 05

Level Goal: The password for the next level is stored in the only human-readable file in the inhere directory. Tip: if your terminal is messed up, try the “reset” command.

Solution: From now on, we will use ll command instead of using ls -alF command as before. There is an alias already defined for it in the ~/.bashrc file.

# alias for ll is defined in ~/.bashrc
...
alias ll='ls -alF'
...

Again, here is the complete terminal interaction:

# list files
ll
...
# change current directory
cd inhere/
# list files
ll
...
# check the file type for ever file in the directory
file ./-file0*
...
# print the content of the only ASCII text file in the directory
cat ./-file07
# grab the flag
...

Level 05 -> Level 06

Level Goal: The password for the next level is stored in a file somewhere under the inhere directory and has all of the following properties:

  • human-readable
  • 1033 bytes in size
  • not executable

Solution: We use the find command to restrict our search.

# only searching in the current directory
find . -size 1033c -type f \! -executable

Level 06 -> Level 07

Level Goal: The password for the next level is stored somewhere on the server and has all of the following properties:

  • owned by user bandit7
  • owned by group bandit6
  • 33 bytes in size

Solution: Just like before, we use the find command to restrict our search. Additionally, we discard the error messages by redirecting them to /dev/null.

# searching the entire file system
find / -user bandit7 -group bandit6 -size 33c 2>/dev/null

Level 07 -> Level 08

Level Goal: The password for the next level is stored in the file data.txt next to the word millionth.

Solution: Use the grep command to search the file.

# Use pipes to redirect outputs to program inputs.
cat data.txt | grep millionth

Level 08 -> Level 09

Level Goal: The password for the next level is stored in the file data.txt and is the only line of text that occurs only once.

Solution: Here we first sort the output and then omit the duplicates with the uniq command.

# Use pipes to chain multiple commands one after the other.
cat data.txt | sort | uniq -u

Level 09 -> Level 10

Level Goal: The password for the next level is stored in the file data.txt in one of the few human-readable strings, preceded by several ‘=’ characters.

Solution: First use the strings command to make the output ASCII readable, then search the output with grep.

# Use strings to print the printable chars in the file.
strings data.txt | grep -E '===+'

Level 10 -> Level 11

Level Goal: The password for the next level is stored in the file data.txt, which contains base64 encoded data.

Solution: Decrypt the output with base64.

# Use -d to decrypt data.
cat data.txt | base64 -d

Level 11 -> Level 12

Level Goal: The password for the next level is stored in the file data.txt, where all lowercase (a-z) and uppercase (A-Z) letters have been rotated by 13 positions.

Solution: Use the tr command to make the shift.

cat data.txt | tr 'A-Za-z' 'N-ZA-Mn-za-m'

Level 12 -> Level 13

Level Goal: The password for the next level is stored in the file data.txt, which is a hexdump of a file that has been repeatedly compressed. For this level it may be useful to create a directory under /tmp in which you can work using mkdir. For example: mkdir /tmp/myname123. Then copy the datafile using cp, and rename it using mv (read the manpages!).

Solution: First use the xxd command to reverse the hexdump effect. Then use the file command to find out the output file type in order to determine the correct compression function.

xxd -r data.txt | gzip --decompress | bzip2 --decompress | gzip --decompress | tar xvO | tar xvO | bzip2 --decompress | tar xvO | gzip -d | cat

Level 13 -> Level 14

Level Goal: The password for the next level is stored in /etc/bandit_pass/bandit14 and can only be read by user bandit14. For this level, you don’t get the next password, but you get a private SSH key that can be used to log into the next level. Note: localhost is a hostname that refers to the machine you are working on.

Solution: Copy the ssh key to your local machine.

# copy the private key to your machine
cat sshkey.private
...
# exit the ssh connection
exit

Change the access rights for the newly created file.

# on local machine - change access rights for the file
chmod 0400 <sshkey.private>
...
# use the key to authenticate for the ssh service
ssh -i sshkey.private bandit14@bandit.labs.overthewire.org -p 2220
...
# on target machine - grab the flag
cat /etc/bandit_pass/bandit14

Level 14 -> Level 15

Level Goal: The password for the next level can be retrieved by submitting the password of the current level to port 30000 on localhost.

Solution: Use netcat to connect to the given port/service.

nc -vn 127.0.0.1 30000
# or
nc -v localhost 30000
...
# paste prev flag to get the new flag
...

Level 15 -> Level 16

Level Goal: *The password for the next level can be retrieved by submitting the password of the current level to port 30001 on localhost using SSL encryption.

Helpful note: Getting “HEARTBEATING” and “Read R BLOCK”? Use -ign_eof and read the “CONNECTED COMMANDS” section in the manpage. Next to ‘R’ and ‘Q’, the ‘B’ command also works in this version of that command…*

Solution: Use the openssl command to connect to the given ssl service.

openssl s_client --connect localhost:30001
...
# paste prev flag to get the new flag

Level 16 -> Level 17

Level Goal: The credentials for the next level can be retrieved by submitting the password of the current level to a port on localhost in the range 31000 to 32000. First find out which of these ports have a server listening on them. Then find out which of those speak SSL and which don’t. There is only 1 server that will give the next credentials, the others will simply send back to you whatever you send to it.

Solution: First, scan the port range with nmap.

nmap localhost -p31000-32000

Then check out the services, that run on the open ports.

nmap -n localhost -p31046,31518,31691,31790,31960 -sV --version-light

Finally, connect to the service that runs ssl and paste the previous flag.

openssl s_client -connect localhost:31790
...
# paste prev flag
# copy the private key
...
exit

Level 17 -> Level 18

Level Goal: There are 2 files in the homedirectory: passwords.old and passwords.new. The password for the next level is in passwords.new and is the only line that has been changed between passwords.old and passwords.new

NOTE: if you have solved this level and see ‘Byebye!’ when trying to log into bandit18, this is related to the next level, bandit19.

Solution: Change the permissions for the ssh private key on your local machine.

# on local machine
chmod 0400 <private-ssh-key-file>

Use the key to connect to level 17.

# on local machine
ssh -i <private-ssh-key-file> bandit17@bandit.labs.overthewire.org -p 2220

Compare the two files with the diff command.

# on local machine
diff passwords.old passwords.new

Level 18 -> Level 19

Level Goal: The password for the next level is stored in a file readme in the homedirectory. Unfortunately, someone has modified .bashrc to log you out when you log in with SSH.

Solution: Specify cat as a command to be executed right after the ssh log in to work around the automatic log off.

ssh bandit18@bandit.labs.overthewire.org -p 2220 cat ./readme

Level 19 -> Level 20

Level Goal: To gain access to the next level, you should use the setuid binary in the homedirectory. Execute it without arguments to find out how to use it. The password for this level can be found in the usual place (/etc/bandit_pass), after you have used the setuid binary.

Solution: Run the executable with the set setuid to read the password file as bandit20 instead of bandit19.

./bandit20-do -S "cat /etc/bandit_pass/bandit20"

Level 20 -> Level 21

Level Goal: There is a setuid binary in the homedirectory that does the following: it makes a connection to localhost on the port you specify as a commandline argument. It then reads a line of text from the connection and compares it to the password in the previous level (bandit20). If the password is correct, it will transmit the password for the next level (bandit21).

NOTE: Try connecting to your own network daemon to see if it works as you think.

Solution: First, we background a netcat listener at an arbitrary port. Then, we use the setuid binary to connect to the same port. Once the flag is sent, we will be provided with a new one.

echo "<previous-flag>" | nc -l localhost 62333 &
...
./suconnect 62333
...

Level 21 -> Level 22

Level Goal: A program is running automatically at regular intervals from cron, the time-based job scheduler. Look in /etc/cron.d/ for the configuration and see what command is being executed.

Solution: If you check out the running cron jobs, you will see, that cronjob_bandit22 runs a script every minute. Once you check out the script, you will be provided with the location of the flag/password.

# check out the cron job
cat /etc/cron.d/cronjob_bandit22
...
# check out the script that is run every minute
cat /usr/bin/cronjob_bandit22.sh
...
# get the flag - the file location is indicated in the script
# this is only an example of a temporary file that was generated for me
cat /tmp/t7O6lds9S0RqQh9aMcz6ShpAoZKF7fgv

Level 22 -> Level 23

Level Goal: A program is running automatically at regular intervals from cron, the time-based job scheduler. Look in /etc/cron.d/ for the configuration and see what command is being executed.

NOTE: Looking at shell scripts written by other people is a very useful skill. The script for this level is intentionally made easy to read. If you are having problems understanding what it does, try executing it to see the debug information it prints.

Solution: Similarly as before, check the cron job, then the script that is run. But here, in order to get to the file, copy the commands that are used in the script to generate the desired file name. Use bandit23 instead of $whoami.

# check out the cron job
cat /etc/cron.d/cronjob_bandit23
...
# check out the script that is run every minute
cat /usr/bin/cronjob_bandit23.sh
...
# run the commands in the script to get the final file name
echo I am user bandit23 | md5sum | cut -d ' ' -f 1
...
# grab the flag in that file
cat /tmp/8ca319486bfbbc3663ea0fbe81326349

Level 23 -> Level 24

Level Goal: A program is running automatically at regular intervals from cron, the time-based job scheduler. Look in /etc/cron.d/ for the configuration and see what command is being executed.

NOTE: This level requires you to create your own first shell-script. This is a very big step and you should be proud of yourself when you beat this level!

NOTE 2: Keep in mind that your shell script is removed once executed, so you may want to keep a copy around…

Solution:

# check out the cron job
cat /etc/cron.d/cronjob_bandit24
...
# check out the script that is run every minute
cat /usr/bin/cronjob_bandit24.sh

Here are the contents of the script that is run every minute:

#!/bin/bash

myname=$(whoami)

cd /var/spool/$myname/foo || exit 1
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 -rf ./$i
    fi
done

As we can see, the script executes and shortly deletes all the scripts (even the hidden ones) that are located in the /var/spool/bandit23/foo/ directory. Therefore, our task here is to create a script that can read out our next flag.

To do so, first we create a temporary file (where we are the owners of the file). This is were we will save the flag that we read out.

# create a temporary file with owner permissions
mktemp
...
# the output will be our path to our temporary file
# like /tmp/tmp.SfdgYS0COS

Now, head over to the /var/spool/bandit23/foo/ directory to create the script. A very simple example could be something like this:

#!/bin/bash
cat /etc/bandit_pass/bandit24 >> /tmp/tmp.SfdgYS0COS

Right after you saved the file (like grabtheflag.sh), you will have to make it executable.

# give everyone on the machine permissions to execute the file
chmod a+x grabtheflag.sh

Now, all you have to do is wait a bit (less than 60 sec) and check your temporary file for the flag.

Make sure you are fast when saving and making your script executable. Your script will be deleted every 60 seconds.

Level 24 -> Level 25

Level Goal: 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 of the 10000 combinations, called brute-forcing. You do not need to create new connections each time

Solution: First, make yourself a temporary directory where you have owner rights.

# create a temporary directory
mktemp -d
...
# head over there
cd <your-temp-dir>

Create a script that writes out all the possible combinations of your flag and the pins into a file. It could look like this:

#!/bin/bash

# flag for level 24
flag="<your-previous-flag>"

# from 10000 on to 0
for i in {10000..0..-1}
do
    echo "$flag $i" >> flags.txt
done

Run your script to generate the flag and pin combinations after you made it executable.

# make your script executable
chmod a+x <your-script-name>
# run your script
./<your-script-name>

Then, we use netcat to open up a tcp connection to our desired port (30002) and we feed the connection our flag and pin pairs. The output is redirected into the output.txt file so that we can easily search for our return flag.

cat flags.txt | nc -vN localhost 30002 > output.txt

Make sure you clean up after yourself after you grabbed the flag.

# move back to your home dir
cd /home/bandit24
# remove your temporary directory and it's files
rm -rv <your-temp-dir>

Level 25 -> Level 26

Level Goal: Logging in to bandit26 from bandit25 should be fairly easy… The shell for user bandit26 is not /bin/bash, but something else. Find out what it is, how it works and how to break out of it.

Solution: The very first thing you should always do is do a quick recon. Simply put, check out your current directory, your user… Once you have done that, it gets pretty clear, that we are provided with bandit26’s private ssh key. The file is sitting in your home directory.

Then, following the task description, we check out which shell bandit26 is using.

cat /etc/passwd | grep "bandit26"

Once we head over and check the file, this is how it looks like:

# file /usr/bin/showtext

#!/bin/sh

export TERM=linux

exec more ~/text.txt
exit 0

In a nutshell, all it does is, that once we log in, instead of the usual bash shell, the more command is run on the ~/text.txt file. Then, once the file is read fully, we simply exit.

So if we try to log in as usual with bandit26’ private ssh key, we will automatically logged out. To avoid this, we have to make sure, that when the more command reads the file, it can’t read it entirely.

We can do this by simply resizing our terminal window when we ssh in as bandit26. Once we are stuck in more, by simply pressing v, we use the subcommand in more to start the vi editor. This will land us inside the vi editor, editing the ~/text.txt file.

The good news is, we can resize our window to actually see something again. The bad news, now we have to escape vi too.

Now, usually we could simply write :![command] to run shell commands from inside the vi editor, but since our shell is still configured to /usr/bin/showtext, we would get back straight to where we started.

To avoid that, we use set shell command inside of vi to specify which shell we want to use. To specify the default bash shell, use the following command:

# inside of the vi editor
:set shell=/bin/bash

Once we specified the shell we want to use, we can simply grab the flag by running

# inside of the vi editor
:! cat /etc/bandit_pass/bandit26

inside the vi editor. To escape the vi editor and land with the usual bash shell, you can simply write :shell into vi.

Level 26 -> Level 27

Level Goal: Good job getting a shell! Now hurry and grab the password for bandit27!

Solution: Since we already have a usual shell connection, once we do our first recon, we will see that there is an executable named bandit27-do lying in our home directory.

Running it without any options/flags will provide us with the program’s basic usage.

# running the executable
./bandit27-do 
Run a command as another user.
  Example: ./bandit27-do id

Since it has the suid set, we can use it’s elevated permissions to grab the next flag.

./bandit27-do cat /etc/bandit_pass/bandit27

Level 27 -> Level 28

Level Goal: There is a git repository at ssh://bandit27-git@localhost/home/bandit27-git/repo. The password for the user bandit27-git is the same as for the user bandit27.

Clone the repository and find the password for the next level.

Solution: One of the solutions is to simply clone the whole repository to your local machine.

# on your local machine - clone the whole repository
git clone ssh://bandit27-git@bandit.labs.overthewire.org:2220/home/bandit27-git/repo

Once you check out the cloned files, you will find the next flag inside the README file.

Level 28 -> Level 29

Level Goal: There is a git repository at ssh://bandit28-git@localhost/home/bandit28-git/repo. The password for the user bandit28-git is the same as for the user bandit28.

Clone the repository and find the password for the next level.

Solution: Repeat the cloning done on the previous level. Once done, look around. This time the README.md file looks like this:

# Bandit Notes
Some notes for level29 of bandit.

## credentials

- username: bandit29
- password: xxxxxxxxxx

So no luck there. But there is something interesting in the git logs.

# get the git logs
git log
commit 899ba88df296331cc01f30d022c006775d467f28 (HEAD -> master, origin/master, origin/HEAD)
Author: Morla Porla <morla@overthewire.org>
Date:   Sun Apr 23 18:04:39 2023 +0000

    fix info leak

commit abcff758fa6343a0d002a1c0add1ad8c71b88534
Author: Morla Porla <morla@overthewire.org>
Date:   Sun Apr 23 18:04:39 2023 +0000

    add missing data

commit c0a8c3cf093fba65f4ee0e1fe2a530b799508c78
Author: Ben Dover <noone@overthewire.org>
Date:   Sun Apr 23 18:04:39 2023 +0000

    initial commit of README.md

The last commit message indicates that there was an info leak before. To check out the changes pushed by that commit, use the git show command with the appropriate commit hash.

# show the git message log and textual diff
git show 899ba88df296331cc01f30d022c006775d467f28
commit 899ba88df296331cc01f30d022c006775d467f28 (HEAD -> master, origin/master, origin/HEAD)
Author: Morla Porla <morla@overthewire.org>
Date:   Sun Apr 23 18:04:39 2023 +0000

    fix info leak

diff --git a/README.md b/README.md
index b302105..5c6457b 100644
--- a/README.md
+++ b/README.md
@@ -4,5 +4,5 @@ Some notes for level29 of bandit.
 ## credentials
 
 - username: bandit29
-- password: <flag>
+- password: xxxxxxxxxx
 

As you can see, the next flag/password is easily readable by simple checking out the differences between the two commits. So instead of the git show command, we could also have used the git diff command like so:

git diff 899ba88df296331cc01f30d022c006775d467f28 abcff758fa6343a0d002a1c0add1ad8c71b88534

Level 29 -> Level 30

Level Goal: There is a git repository at ssh://bandit29-git@localhost/home/bandit29-git/repo. The password for the user bandit29-git is the same as for the user bandit29.

Clone the repository and find the password for the next level.

Solution: We repeat the cloning the same way as we did on the previous two levels. Checking out the cloned README.md file leaves us with:

cat README.md 
# Bandit Notes
Some notes for bandit30 of bandit.

## credentials

- username: bandit30
- password: <no passwords in production!>

Sadly, no flag here. But there is a hint that there may be more branches in our repo. To list both the remote and the local branches use the -a flag.

git branch -a
  dev
* master
  remotes/origin/HEAD -> origin/master
  remotes/origin/dev
  remotes/origin/master
  remotes/origin/sploits-dev

If we change over to the development branch, the flag can be found in the README.md file. ( Only the dev branch contains the flag.)

# change over to the dev branch
git checkout dev
# grab the flag
cat README.md

Level 30 -> Level 31

Level Goal: There is a git repository at ssh://bandit30-git@localhost/home/bandit30-git/repo. The password for the user bandit30-git is the same as for the user bandit30.

Clone the repository and find the password for the next level.

Solution: After cloning the repo as in the previous levels, we are again greeted with no flag in the README.md file. Even looking at multiple branches does not return anything interesting. But looking around does give us an idea to check out the git tags.

# list the git tags
git tag -l
secret

That’s definitely something we need to look into. So we use the git show command in order to check the tagging message, which is stored with the tag.

git show secret
<flag>

The tagging message is exactly what we were looking for: our next flag/password.

Level 31 -> Level 32

Level Goal: There is a git repository at ssh://bandit31-git@localhost/home/bandit31-git/repo. The password for the user bandit31-git is the same as for the user bandit31.

Clone the repository and find the password for the next level.

Solution: Just like before, cloning first, followed by a quick recon. The exact task is written in the README.md file.

cat README.md 
This time your task is to push a file to the remote repository.

Details:
    File name: key.txt
    Content: 'May I come in?'
    Branch: master

So, let’s do exactly that.

# first create and modify the key.txt file as required
echo 'May I come in?' > key.txt
# then check out the .gitignore file
cat .gitignore 
*.txt

# blocks all the txt files :/

Now, change the .gitignore file to allow the key.txt file.

# allow our key.txt file
echo '!key.txt' >> .gitignore

For a successful push, an email and a name is required. Let’s configure them.

# configure a local git user name and email
git config user.name "bandit31-git"
git config user.email bandit31-git@mail.com

Almost finished. All we have to do now is to save our changes. Using git, this is done by first staging the changed files with the git add command. Then commit those changes with the git commit command. And lastly, upload these changes with the git push command.

# stage all the changed files
git add .
...
# commit those changes
git commit -m "modified .gitignore to allow .txt files; added key.txt as required"
...
# finally, push your changes
git push
                         _                     _ _ _   
                        | |__   __ _ _ __   __| (_) |_ 
                        | '_ \ / _` | '_ \ / _` | | __|
                        | |_) | (_| | | | | (_| | | |_ 
                        |_.__/ \__,_|_| |_|\__,_|_|\__|
                                                       

                      This is an OverTheWire game server. 
            More information on http://www.overthewire.org/wargames

bandit31-git@bandit.labs.overthewire.org's password: 
Enumerating objects: 6, done.
Counting objects: 100% (6/6), done.
Delta compression using up to 4 threads
Compressing objects: 100% (2/2), done.
Writing objects: 100% (4/4), 376 bytes | 376.00 KiB/s, done.
Total 4 (delta 0), reused 0 (delta 0), pack-reused 0
remote: ### Attempting to validate files... ####
remote: 
remote: .oOo.oOo.oOo.oOo.oOo.oOo.oOo.oOo.oOo.oOo.
remote: 
remote: Well done! Here is the password for the next level:
remote: <flag> 
remote: 
remote: .oOo.oOo.oOo.oOo.oOo.oOo.oOo.oOo.oOo.oOo.
remote: 
To ssh://bandit.labs.overthewire.org:2220/home/bandit31-git/repo
 ! [remote rejected] master -> master (pre-receive hook declined)
error: failed to push some refs to 'ssh://bandit.labs.overthewire.org:2220/home/bandit31-git/repo'

If we did everything right, the next flag is returned after we finish uploading our repo changes.

Level 32 -> Level 33

Level Goal: After all this git stuff its time for another escape. Good luck!

Solution: I was kinda stuck at this level for a while. But after a little bit of searching, I came across a very nice walkthrough walkthrough. So using the same trick:

# once we log in we are greeted with the welcome message and then some
WELCOME TO THE UPPERCASE SHELL
# break the uppercase shell with $0
>> $0
...
# grab the flag
$ cat /etc/bandit_pass/bandit33
<flag>
$ 

Level 33 -> Level 34

Level Goal: At this moment, level 34 does not exist yet.

Solution: It is still nice to log in :) There is a README.txt file in our home directory. Check it out.

bandit33@bandit:~$ cat README.txt 
Congratulations on solving the last level of this game!

At this moment, there are no more levels to play in this game. However, we are constantly working
on new levels and will most likely expand this game with more levels soon.
Keep an eye out for an announcement on our usual communication channels!
In the meantime, you could play some of our other wargames.

If you have an idea for an awesome new level, please let us know!
bandit33@bandit:~$ 

And with this, we successfully finished the bandit wargame. It was very nice, kudos to the creators. :)