Introduction

Bandit is just one of several wargames available at overthewire.org. It's aimed at beginners to Linux and CTFs and provides an excellent introduction to the basics of the Linux command line.

In this walkthrough I'm going to show the process for solving each challenge while also providing some insight into what each challenge is trying to teach and why it's useful, so keep an eye out for callouts like those below.

i !
Note

Access to each Bandit level is made over SSH. The username will correspond to the index of the level starting at 0. Always make sure you're logging into the correct Bandit level!

i !
Warning

If you're looking for the solution of a particular level, you are highly encouraged to attempt it on your own before following this walkthrough. You will learn some useful things regardless, but attempting it on your own first will help the knowledge stick better. I promise.

I have a few recommendations before getting started. Most of the OverTheWire Bandit levels provide links to potentially useful manual pages. Unfortunately the man pages can be somewhat cryptic, especially for beginners. For that reason I recommend the following steps to try to get unstuck when slogging through the man pages for any particular command.

  1. Search for examples. Many man pages contain examples of their usage. These may be marked by all-caps EXAMPLES in some cases, but sometimes not.
  2. If skimming through the man pages doesn't surface any apparent examples, then you may want to refer to cheat.sh. Cheat.sh is a database of usage examples for thousands of command line programs. It can be searched from the main page or by simply adding a / followed by the command. For example https://cheat.sh/ssh will return examples of ssh usage. This one can be helpful when trying to understand how a command is commonly used and which command flags are most useful.
  3. If you're struggling to understand some of the examples then paste them into https://explainshell.com. Explainshell provides a more readable breakdown of each command line flag and argument. This tool can save you a lot of time flipping back and forth through the man pages.

Please also note that for any terminal output, the command prompt will be truncated to a single $ for brevity. Just remember that you'll need to be logged in to the appropriate level before executing any of the commands.

For example:

$ cat hello.txt
Hello there!

Logging in

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.

Bandit 0 provides an introduction to SSH and provides a couple useful links for further research.

Firstly, let's discuss what SSH is and how it's used day-to-day. SSH is a networking protocol that allows secure communication between networked computer systems. This is distinct from the ssh command line program linked above. The program implements the protocol and allows regular users to utilize the SSH protocol for secure communication. The security of SSH is based on public-key cryptography which you're welcome to read more about, but many of the details are out of scope for this walkthrough.

The most important thing you need to know about SSH is that it can be used to login to a computer system with a username and password just as if you were physically present. Except in this case it's another system accessible over the network. This is how it will be used over the course of the Bandit wargame as well as some of the other wargames by OverTheWire.

i !
Note

It's worth mentioning that SSH is usually (and preferably) used with an SSH key. More specifically a key-pair. A public key and a private key which are both needed to take advantage of the aforementioned public-key cryptography. I won't go into detail here, but don't worry there are a couple later levels that do utilize SSH keys, so I'll discuss them in more detail when we reach them.

The prompt tells us that both the username and password are bandit0.

Just as important though, is the network location we've been provided, bandit.labs.overthewire.org, which we'll use to connect to level 0. Actually, bandit.labs.overthewire.org is what's known as a hostname or domain name. More specifically, it's a fully qualified domain name (FQDN). You can recognize an FQDN because it will include a top-level domain (TLD) such as .com, .net, or .org. This naming system for computers is called the Domain Name System (DNS). Luckily we don't need to dig into the details here. Just know that one of these FQDNs will resolve to an IP address which can (usually) be used to uniquely identify a computer system on the internet.

This unique name will tell the ssh program what system to connect to. The ssh program expects the user credentials and hostname in the following format ssh://[user@]hostname[:port]. This is explained in the first couple paragraphs of the ssh man page.

ssh connects and logs into the specified destination, which may be specified as either [user@]hostname or a URI of the form ssh://[user@]hostname[:port].

The elements wrapped in brackets [ and ] are actually optional. Linux man pages will commonly express the available flags and arguments for a command in this way.

With that knowledge we can now connect to the first level with this command.

ssh ssh://bandit0@bandit.labs.overthewire.org:2220

You should be greeted by a banner for the Bandit wargame and a prompt requesting entry of the bandit0 password. Enter bandit0 and you're ready to get started.

i !
Note

It isn't necessary to solve any of the Bandit levels, but I highly recommend researching DNS. It's intrinsic to how the internet operates and you'll likely encounter it again and again if you pursue any area of IT, software, or cybersecurity.

Resources:

  • What is DNS?: an excellent intro to the basics of DNS by freeCodeCamp
  • Implement DNS in a weekend: if you have some programming experience, I highly recommend following this walkthrough by Julia Evans

Walkthrough

Level 0

Now that we're logged in, it's time to solve the first level.

The password for the next level is stored in a file called readme located in the home directory. Use this password to login to 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.

Useful commands 1

The prompt also provides links to the man pages for several commands.

With perhaps the exception of du. Each of these commands is fundamental for any Linux user who wants to effectively use the command line.

CommandDescription
lslist the files in a directory
cdchange your current directory
catdisplay the contents of a file
filedisplay information about the content of a file
dudisplay the disk space used by files
findsearch the file system for files with various parameters such as filename, file type, file size, etc.

You are highly encouraged to review each of the commands. At the very least check out the cheat.sh page for each. Regardless these are all pretty common, so you'll get plenty of experience with these commands in later levels.

For now, it should be apparent that one of the above commands should serve to show us the contents of the readme file mentioned in the prompt. The humble cat command.

It takes a filename as a parameter. So providing it with the filename readme should print it's contents.

$ cat readme
Congratulations on your first steps into the bandit game!!
Please make sure you have read the rules at https://overthewire.org/rules/
If you are following a course, workshop, walkthrough or other educational activity,
please inform the instructor about the rules as well and encourage them to
contribute to the OverTheWire community so we can keep these games free!

The password you are looking for is: [REDACTED PASSWORD]

I won't be providing any of the passwords throughout this walkthrough per the OverTheWire rules. So get out your notepad and copy those passwords. You'll need them to return to the last level you've completed without completing each level again. Once you've done that, you're ready to continue to Level 1.

Level 1

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

The prompt also provides links to the man pages for the same commands as level 0.

Naturally, you might think to try the cat command again. Afterall, we're told the password in in the file called -. Let's try it and see what happens.

$ cat -

Strange, you're left with a blank line on the terminal now. And, if you type some text and press the Enter key, the text is repeated back to the terminal like so.

$ cat -
hello there
hello there

The reason for this will become clear if you read the description from the cat man page.

NAME
   cat - concatenate files and print on the standard output

SYNOPSIS
   cat [OPTION]... [FILE]...

DESCRIPTION
   Concatenate FILE(s) to standard output.
   With no FILE, or when FILE is -, read standard input.

So, according to the description, the parameter -, makes cat read from standard input. You may have guessed this already, but standard input or STDIN is usually what's entered by the user in the terminal; however, STDIN doesn't explicitly refer to input from a user. Rather, it refers to a stream of data that is being sent to a program, so it may also refer to files or even the output of other programs that is being passed to other programs.

You'll most likely hear of this concept of input and output referred to as STDIO or standard input and output. If you'd like to read more, there is an excellent article by freeCodeCamp explaining more about it and many other useful concepts.

There will be opportunities to demonstrate STDIO and IO redirection later, but for this level, all you need to recognize is that - is a special character that tells cat to read input from STDIN instead of a file as we saw before. So to properly refer to the - file, it must be referenced by some other means than the simple filename. There are several ways to accomplish that.

  1. Use ./<filename> where <filename> would be - for this example. The . is a special character that is interpreted as the current directory. This is usually implied when we just enter a file by it's name. However, stating it explicitly allows us to circumvent the special case of using - as an argument to cat.

    $ cat ./-
    [REDACTED PASSWORD]
    
  2. Use the full path. On Linux and other Unix-based systems the root of the file system can be specified with a /. To use this method though we'll need to know the full path of the - file. To get that we can use the pwd command, which is short for "print working directory".

    $ pwd
    /home/bandit1
    

    To complete the full path for - we just need to append /<filename>.

    $ cat /home/bandit1/-
    [REDACTED PASSWORD]
    
  3. We can also use what is called a glob (*). The glob can be used to execute commands over multiple files at once. For example using the following command will print all files in the current working directory.

    $ cat ./*
    [REDACTED PASSWORD]
    

    In this example the only file in our current directory is the password file -. But just like the other examples it circumvents the special - argument to cat.

    The glob is actually a part of a larger set of filename expansion features available in Bash. They're quite useful and we may see more of them in future levels.

i !
Tip

The main takeway from this level is that there are special characters that may change how commands are interpreted on the command line. Some of them will be built-in to whatever shell you're using, but some may just be conventions like the - character for STDIN and won't apply to every program. Here is a breakdown of the special characters used in the Bash shell that you might want to watch out for. https://mywiki.wooledge.org/BashGuide/SpecialCharacters

Level 2

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

This level is similar to Level 1, except that the file is not a special character. Instead it contains special characters, the space █. The space character is essential for the shell to interpret the input text.

$ cat spaces in this filename
cat: spaces: No such file or directory
cat: in: No such file or directory
cat: this: No such file or directory
cat: filename: No such file or directory

As you can see from above, entering the name as it's written will cause the cat command to interpret each word in the file as a separate filename.

Just as before, there are a few ways to get around this.

  1. Escape the space characters. In Bash, the backslash \ is used as an escape character. When the \ is used, the following character is interpreted literally. This allows the space characters of the filename to be "escaped".

    $ cat spaces\ in\ this\ filename
    [REDACTED PASSWORD]
    
  2. Instead of escaping the spaces individually, the filename can also be surrounded by single quotes. Every character between two single quotes is interpreted literally.

    $ cat 'spaces in this filename'
    [REDACTED PASSWORD]
    
  3. Similarly, double quotes can also be used to interpret the surrounded characters literally. However, there are some exceptions, and double quotes allow some special characters to be interpreted. But the space character is not one those, so it is functionally the same as the above example in this case.

    $ cat "spaces in this filename"
    [REDACTED PASSWORD]
    
i !
Tip

Recognize that it's sometimes necessary to escape characters within filenames and other arguments.

Most Linux users will avoid naming files with any special characters, but sometimes you'll still run into them. This is especially true with files created on Windows where spaces inside file and directory names are much more common.

Level 3

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

$ ls ./inhere

According to the ls command, there don't appear to be any files in the inhere directory. That's because ls doesn't display hidden files by default.

To show hidden files with ls, it's necessary to use the --all or -a flag.

$ ls -a ./inhere
.  ..  ...Hiding-From-You

With the --all flag enabled, ls now shows all the files in the inhere directory. Including the hidden files. In Linux and other Unix-based systems, a leading dot . in a filename is used to indicate a hidden file. As such, you may here them referred to as dotfiles.

Now that we know the name of the hidden file, cat can be used to print the contents just as we've seen before.

$ cat inhere/...Hiding-From-You
[REDACTED PASSWORD]
i !
Note

The term "dotfile" is also frequently used to refer to a user's personal configuration files. This is because many configuration files follow the leading dot . convention, so they don't clutter up directory listings.

i !
Tip

Some systems will have aliases for the ls command as well, with various flags enabled. Here a few common ones.

  • ll for ls -AlhF to list all files in a human-readable, long format
  • la for ls -A to list all files excluding . and ..
  • l for ls -CF to list files in a column format
  • lsd for ls --group-directories-first

See this DigitalOcean article to learn more about common aliases and how to configure your own.

Level 4

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.

If you haven't done so already, now would be a good time to read through the manual for each of the commands that were already mentioned in Level 0.

If you've read through the description for each, you should have a pretty good idea of which command you'll need to solve this one.

This level is asking us to identify which files in inhere are human readable. The file command can get the information we need.

From the file manual page we know that we can expect a response of text for any files that only contain typical ASCII characters.

The  type  printed  will  usually  contain one of the words `text` (the file
contains only printing characters and a few common control characters and is
probably safe to read on an ASCII terminal), `executable` (the file contains
the result of compiling a program  in  a form  understandable to some UNIX
kernel or another), or `data` meaning anything else (data is usually “binary”
or non-printable).

One option is to execute file on each file in inhere one-by-one like so.

$ file inhere/-file00
inhere/-file00: data

A better way is to use the glob * that we've seen previously. This allows us to run the file command on all the files at once.

$ file ./inhere/*
./inhere/-file00: data
./inhere/-file01: data
./inhere/-file02: data
./inhere/-file03: data
./inhere/-file04: data
./inhere/-file05: data
./inhere/-file06: data
./inhere/-file07: ASCII text
./inhere/-file08: data
./inhere/-file09: data

From here it's obvious that we want the file with the text data at ./inhere/-file07.

$ cat ./inhere/-file07
[REDACTED PASSWORD]
i !
Tip

Always keep an eye out for opportunities to use globbing. Especially if you're running commands over a bunch of files.

Level 5

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

This level requires a precise approach to locate the correct file. While it's technically possible to solve with just the ls and file commands. It would require some tedious manual searching to find the files that match each of the criteria. Luckily the find command is capable of locating files with all the above criteria. You just need to know the right flags.

The find command is essential to efficiently locating files on Linux systems. It has several flags that can be used to refine its search. Most importantly for this level are -readable, -size and -executable.

$ find -readable -size 1033c -not -executable
./inhere/maybehere07/.file2

Note -size and -not flags. The c suffix for the -size argument is used to indicate a size in bytes. The other available suffixes are all available in the find man page. Additionally, the -not flag negates the next expression, thus locating any files that aren't executable in this example.

i !
Note

In this case the content and size of the file are sufficient to uniquely identify the file and the -not -executable isn't strictly necessary.

$ find -readable -size 1033c
./inhere/maybehere07/.file2

Once again, cat the file to get the password.

$ cat ./inhere/maybehere07/.file2
[REDACTED PASSWORD]

Level 6

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

This level is very similar to Level 5 with a couple minor differences. First, the file is "stored somewhere on the server" instead of in the inhere directory. That just means we'll need to run the find command from the root of the file system to ensure the file isn't missed. Secondly, the file is specified by two new parameters. The user and group that own the file.

Searching the find manpage you can find the two flags -user and -group to filter for files owned by the bandit7 user and the bandit6 group as specified by the prompt. The final parameter is for a -size of 33 bytes which we already saw in level 5.

Here's an example of the output from find with all the required arguments.

$ find / -user bandit7 -group bandit6 -size 33c
find: ‘/drifter/drifter14_src/axTLS’: Permission denied
find: ‘/root’: Permission denied
find: ‘/snap’: Permission denied
find: ‘/tmp’: Permission denied
find: ‘/proc/tty/driver’: Permission denied
find: ‘/proc/250118/task/250118/fd/6’: No such file or directory
find: ‘/proc/250118/task/250118/fdinfo/6’: No such file or directory
find: ‘/proc/250118/fd/5’: No such file or directory
...
[TRUNCATED OUTPUT]

Unfortunately searching with find from / has a side effect. Any files or directories that the current user is not allowed to read will print an error to the terminal. This makes it pretty difficult to parse the output for any resulting files that match our search. To avoid this flood of errors, a common solution is to redirect the standard error stream to /dev/null This is actually just a file that discards anything written to it.

To redirect a data stream in Bash we must specify its file descriptor, which is an integer. The file descriptor is followed by a greater than sign > which indacates that the stream should be redirected to a target file. Here is the same command as above with all errors redirect to /dev/null.

$ find / -user bandit7 -group bandit6 -size 33c 2>/dev/null
/var/lib/dpkg/info/bandit7.password

As you can see, the output is considerably easier to understand. Read the file at /var/lib/dpkg/info/bandit7.password to get the next password.

Level 7

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

Useful commands 2

This is the first level that OverTheWire introduces some new recommended commands since level 0.

CommandDescription
manaccess the system reference manuals
grepprint lines that match patterns
sortsort lines in text files
uniqremove duplicate lines from a file
stringsprint readable strings from arbitrary files (even binary)
base64encode data into Base64
trtranslate and replace characters
tara utility for archive files
gzipa utility for compressing files
bzip2a utility for compressing files
xxda tool for creating a hex dump of a file

Once again, I highly recommend at least reading through the introduction for each of these commands and checking out the examples at cheat.sh.

If you've followed the above advice, there should really only be one contender to solve this level. The grep command.

NAME
   grep - print lines that match patterns

SYNOPSIS
   grep [OPTION...] PATTERNS [FILE...]
   grep [OPTION...] -e PATTERNS ... [FILE...]
   grep [OPTION...] -f PATTERN_FILE ... [FILE...]

DESCRIPTION
   grep  searches for PATTERNS in each FILE. PATTERNS is one or more
   patterns separated by newline characters, and grep prints each line that
   matches a pattern. Typically PATTERNS should be quoted when grep is used in a
   shell command.

According to the syntax description we should be able to search for patterns in a file with the following syntax.

grep <PATTERN> <FILE>

Swapping in the values mentioned in the prompt will return the line we're looking for.

$ grep "millionth" data.txt
millionth       [REDACTED PASSWORD]

Regex

Grep and more generally regular expressions (regex) are extremely useful. You'll find many applications have integrated support for text search via regex. In particular text editors, word processors, and programming IDEs.

i !
Tip

To explore regex more I highly recommend reading through the Regex Quick Start Guide from regular-expression.info to get a feel for what's possible with regex and then follow that up with some experimentation on regex101.com. This is a tool that visualizes regex matches. Drop any text you want into it and try out all kinds of search patterns. Try to match words, letters, various combinations of upper and lowercase letters, punctuation, etc. Seeing regex in action and observing precisely what matches with different patterns will give you a much better intuition for what's possible than anything I could write here.

Regex can get very complicated very quickly, so regex101 is also a great tool for debugging your regex. Definitely give it a bookmark, it'll be a life saver. Trust me.

Level 8

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

This level is the first that seriously benefits from chaining two commands together. In Bash this is done with the pipe | character. The pipe, when placed after a command will pass all of the output (stdout) into the input (stdin) of the command that follows it.

For example, we can combine the ls and grep commands to list only those files that contain "bash" in the name.

$ ls -a | grep bash
.bash_logout
.bashrc

Remember that the -a flag is necessary to list hidden or dot files.

Read through the Piping and Redirection article provided under the helpful reading material section to learn more about piping.

Reviewing the recommended commands, one should stick out.

NAME
   uniq - report or omit repeated lines

SYNOPSIS
   uniq [OPTION]... [INPUT [OUTPUT]]

DESCRIPTION
   Filter adjacent matching lines from INPUT (or standard input), writing
to OUTPUT (or standard output).

   With no options, matching lines are merged to the first occurrence.

The uniq command is able to filter matching or repeated lines. Since we're looking for a unique line in a file, this will be helpful. However, there is one caveat when using uniq that you must be aware of. The uniq command filters adjacent matching lines. This means that any matching lines that aren't directly adjacent, won't be filtered. So the first step must be to organize the file such that matching lines are adjacent. In other words the file should be sorted. The sort program is designed precisely for this use case.

For example, here's the first 20 lines of data.txt when sorted.

$ sort data.txt | head -n20
0BKVRLEJQcpNx8wnSPxDLFnFKlQafKK6
0BKVRLEJQcpNx8wnSPxDLFnFKlQafKK6
0BKVRLEJQcpNx8wnSPxDLFnFKlQafKK6
0BKVRLEJQcpNx8wnSPxDLFnFKlQafKK6
0BKVRLEJQcpNx8wnSPxDLFnFKlQafKK6
0BKVRLEJQcpNx8wnSPxDLFnFKlQafKK6
0BKVRLEJQcpNx8wnSPxDLFnFKlQafKK6
0BKVRLEJQcpNx8wnSPxDLFnFKlQafKK6
0BKVRLEJQcpNx8wnSPxDLFnFKlQafKK6
0BKVRLEJQcpNx8wnSPxDLFnFKlQafKK6
0eJPctF8gK96ykGBBaKydhJgxSpTlJtz
0eJPctF8gK96ykGBBaKydhJgxSpTlJtz
0eJPctF8gK96ykGBBaKydhJgxSpTlJtz
0eJPctF8gK96ykGBBaKydhJgxSpTlJtz
0eJPctF8gK96ykGBBaKydhJgxSpTlJtz
0eJPctF8gK96ykGBBaKydhJgxSpTlJtz
0eJPctF8gK96ykGBBaKydhJgxSpTlJtz
0eJPctF8gK96ykGBBaKydhJgxSpTlJtz
0eJPctF8gK96ykGBBaKydhJgxSpTlJtz
0eJPctF8gK96ykGBBaKydhJgxSpTlJtz

As mentioned before, the pipe | is useful here to send the output of the sort command to uniq.

$ sort data.txt | uniq | head -n20
0BKVRLEJQcpNx8wnSPxDLFnFKlQafKK6
0eJPctF8gK96ykGBBaKydhJgxSpTlJtz
0kJ7XHD4gVtNSZIpqyP1V45sfz9OBLFo
0lPOvKhpHZebxji0gdjtGCd5GWiZnNBj
0REUhKk0yMqQOwei6NK9ZqIpE5dVlWWM
1jfUH1m4XCjr7eWAeleGdaNSxFXRtX0l
1VKPEkd0bCtIRwMFVQfY7InulwOFyDsn
2u8fvAzvnaFlvQG3iPt4Wc1TFhPcGxhH
35l6mr3f6TvlJyDwU6aUgJX07cLhr6t9
3FIgajXBiaQAiTMVGo1gxRDSiACNyvvJ
3mNA2le0gfURQKNHVIhGkMNLqLwjyyLN
4CKMh1JI91bUIZZPXDqGanal4xvAg0JM
4P8FsHcdr7d5WKnPtAaXY5SslKICd2gL
5EmwMKZHwF6Lwq5jHUaDlfFJBeHbcX0b
5hYz0028e1Q2TrtPVz5GZbpMzZNjebhh
5I2jWpqjtVp576xXI2TLh1UCyXJtGQ78
6Boy6esAjnIxCYn8uI6KZ7VD7zysDM8i
7cP8ssLElERHXqOJc9T84bxsmJBjNXk2
7qHmEo1FEbzthgyNpKc38YofXjYKZv18
8FCtUQlFXsJnNeyiDY5KfE3vRy6sZFEJ

Well that's strange. Why has uniq returned all those lines? If you read the description for uniq carefully, then one line explains this.

With no options, matching lines are merged to the first occurrence.

So essentially what we have here is a file with each unique line of the file where adjacent duplicates have been compressed into a single line. However, we want to list only the unique lines from the input. For that, the -u flag will meet our needs. The -u flag tells sort to only print unique lines. In other words, only lines without any duplicates in the input.

So finally, we have a solution.

$ sort data.txt | uniq -u
[REDACTED PASSWORD]

Level 9

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.

Once again we're searching a file, so exploring our options with grep may be a good idea.

Trying a basic grep for several = characters doesn't seem to work.

$ grep '=== \w*' data.txt
grep: data.txt: binary file matches

The \w is a shortcut for any word characters which equivalent to [a-zA-Z0-9_].

We can see here, that grep found some matches, but data.txt was interpreted as a binary file. We can force grep to process the file as if it were text with the -a flag.

$ grep -a '=== \w*' data.txt
D]
  h#!QJsVzl7POl%Y]Ha^UvToD|@T^N8g}b}?
Q#gm1x}========== theѦ+idW^)F1>)٘SK3PZt&xs肉WB/2ÜB       Ź/Bjɢ<7<u/d|
                                                                    -n
#iu=
    7֣n)Uջش5bBKK}x>}:4Rl_7gHD:274CFy
6!&zB$l_GphqI.02H$Twm⧫o3mt0p~L3JprD========== passwordi L ~ˏ<@Ȅh$%Q5Dk |3
~Tf;o9sP#t+Pe΢쵟
OqDf.8Czmnf&vl:FXKbM
                 CIBi>Y
Еk      $nXT=~}*4a2?TO"'&J~fDV3========== isd5z(#&s!10&poq
nR F
    z|!(if+A64+'FTb5A}
éT:kAU2Qcɐ%#g+;YA_ekrX53|f8+e~&Oiu?VhM}^Qp^G==6!sT:     "uVa-t\fg
]󈍅(.ۍg:7nnp CD`voSQ-<]`@#H UumBiAj堵!O&D9========== [REDACTED PASSWORD]

On the last line of the above output, you'll find the password to the next level.

i !
Tip

It's possible for grep to output the precise matching text instead of each entire line. The -o flag is needed to do this. It tells grep to only output the matched pattern.

$ grep -a -o '=== \w*' data.txt
=== the
=== passwordi
=== is
=== [REDACTED PASSWORD]

This gives a much clearer picture of the password without all the surrounding binary data.

Level 10

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

This one is pretty straight forward. The prompt gives it away by mentioning that the data is Base64 encoded.

Check the manual for the base64 command and you'll find one of the first flags is -d for --decode.

$ base64 -d data.txt
The password is [REDACTED PASSWORD]
i !
Note

Be sure you understand what encoding is in this context.

Base64 is just one scheme of many to convert binary data into printable characters i.e. alphanumeric characters with the addition of the equal sign =. In fact, that equal sign = is used for padding the end of encoded text, so it's often a dead giveaway that some text was Base64 encoded. For example, the word "password" when Base64 encoded is cGFzc3dvcmQ=.

Base64 encoded text is extremely common on the web and you're likely to come across it at some point, so it may behoove you to read up on it. FreeCodeCamp has an excellent article that gives a good overview of how Base64 works and what it's used for.

You're less likely see other forms of binary to text encoding, but feel free to read more.

Level 11

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

This challenge is describing what's commonly known as a shift cipher or Caesar cipher. It's not actually used in modern times for any meaningful attempts at securing messages, but it's somewhat popular in CTFs and wargames.

To keep within the spirit of the wargame, let's first go over how you might solve this challenge in the terminal. One of the recommended commands is tr which can "[t]ranslate, squeeze, and/or delete characters" according to the description. Here's a few different ways to go about it.

# Rotate the alphabet by 13 letters where both input and output are explicit
cat data.txt | tr "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ" "nopqrstuvwxyzabcdefghijklmNOPQRSTUVWXYZABCDEFGHIJKLM"

# Similar to above except the input is defined by a regular expression
cat data.txt | tr "a-zA-Z" "nopqrstuvwxyzabcdefghijklmNOPQRSTUVWXYZABCDEFGHIJKLM"

# Similar to above except both the input and output are defined by a regular expression
# Note: the odd arrangement for the output is the necessary since Regex doesn't allow
# the letter ranges to wrap around
cat data.txt | tr "a-zA-Z" "n-za-mN-ZA-M"
i !
Tip

While the tr command is cool and all, there's an even cooler tool you should be using when investigating any challenge related to cryptography. And that's CyberChef. CyberChef has a huge number of useful features for transforming data and supports hundreds of data formats and encoding schemes.

Check out the CyberChef ROT13 cipher solver.

Level 12

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. Use mkdir with a hard to guess directory name. Or better, use the command mktemp -d. Then copy the datafile using cp, and rename it using mv (read the manpages!)

This challenge is quite tedious. As the prompt mentions, it's a good idea to create a temp directory to work with all the files. For example mkdir /tmp/my-super-secret-directory followed by mv ~/data.txt /tmp/my-super-secret-directory. Now we're ready to begin.

The first step is to recognize the format of the data.txt.

Taking a look at the first few lines shows that this file isn't just a text file. It's a hexdump. Read the first few lines with head -n5 data.txt.

00000000: 1f8b 0808 dfcd eb66 0203 6461 7461 322e  .......f..data2.
00000010: 6269 6e00 013e 02c1 fd42 5a68 3931 4159  bin..>...BZh91AY
00000020: 2653 59ca 83b2 c100 0017 7fff dff3 f4a7  &SY.............
00000030: fc9f fefe f2f3 cffe f5ff ffdd bf7e 5bfe  .............~[.
00000040: faff dfbe 97aa 6fff f0de edf7 b001 3b56  ......o.......;V

Don't panic! You don't need to be able to read this stuff right away. Just recognizing it as a hexdump is enough. Fortunately, one of the recommended commands is made specifically to handle hexdumps. The xxd command.

Using xxd with the -r flag can reverse the hexdump into a binary file.

xxd -r data.txt > data

This output data file is now in it's original format and can be examined with file data to determine it's type.

data: gzip compressed data, was "data2.bin", last modified: Thu Sep 19 07:08:15 2024, max compression, from Unix, original size modulo 2^32 574

The result from file identifies it as "gzip compressed data". To decompress the archive, use the gunzip command.

i !
Warning

Be aware, that the archive utilities like gunzip may require particular file extensions when decompressing files. For example, .gzip or .gz. Otherwise you may get an error like this.

gzip: data: unknown suffix -- ignored

To complete this challenge, you must repeat this process of decompressing or extracting data into a new format, then verifying the new format with file eight times to reach the original flag file content.

Below is a script describing each step of the decompression. You could run the script directly on the Bandit host to get the flag, but I encourage you to walk through each decompression step manually and observe the different flags being used for each command.

#!/bin/sh
# extract.sh
xxd -r data.txt > f1.gz;    # extract first gzip archive from hexdump
gunzip -c f1.gz > f2.bz2;   # extract bzip2 archive from f1.gz
bunzip2 -c f2.bz2 > f3.gz;  # extract gzip archive from f2.bz2
gunzip -c f3.gz > f4.tar;   # extract tar archive from f3.gz
tar -xOf f4.tar > f5.tar;   # extract tar archive from f4.tar
tar -xOf f5.tar > f6.bz2;   # extract bzip2 archive from f5.tar
bunzip2 -c f6.bz2 > f7.tar; # extract tar archive from f6.bz2
tar -xOf f7.tar > f8.gz;    # extract gzip archive from f7.tar
gunzip -c f8.gz > flag;     # extract plaintext flag file from f8.gz

cat flag;

Level 13

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

Useful commands 3

Once again, OverTheWire has provided some new recommended commands to investigate.

CommandDescription
ssha program for logging into or executing commands on remote machines
telnetcommunicate with another host using the TELNET protocol
ncthe swiss army knife for communicating over the network using TCP, UDP, or Unix-domain sockets
openssla program for using various cryptography functions of the OpenSSL crypto library from the shell
s_clienta program implementing a generic SSL/TLS client
nmapa network scanner for network exploration and security auditing

Each program is worth exploring, but for this challenge we'll only need ssh. However, it won't be used quite the same as before. This time an SSH private key is required to login to the next level.

After logging in to bandit13, you should find the private SSH key mentioned in the prompt at /home/bandit13/sshkey.private. Copy that file to your primary host. You can simply copy paste the text if you wish, or use something like scp.

i !
Warning

Watch out! The ssh command requires private key files to have appropriate permissions. If they aren't correct you may receive an error message like this.

@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@
@         WARNING: UNPROTECTED PRIVATE KEY FILE!          @
@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@
Permissions 0644 for 'sshkey.private' are too open.
It is required that your private key files are NOT accessible by others.
This private key will be ignored.
Load key "sshkey.private": bad permissions

A brief reminder of the octal permissions.

Octal NumberPermission TypeSymbols
0No permissions---
1Execute--x
2Write-w-
3Write + Execute-wx
4Readr--
5Read + Executer-x
6Read + Writerw-
7Read + Write + Executerwx

As the error mentions, the permissions 0644 are too open. That's because private key files should only be readable and/or writeable by the user they belong to. Usually that means either 600 or 400, though 400 is a bit strict and won't allow the file to edited.

So, setting the permissions to 600 would give the key file read and write access for the user, and no permissions for either the group or others.

chmod 600 sshkey.private

Now we're ready to connect to bandit14.

To use a key file with ssh, the -i flag can be used.

E.g.

ssh user@host -i private_key

To connect to bandi14 use the following command.

ssh bandit14@bandit.labs.overthewire.org -p 2220 -i sshkey.private

SSH is the most common protocol used for remotely administrating Linux and Unix-like systems, and using a key file as we've done here is by far the most common way it's used. It's not strictly necessary, but I highly recommend reading up on the fundamentals of public-key cryptography. It's how SSH guarantees1 it's security.

i !
Note

In the solution above, we just used the -i flag to specify the private key file. However, anyone using ssh on a regular basis will rightly tell you to consider configuring ssh on your system for a much simpler workflow.

If you're connecting to many hosts via ssh, it is much more convenient to configure the ssh-agent to handle your ssh keys automatically so you don't need to specifiy the key file with the -i flag each time.

You may also want to combine this with host-specific configurations. This can be done with a config file usually at ~/.ssh/config. Check out the ssh_config manual for more details.

Level 14

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

If you aren't familiar with IP addresses or ports, I highly encourage you to read the recommended reading material on the Bandit 14 page.

This time we need to connect to a particular port, but we won't be using the SSH protocol. Instead we need to send the current password as unencrypted text to port 30000 on localhost. The nc (netcat) utility can do just that.

Netcat establishes a connection with the following syntax:

nc <destination> <port>
  • The destination can be any domain as long as it resolves to an IP address, or it can take an IP address directly.
  • The port corresponds to the TCP/UDP port.

Now, connect with the parameters given in the prompt.

netcat localhost 30000

This will establish the connection, but won't provide a new terminal prompt. That's because the program you've connected to is now awaiting input. Provide the level 14 password and press Enter, and the level 15 password will be returned.

Level 15

The password for the next level can be retrieved by submitting the password of the current level to port 30001 on localhost using SSL/TLS encryption.

Helpful note: Getting “DONE”, “RENEGOTIATING” or “KEYUPDATE”? Read the “CONNECTED COMMANDS” section in the manpage.

Now, we've got another challenge asking us to connect to a port on localhost and submit the password, but this time, the password must be encrypted using SSL/TLS.

One of the recommended commands is s_client which is a generic SSL/TLS2 client. We'll need to use a couple flags, -host, and -port.

openssl s_client -host localhost -port 30001

It's actually possible to elide the -host flag since s_client connects to localhost by default.

openssl s_client -port 30001

By default there, will be a fair amount of output describing the security parameters for the connection such as the certificate and handshake details.

Once the connection is established there should be a line at the bottom read R BLOCK. At this point the terminal is awaiting input. Provide the current password to receive the password for the next level.

---
read R BLOCK
[REDACTED BANDIT 15 PASSWORD]
Correct!
[REDACTED BANDIT 16 PASSWORD]

closed

Level 16

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/TLS 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.

Helpful note: Getting “DONE”, “RENEGOTIATING” or “KEYUPDATE”? Read the “CONNECTED COMMANDS” section in the manpage.

This challenge is almost identical to Level 15. However, this time we are not given the appropriate port. Instead, we're only given a range of suspect ports. To identify the services running on these TCP/UDP ports, we can use the nmap program.

Nmap is a network scanning tool which is often used to automatically enumerate devices and ports in a network. In our case, we're only interested in ports on the Bandit host, or in other words the localhost.

To scan all the ports in the given range on localhost we'll run an nmap command.

nmap localhost -p31000-32000

This should return the following:

Starting Nmap 7.94SVN ( https://nmap.org ) at 2025-05-29 20:28 UTC
Nmap scan report for localhost (127.0.0.1)
Host is up (0.00017s latency).
Not shown: 996 closed tcp ports (conn-refused)
PORT      STATE SERVICE
31046/tcp open  unknown
31518/tcp open  unknown
31691/tcp open  unknown
31790/tcp open  unknown
31960/tcp open  unknown

Nmap done: 1 IP address (1 host up) scanned in 0.18 seconds

This scan results in just a few open ports. Given such a short list, one option is to attempt communication on each port to see which one speaks SSL/TLS. However, if there were many more open ports, it would make more sense to automate that detection with nmap. Luckily this is possible with just one flag -sV which can be found under the SERVICE/VERSION DETECTION section in the manpage.

nmap localhost -p31000-32000

This version of the command will take a bit longer since nmap must analyze the traffic on each open port to determine what service may be running. The output should look something like this.

Starting Nmap 7.94SVN ( https://nmap.org ) at 2025-05-29 21:11 UTC
Nmap scan report for localhost (127.0.0.1)
Host is up (0.00018s latency).
Not shown: 996 closed tcp ports (conn-refused)
PORT      STATE SERVICE     VERSION
31046/tcp open  echo
31518/tcp open  ssl/echo
31691/tcp open  echo
31790/tcp open  ssl/unknown
31960/tcp open  echo
1 service unrecognized despite returning data. If you know the service/version, please submit the following fingerprint at https://nmap.org/cgi-bin/submit.cgi?new-service :
SF-Port31790-TCP:V=7.94SVN%T=SSL%I=7%D=5/29%Time=6838CD8E%P=x86_64-pc-linu
SF:x-gnu%r(GenericLines,32,"Wrong!\x20Please\x20enter\x20the\x20correct\x2
SF:0current\x20password\.\n")%r(GetRequest,32,"Wrong!\x20Please\x20enter\x
SF:20the\x20correct\x20current\x20password\.\n")%r(HTTPOptions,32,"Wrong!\
SF:x20Please\x20enter\x20the\x20correct\x20current\x20password\.\n")%r(RTS
SF:PRequest,32,"Wrong!\x20Please\x20enter\x20the\x20correct\x20current\x20
SF:password\.\n")%r(Help,32,"Wrong!\x20Please\x20enter\x20the\x20correct\x
SF:20current\x20password\.\n")%r(FourOhFourRequest,32,"Wrong!\x20Please\x2
SF:0enter\x20the\x20correct\x20current\x20password\.\n")%r(LPDString,32,"W
SF:rong!\x20Please\x20enter\x20the\x20correct\x20current\x20password\.\n")
SF:%r(SIPOptions,32,"Wrong!\x20Please\x20enter\x20the\x20correct\x20curren
SF:t\x20password\.\n");

Service detection performed. Please report any incorrect results at https://nmap.org/submit/ .
Nmap done: 1 IP address (1 host up) scanned in 155.35 seconds

Here we have the same open ports as before, but this time, with some additional version information. You'll notice that SSL/TLS responses were only detected on ports 31518 with ssl/echo and 31790 with ssl/unknown. The ssl/echo is just a simple echo service like the others, except that it is served via SSL/TLS, which only leaves the remaining ssl/unknown service. You can actually see the initial text sent and received by nmap when analyzing port 31790. It seems to include several failed password attempts.

Just as in Level 15, we can use the following OpenSSL command to start an SSL/TLS connection with our target port.

After the initial connection is established, the program awaits some user input which requires the password to the current level.

Once the password is entered, it's possible that the service does not immediately reply with the credentials for the next level. As mentioned in the prompt, if there is a response including "DONE", "RENEGOTIATING", or "KEYUPDATE" then you may need to check the "CONNECTED COMMANDS" section of the openssl-s_client manpage.

CONNECTED COMMANDS (BASIC)
       If  a connection is established with an SSL/TLS server then any data received from the server
       is displayed and any key presses will be sent to the server. If end of file is  reached  then
       the connection will be closed down.

       When  used  interactively  (which  means  neither  -quiet  nor -ign_eof have been given), and
       neither of -adv or -nocommands are given then "Basic" command mode is entered. In  this  mode
       certain commands are recognized which perform special operations. These commands are a letter
       which  must  appear  at the start of a line. All further data after the initial letter on the
       line is ignored.  The commands are listed below.

       Q   End the current SSL connection and exit.

       R   Renegotiate the SSL session (TLSv1.2 and below only).

       C   Attempt to reconnect to the server using a resumption handshake.

       k   Send a key update message to the server (TLSv1.3 only)

       K   Send a key update message to the server and request one back (TLSv1.3 only)

In my case, I received a "KEYUPDATE" response because the password I used begins with a "k".

--- snip ---
read R BLOCK
[REDACTED LEVEL 15 PASSWORD]
KEYUPDATE

Since I didn't provide any of the -quiet, -ign_eof, -adv, or -nocommands flags, the connection entered the "Basic" command mode which uses the first character in the next line of input to determine which command to execute.

To prevent this "Basic" mode from clobbering our input we need use one of these flags. For example.

openssl s_client -connect localhost:31790 -nocommands

Which should return the following.

--- snip ---
read R BLOCK
[REDACTED LEVEL 16 PASSWORD]
Correct!
-----BEGIN RSA PRIVATE KEY-----
[REDACTED RSA KEY]
-----END RSA PRIVATE KEY-----

closed

This time, the service accepts the password and replies with an RSA key to access the next level and closes the connection.

Level 17

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

This level wants us to find any differences between the passwords.old and passwords.new files. One of the recommended commands is diff which has the sole purpose of comparing files line by line and displaying the differences.

Let's get a diff of our two password files.

$ diff passwords.old passwords.new
42c42
< C6XNBdYOkgt5ARXESMKWWOUwBeaIQZ0Y
---
> [REDACTED LEVEL 18 PASSWORD]

The first line indicates which lines were changed. In this case a change has been identified in line 42 of both files. The lines above the --- separator are the changes made in the file that was input as the first argument (passwords.old), while the lines under the --- correspond to the second argument (password.new).

We can see the password for level 18 highlighted in green.

Level 18

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.

While ssh is primarily used for starting a user shell on a remote host, commands can actually be executed directly without starting a new shell. The SYNOPSIS of the ssh manpage shows the necessary syntax.

SYNOPSIS
       ssh   [-46AaCfGgKkMNnqsTtVvXxYy]  [-B  bind_interface]  [-b  bind_address]  [-c  cipher_spec]
           [-D [bind_address:]port] [-E log_file]  [-e  escape_char]  [-F  configfile]  [-I  pkcs11]
           [-i   identity_file]   [-J  destination]  [-L  address]  [-l  login_name]  [-m  mac_spec]
           [-O ctl_cmd] [-o option] [-P tag] [-p port] [-R address]  [-S  ctl_path]  [-W  host:port]
           [-w local_tun[:remote_tun]] destination [command [argument ...]]
       ssh [-Q query_option]

After the required destination parameter you can see the optional [command [argument ...]] parameter. Any commands provided here will be executed on the remote host.

So the command to output the password in the remote host's readme file should look like this.

$ ssh bandit18@bandit.labs.overthewire.org cat readme
                         _                     _ _ _
                        | |__   __ _ _ __   __| (_) |_
                        | '_ \ / _` | '_ \ / _` | | __|
                        | |_) | (_| | | | | (_| | | |_
                        |_.__/ \__,_|_| |_|\__,_|_|\__|


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

bandit18@bandit.labs.overthewire.org's password:
[REDACTED LEVEL 18 PASSWORD]
i !
Note

This command should be executed from your own system, not from the system hosting Bandit i.e. while logged into one of the previous levels.

I've configured my SSH client to automatically use port 2220 as required for the Bandit wargame which is why I haven't explicitly provided the port in this example. To do the same, add the following line to your ~/.ssh/config file or provide the port number via the -p flag.

Host bandit.labs.overthewire.org
    port 2220

Level 19

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.

First let's define a few terms.

TermDescriptions
Real User ID (RUID)The ID of the user which started a process
Effective User ID (EUID)The ID of the user which defines the privileges of a running process.
suid permission bitA special file permission bit besides the standard read (r), write (w), and execute (x) that allows a file to be executed as the owner of that file

For the bandit20-do permissions the suid bit can be seen as the s in the execute position of the user permissions.

$ ls -l
total 16
-rwsr-x--- 1 bandit20 bandit19 14884 Apr 10 14:23 bandit20-do

The special (suid) permission is set and the owner of the file is bandit20, thus allowing the binary access to resources available to the user bandit20.

By default, the effective user ID matches the real user ID. However, If you execute the provided binary without any arguments some usage information will be returned.

$ ./bandit20-do
Run a command as another user.
  Example: ./bandit20-do id

Note that the id command will show the real and effective user and group IDs for the current user's shell unless provided with an argument. Now, let's run that example.

$ ./bandit20-do id
uid=11019(bandit19) gid=11019(bandit19) euid=11020(bandit20) groups=11019(bandit19)

See how it differs from running the plain id command run as the bandit19 user.

$ id
uid=11019(bandit19) gid=11019(bandit19) groups=11019(bandit19)

An additional item is returned from the bandit20-do invocation, the effective user ID euid=11020(bandit20). This indicates that the commands run via bandit20-d0 should be able to access files available to the bandit20 user. Which includes the password file.

As long as we know its location which was provided as /etc/bandit_pass/bandit20 we can simply output the file. So, solving this challenge is actually quite simple. Execute a cat command with the password file as an argument via the bandit20-do binary like so:

$ ./bandit20-do cat /etc/bandit_pass/bandit20
[REDACTED BANDIT 20 PASSWORD]

Level 20

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

Useful commands 4

This challenge recommends some new commands that we haven't seen so far.

CommandDescription
screena terminal multiplexer
tmuxa terminal multiplexer
jobsdisplay the status of all jobs in the current session
bgsend a running job to the background
fgbring a backgrounded job to the foreground

These programs introduce some new concepts that are pretty important for anyone working on Linux system whether as a user or an administrator. Let's briefly go over them.

Job control

You can read more about it in the Job Control section of the Bash Reference Manual. But, to summarize, job control, is a system for suspending and resuming processes and allowing them to run in the background such that the user can continue executing other commands without waiting for previous commands to complete.

To interact with the job control system, we have a few tools including: the jobs, bg, and fg commands as well as the & character, and the Ctrl+Z keyboard shortcut which you may see referred to as the suspend character or ^Z.

We generally use two methods to run a program in the background. The first is to execute a command with an & at the end of the line.

For example

$ cat - &
[1] 1809443

The cat process has been added to the jobs list and is now in the background.

$ jobs
[1]+  Stopped                 cat -

As you can see when listing the available jobs, the command has been Stopped which indicates that is has been suspended and is no longer running. However, we can bring this process back to the foreground so that we can interact with it again using the fg command.

$ fg
cat -
hello
hello

Besides starting a command with the &, we can also use the Ctrl+Z keyboard shortcut to send an already running process to the background. Let's send the cat process to the background once again.

^Z
[1]+  Stopped                 cat -
$ jobs
[1]+  Stopped                 cat -

You can see here we've once again stopped the process running cat and returned it to the background. The ^Z indicates the pressing of Ctrl+Z. Be aware though, that backgrounding a process in this manner will also stop the process. To let it continue running in that background you must use the bg command.

$ bg %1
[1]+ cat - &

[1]+  Stopped                 cat -

Here we used what's known as a job specification or job spec to identify the job with a % followed by the ID of 1 which you may need to do if you have several backgrounded jobs. In this little example, since the cat process is awaiting user input, it's immediately stopped again after telling it to run in the background. However, a longer running process would continue until the program exits or is awaiting more user input.

Terminal multiplexing

Both tmux and screen are terminal multiplexers. Which just means they can display multiple terminals within a single window. They allow you to split these windows as needed and can allow grouping terminals, copying and pasting etc. This can all be done from the command line with these tool, which is why they're useful when working over a remote SSH session.

There are also GUI-based alternatives. Terminal emulators like Terminator, Konsole, and even Windows Terminal ship with multiplexing features, but I won't go into too much detail here. I recommend you try out whichever ones work for your system and see what you like best. Just know that only the terminal-based multiplexers like tmux and screen will enable you to use multiple terminals on a remote system without initiating an additional SSH session.

Solution

This challenge provides another setuid binary, just how Level 19 did. Checking the permissions shows that this binary also has the suid bit set with an owner of bandit21, so it should be able to read the bandit21 password file for us in some way.

$ ls -l
total 16
-rwsr-x--- 1 bandit21 bandit20 15608 Apr 10 14:23 suconnect

First off, let's check the usage information for suconnect.

$ ./suconnect
Usage: ./suconnect <portnumber>
This program will connect to the given port on localhost using TCP. If it receives the correct password from the other side, the next password is transmitted back.

For this walkthrough we'll use the job control method.

First, we need to setup a TCP listener on a high port3 for the suconnect binary to connect to. The simplest way to do this is with the netcat utility which we've used before.

Here we run netcat in listening mode and specify port 9898, but importantly we've provided the & at the end of the line to send the process to the background.

$ nc -l -p 9898 &
[1] 744373

Running the jobs command will list the active jobs which now includes the netcat command we just launched. As you can see it's still running since it's awaiting a connection.

$ jobs
[1]+  Running                 nc -l -p 9898 &

Next we need to run the suconnect binary and connect to the same port, 9898. We also execute this command as a background job.

$ ./suconnect 9898 &
[2] 913657

The jobs list now contains both of our programs running in the background.

$ jobs
[1]+  Stopped                 nc -l -p 9898
[2]-  Running                 ./suconnect 9898 &

You can see that the netcat command has been stopped, but we can start it again by bringing it into the foreground with the fg command to interact with it. Note that since the netcat job has the + indicator it will be selected by default, but we can also specify the specific job to bring to the foreground with it's job number.

Once you bring the netcat job to the foreground, the terminal will once again be awaiting input. Provide the level 20 password as the prompt informed us.

$ fg %1
nc -l -p 9898
[REDACTED LEVEL 20 PASSWORD]
Read: [REDACTED LEVEL 20 PASSWORD]
Password matches, sending next password
[REDACTED LEVEL 21 Password]
[2]+  Done                    ./suconnect 9898

As you can see, suconnect accepted the level 20 password and replied with the password for level 21. We can also see that the job for suconnect completed and exited normally by the Done response in the final line.

Level 21

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.

The prompt also recommends some useful manpages.

Cron

ManpageDescription
cron(8)system for scheduling commands
crontab(1)manage crontab files for a user
crontab(5)crontab file format and syntax

Cron is a system for executing commands at regular intervals. This is a very common task for system administrators. Whether a task involves performing regular backups of data or generate reports automatically.

Contab guru is a convenient tool to parse crontabs if you're unfamiliar with the syntax or want to verify the correctness of your own crontabs.

Solution

The prompt points us to the configuration files in /etc/cron.d.

$ ls -l
total 28
-rw-r--r-- 1 root root 123 Apr 10 14:16 clean_tmp
-rw-r--r-- 1 root root 120 Apr 10 14:23 cronjob_bandit22
-rw-r--r-- 1 root root 122 Apr 10 14:23 cronjob_bandit23
-rw-r--r-- 1 root root 120 Apr 10 14:23 cronjob_bandit24
-rw-r--r-- 1 root root 201 Apr  8  2024 e2scrub_all
-rwx------ 1 root root  52 Apr 10 14:24 otw-tmp-dir
-rw-r--r-- 1 root root 396 Jan  9  2024 sysstat

Checking the contents of the cronjob_bandit22 file reveals a specification for two cron jobs.

@reboot bandit22 /usr/bin/cronjob_bandit22.sh &> /dev/null
* * * * * bandit22 /usr/bin/cronjob_bandit22.sh &> /dev/null

The first line runs the cronjob_bandit22.sh script at boot time, and the second line runs the same script every minute.

Let's see what that script does.

#!/bin/bash
chmod 644 /tmp/t7O6lds9S0RqQh9aMcz6ShpAoZKF7fgv
cat /etc/bandit_pass/bandit22 > /tmp/t7O6lds9S0RqQh9aMcz6ShpAoZKF7fgv

According to this script, the password for bandit22 seems to be written to the file /tmp/t7O6lds9S0RqQh9aMcz6ShpAoZKF7fgv. Reading that file will reveal the password.

$ cat /tmp/t7O6lds9S0RqQh9aMcz6ShpAoZKF7fgv
[REDACTED LEVEL 22 PASSWORD]

Level 22

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.

This level once again involves Cron. You may remember, from Level 21 that there were a few other cron files in /etc/cron.d.

This time we're interested in the /etc/cron.d/cronjob_bandit22 file.

@reboot bandit23 /usr/bin/cronjob_bandit23.sh  &> /dev/null
* * * * * bandit23 /usr/bin/cronjob_bandit23.sh  &> /dev/null

Once again this crontab points to a script in /usr/bin.

1#!/bin/bash
2myname=$(whoami)
3mytarget=$(echo I am user $myname | md5sum | cut -d ' ' -f 1)
4
5echo "Copying passwordfile /etc/bandit_pass/$myname to /tmp/$mytarget"
6
7cat /etc/bandit_pass/$myname > /tmp/$mytarget

This script is quite similar to the script in Level 21 except it's now using a variable, mytarget to store the target directory. And, instead of using a hardcoded location, the mytarget is built with a series of commands.

However, be aware that the $myname variable is being populated when the script runs as the bandit23 user. So you can't use the results of whoami in the current shell for the $myname variable since that resolves to bandit22. Instead, the $myname variable should be bandit23. Replace it in the command line, like so, to calculate the correct location for the password.

echo I am user bandit23 | md5sum | cut -d ' ' -f 1

Running this series of commands will give us the value stored in $mytarget which is 8ca319486bfbbc3663ea0fbe81326349. Therefore, the final location for the password is /tmp/8ca319486bfbbc3663ea0fbe81326349. Output the file to get the password.

$ cat /tmp/8ca319486bfbbc3663ea0fbe81326349
[REDACTED BANDIT 23 PASSWORD]

Level 23

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…

This challenge is similar to the last one, but this time we won't just be reading a shell script. We'll be writing out own.

Shell scripting

We mostly breezed by all the scripts in the previous levels, but now that we're writing our own. Let's break them down a bit.

First we'll look at the anatomy of a shell script. Shell scripts are just text files. On Linux, file extensions in Linux usually4 aren't necessary, but it's common to end shell scripts with a .sh e.g. shell.sh.

The first line of any script should include a shebang to specify what kind of script it is. When a file starting with a shebang is executed, the filepath provided after the shebang points the interpreter that will be used for the script.

For example, a shell script might begin like so.

#!/bin/bash
echo Hello there!

But it's also possible to indicate other types of scripts, like Python for example.

#!/usr/bin/python
print("Hi!")

We'll only be writing shell scripts in this walkthrough though.

After the shebang, you can write any command as you'd normally write directly in the terminal. For example, here's a script making a directory and writing some text to a file in that directory.

#!/bin/sh
mkdir -p test_dir
echo "TESTING" > test_dir/test.txt
cat test_dir/test.txt

There's a lot more to Bash and shell scripting, but this should be enough to get started. If you'd like to learn more, the Bash Reference Manual is an excellent reference.

If you're planning to do any real work with Bash scripts, definitely read through the Bash scripting quirks & safety tips article by Julia Evans. It'll walk you through some of the common gotchas.

i !
Warning

Don't forget to make any script you write executable. On most systems files aren't created with the execute (x) permission. So you'll need to modify the permissions with a command like chmod +x. This will enable any user to execute the script.

Solution

Now let's write our script.

We know that bandit24 has a cronjob running. You'll find it at /etc/cron.d/cronjob_bandit24.

@reboot bandit24 /usr/bin/cronjob_bandit24.sh  &> /dev/null
* * * * * bandit24 /usr/bin/cronjob_bandit24.sh  &> /dev/null

Just like the last level, the cronjob is executing a script in /usr/bin. Let's examine it.

1#!/bin/bash
2
3myname=$(whoami)
4
5cd /var/spool/$myname/foo
6echo "Executing and deleting all scripts in /var/spool/$myname/foo:"
7for i in * .*;
8do
9 if [ "$i" != "." -a "$i" != ".." ];
10 then
11 echo "Handling $i"
12 owner="$(stat --format "%U" ./$i)"
13 if [ "${owner}" = "bandit23" ]; then
14 timeout -s 9 60 ./$i
15 fi
16 rm -f ./$i
17 fi
18done

According to the echo message, this script should be executing and deleting all the scripts in /var/spool/bandit24/foo.

So, presumably, if that foo directory is writable by bandit23 then we could write a script. Copy it into that directory, and then simply wait for the cronjob to activate and run our script. Let's check.

$ ls -l /var/spool/bandit24/
total 4
drwxrwx-wx 62 root bandit24 4096 Jun 17 13:51 foo

Note the atypical w in the "other" portion of the directory permissions. That means we'll be able to copy in any scripts we'd like.

There are plenty of different ways you could solve this, but the simplest is to send the contents of the bandit24 password file to a new file which we can read. Here's an example solution.

1#!/bin/sh
2mkdir -p /tmp/cblanken_L24
3cat /etc/bandit_pass/bandit24 > /tmp/cblanken_L24/pass.txt

Don't forget to swap in your own temporary directory name and make your script file executable with chmod.

$ chmod +x solve.sh

Then copy your script into /var/spool/bandit24/foo and wait for it to be executed.

$ cp solve.sh /var/spool/bandit24/foo/

The cronjob is triggered every minute, so you shouldn't have to wait long.

$ cat /tmp/cblanken_L24/pass.txt
[REDACTED LEVEL 24 PASSWSORD]

Level 24

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

There are a lot of possible ways to solve this, but I recommend splitting it into two parts.

  1. First we need to generate these 4 digit PINs
  2. Then we'll send them to port 30002.

Generating PINs

One convenient way to do this is with the seq command which can generate sequences of numbers.

For example

$ seq 0 10
0
1
2
3
4
5
6
7
8
9
10

You might notice though that all these single digits aren't exactly valid PINs. They should have leading zeroes up to 4 digits.

Fortunately, seq has a flag that will pad our numbers with leading zeroes just how we need using the -w flag. Here's an excerpt from the manual.

-w, --equal-width
   equalize width by padding with leading zeroes

Now we have our leading zeroes, and we just need to update the maximum argument from 10 to 9999 to get every possible 4-digit pin.

$ seq -w 0 9999
0000
0001
0002
0003
0004
--- snip ---
9995
9996
9997
9998
9999

Next let's see what we need to be sending to port 30002. If you start a connection to the port with netcat, the service will reply with a simple explanation.

$ 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.

It says here that the bandit24 password and our PIN should be sent in a single line with a space separating the two. To write this all out, it would be a bit unwieldy to do it all directly from the shell, so let's write ourselves a Bash script.

We've already gone over some of the basics, but here we'll need to introduce some new syntax. The for loop.

#!/bin/bash

# Generate PINs
for i in $(seq -w 0 9999); do
   echo "[REDACTED LEVEL 24 PASSWORD] $i"
done

Now, I want to return to something mentioned in the prompt. It reads.

You do not need to create new connections each time.

This is actually quite an important concept when writing scripts or doing any programming really. Understanding the requirements for the script you're writing and making it perform well without doing extra work or using resources that aren't strictly necessary. In this case the prompt has hinted to us that we really only need one connection to port 30002 to do all this work.

Now I want you to think about what this script will do with the addition of one line. Consider why this might be problematic.

for i in $(seq -w 0 9999); do
   echo "[REDACTED LEVEL 24 PASSWORD] $i" | nc localhost 30002
done

The problem here is that we've placed our call to netcat within our for loop. That means netcat will be creating and tearing down the connection to port 30002 for every iteration of this loop. If you try this script you'll also notice an even more glaring problem. The program actually hangs after sending only the first code. That's because netcat will await user input as long as the connection hasn't been closed and since the correct PIN wasn't sent, the service is still awaiting more input.

So we can already see this approach may cause some problems. Instead, let's generate all our numbers with our first script and then pass the output to netcat with a pipe. In this way, only one connection is required, and we can guarantee that all the PINs will be sent to the service on port 30002.

$ ./gen_pincodes.sh | nc localhost 30002

Running the script will yield the password once the correct PIN is reached.

--- snip ---
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 [REDACTED LEVEL 25 PASSWORD]

Level 25

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.

NOTE: if you’re a Windows user and typically use Powershell to ssh into bandit: Powershell is known to cause issues with the intended solution to this level. You should use command prompt instead.

We're told bandit 26 runs some other shell than bash.

There are a couple ways to discover a user's default shell in Linux. One is the /etc/passwd file. It lists all the users of the system, whether they can login, and what default shell they have. We can cat /etc/passwd and search for bandit26 to discover its shell. Here's the relevant excerpt.

$ cat /etc/passwd
--- snip ---
bandit24:x:11024:11024:bandit level 24:/home/bandit24:/bin/bash
bandit25:x:11025:11025:bandit level 25:/home/bandit25:/bin/bash
bandit26:x:11026:11026:bandit level 26:/home/bandit26:/usr/bin/showtext
bandit27:x:11027:11027:bandit level 27:/home/bandit27:/bin/bash
bandit28:x:11028:11028:bandit level 28:/home/bandit28:/bin/bash
--- snip ---

To display this information in a more readable format, we can use the lslogins command as well.

$ lslogins bandit26
Username:                           bandit26
UID:                                11026
Gecos field:                        bandit level 26
Home directory:                     /home/bandit26
Shell:                              /usr/bin/showtext
No login:                           no
Primary group:                      bandit26
GID:                                11026
Last login:                         14:03
Last terminal:                      pts/171
Last hostname:                      183.171.69.129
Hushed:                             no
Running processes:                  0

Last logs:

Note how the Shell field for bandit26 is /usr/bin/showtext as opposed to the other levels which use /usr/bin/bash as the default shell.

Running file on /usr/bin/showtext shows that it's a regular ASCII file.

$ file /usr/bin/showtext
/usr/bin/showtext: POSIX shell script, ASCII text executable

It's identified as a shell script, so let's read through it to see what it's doing.

1#!/bin/sh
2
3export TERM=linux
4
5exec more ~/text.txt
6exit 0

Pretty short, it seems to be setting the $TERM shell variable to "linux" and then replaces the current shell with a more command with the file /home/bandit26/text.txt (remember the ~ will expand to the home directory of the user). We can't directly read the contents of the text.txt file from bandit25 but since the more pager is being used it should print the contents to the screen when we try to login as the bandit26 user.

Pagers

Before we continue though, we need to discuss a bit about pagers.

You might have already used one before, but this is our first look at a challenge that strictly requires the use of one. The pager used here is more, but it's more likely you'll have used less which is the default in many Linux distributions.

Essentially pagers allow us to view text in the terminal such that it's paginated. As opposed to printing an entire file with the cat command all in one shot. A pager like more will stop writing text once the screen is filled and then allow you to continue page by page until you've reached the end of the text. The less command has some more advanced text search features and allows moving forward and backwards through a file unlike more.

In any case, I'd recommend looking through the help menu of less (just press h after opening a file to see a summary of less commands).

1 SUMMARY OF LESS COMMANDS
2
3 Commands marked with * may be preceded by a number, N.
4 Notes in parentheses indicate the behavior if N is given.
5 A key preceded by a caret indicates the Ctrl key; thus ^K is ctrl-K.
6
7 h H Display this help.
8 q :q Q :Q ZZ Exit.
9 ---------------------------------------------------------------------------
10
11 MOVING
12
13 e ^E j ^N CR * Forward one line (or N lines).
14 y ^Y k ^K ^P * Backward one line (or N lines).
15 f ^F ^V SPACE * Forward one window (or N lines).
16 b ^B ESC-v * Backward one window (or N lines).
17 z * Forward one window (and set window to N).
18 w * Backward one window (and set window to N).
19 ESC-SPACE * Forward one window, but don't stop at end-of-file.
20 d ^D * Forward one half-window (and set half-window to N).
21 u ^U * Backward one half-window (and set half-window to N).
22 ESC-) RightArrow * Right one half screen width (or N positions).
23 ESC-( LeftArrow * Left one half screen width (or N positions).
24 ESC-} ^RightArrow Right to last column displayed.
25 ESC-{ ^LeftArrow Left to first column.
26 F Forward forever; like "tail -f".
27HELP -- Press RETURN for more, or q when done

Now back to the challenge at hand.

Solution

Bandit 25 has an ssh key for bandit 26 in the home directory. We'll retrieve that first so we can ssh directly into bandit 26. But, if you try to login with this key we're greeted with the usual message followed by a closed connection.

$ ssh bandit26@bandit.labs.overthewire.org -i bandit26.key
--- snip ---
--[ More information ]--

  For more information regarding individual wargames, visit
  http://www.overthewire.org/wargames/

  For support, questions or comments, contact us on discord or IRC.

  Enjoy your stay!

  _                     _ _ _   ___   __
 | |                   | (_) | |__ \ / /
 | |__   __ _ _ __   __| |_| |_   ) / /_
 | '_ \ / _` | '_ \ / _` | | __| / / '_ \
 | |_) | (_| | | | | (_| | | |_ / /| (_) |
 |_.__/ \__,_|_| |_|\__,_|_|\__|____\___/
Connection to bandit.labs.overthewire.org closed.

The crux of this challenge relies on that fact that the paging of more is dependent upon the size of the text input. If the number of lines of text exceed the available lines on-screen then more will paginate the output, otherwise it behaves like the cat command. So, in the case above, my terminal was able to print the entire text.txt file which we can probably assume contains the ASCII art of bandit26 since that's the last thing we see and the only thing that deviates from the normal welcome message from previous levels.

So, that means it should be possible to trigger the pager by shrinking the height of our terminal to less than 6 lines high.

level 26 more truncation

Since the output is paginated, the connection doesn't immediately end because the more program is waiting for us to page through the remaining content. However, more has some other features that can help us escape this prompt. If you press either the h or ? key while on this prompt, more will list out a summary of commands. One of which includes the option to execute commands in a subshell. level 26 more help

This is promising and there may be a way to execute commands directly via more to retrieve the bandit26 password and to solve for the next one. However, it's quite laborious since more won't yield any output for any commands we run, so we would have to write to various temporary files to manage it.

Instead, I'm going to show you a neat trick with Vi. If you look carefully there's an option just below the !<cmd> option. The v command which will open vi.

Vi and Vim

Vi is a powerful text editor that is a common default on Linux systems. You may have also heard of its descendant Vim which has even more capabilities.

I won't be going into much detail on the features of Vim here. Just enough to demonstrate the solution, but I highly recommend working through the beginner section of the Vim Guide by The Valuable Dev if you're interested in discovering its power.

From here on out I'll refer to Vi and Vim collectively as Vim for simplicity. Just be aware they are in fact distinct. Even if we don't run into any distinguishing features in this walkthrough.

Firstly, Vim is not a typical text editor like most folks are familiar with like Windows' Notepad and Apple's TextEdit. Vim exists entirely in the terminal and was designed to function entirely without a pointing device like a mouse. Vim is what's known as a modal editor. Instead of always interpreting input from the keyboard as text to be written, Vim has several different modes:

  • normal mode (this is the default mode usually used for navigating your files)
  • insert mode (this is like your typical text editors)
  • visual mode (kind of like when you select text with your mouse in other editors)
  • command mode

The main thing you need to know for this challenge is that the colon : is used to enter command mode.

The first command we'll use is :edit. This command will open a file so let's use it to grab the bandit26 password.

:edit /etc/bandit_pass/bandit26
[REDACTED BANDIT 26 PASSWORD]
~
~
~
~
~
"/etc/bandit_pass/bandit26" [readonly] 1L, 33B           1,1           All

While this is a good first step, we still need some way access a shell to complete the next level.

Fortunately Vim can also open a shell with the :shell command. You can read more about it with the :help command. The full command is :help :shell.

Vim :shell help

Unfortunately, by default, it uses the same shell that spawned the process. Use the set command to change the mentioned shell variable like so.

:set shell=/bin/bash

At last, we can open a bash shell with the :shell command.

Vim shell

Level 26

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

  • If we look in the home directory of bandit27 we'll find a setuid script called bandit27-do. When we run it we get the following output.

This level is essentially a duplicate of Level 19 since the real difficulty is in gaining a shell via more as shown in Level 25.

The suid binary bandit27-do is available in the home directory.

$ ./bandit27-do
Run a command as another user.
  Example: ./bandit27-do id

Print the bandit27 password like so.

$ ./bandit27-do cat /etc/bandit_pass/bandit27
[REDACTED BANDIT 27 PASSWORD]

Level 27

There is a git repository at ssh://bandit27-git@localhost/home/bandit27-git/ repo via the port 2220. 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.

Git

Git is a software version control system. As of this writing Git is probably the most popular version control system for software and has been for a while. At any rate, you'll likely come across it while doing any kind of IT administration or security work.

The best resource to learn about Git is probably the reference documentation and the Pro Git book which is freely available online, but it isn't necessary to know a lot of details for these challenges.

To start you really only need to know a few commands.

Git CommandDescription
git-clone(1)download a repository from a remote system
git-checkout(1)interact with the repository at a certain commit
git-log(1)list commits
git-branch(1)list branches

The repository mentioned in the prompt can be cloned with the following command.

$ git clone ssh://bandit27-git@localhost:2220/home/bandit27-git/repo

After entering the level password, the git clone should complete with some progress indicators.

remote: Enumerating objects: 3, done.
remote: Counting objects: 100% (3/3), done.
remote: Compressing objects: 100% (2/2), done.
Receiving objects: 100% (3/3), done.
remote: Total 3 (delta 0), reused 0 (delta 0), pack-reused 0

And, there should now be a directory named repo in the current directory.

The repository contains a single README file which contains the password.

$ cat README
The password to the next level is: [REDACTED LEVEL 28 PASSWORD]

Level 28

There is a git repository at ssh://bandit28-git@localhost/home/bandit28-git/ repo via the port 2220. 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.

Just as before, clone the repository.

$ git clone ssh://bandit28-git@localhost:2220/home/bandit28-git/repo

This time, the README file contains a password that has been redacted.

# Bandit Notes

Some notes for level29 of bandit.

## credentials

- username: bandit29
- password: xxxxxxxxxx

Examine the change history with the git log command.

commit 674690a00a0056ab96048f7317b9ec20c057c06b (HEAD -> master, origin/master, origin/HEAD)
Author: Morla Porla <morla@overthewire.org>
Date:   Thu Apr 10 14:23:19 2025 +0000

    fix info leak

commit fb0df1358b1ff146f581651a84bae622353a71c0
Author: Morla Porla <morla@overthewire.org>
Date:   Thu Apr 10 14:23:19 2025 +0000

    add missing data

commit a5fdc97aae2c6f0e6c1e722877a100f24bcaaa46
Author: Ben Dover <noone@overthewire.org>
Date:   Thu Apr 10 14:23:19 2025 +0000

    initial commit of README.md

There's a reference to an "info leak" from the previous commit. Check out the previous commit with the git checkout command.

$ git checkout fb0df1358b1ff146f581651a84bae622353a71c0

This will change the repository to its original state at the specified commit hash.

At this point in the repositories history, the bnadit29 password had not yet been redacted in the README file.

# Bandit Notes

Some notes for level29 of bandit.

## credentials

- username: bandit29
- password: [REDACTED LEVEL 29 PASSWORD]

Level 29

There is a git repository at ssh://bandit29-git@localhost/home/bandit29-git/ repo via the port 2220. 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.

Clone the repository.

$ git clone ssh://bandit29-git@localhost:2220/home/bandit29-git/repo

Similar to Level 28 there is a README file.

# Bandit Notes

Some notes for bandit30 of bandit.

## credentials

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

Looking at the log again doesn't turn up anything interesting.

commit 3910630172946c9ffb75842922e394b772c672bd (HEAD)
Author: Ben Dover <noone@overthewire.org>
Date:   Thu Apr 10 14:23:21 2025 +0000

    add gif2ascii

commit 3b8b91fc3c48f1a19d05670fd45d3e3f2621fcfa (origin/master, origin/HEAD, master)
Author: Ben Dover <noone@overthewire.org>
Date:   Thu Apr 10 14:23:21 2025 +0000

    fix username

commit 8d2ffeb5e45f87d0abb028aa796e3ebb63c5579c
Author: Ben Dover <noone@overthewire.org>
Date:   Thu Apr 10 14:23:21 2025 +0000

    initial commit of README.md

This is where some knowledge of Git's branching is useful. By default, the git log command will only display commits from current branch. In this case the master branch.

Use the git branch command to show the active branch.

$ git branch
* master

Additionally using the --all flag will list all the local and remote branches.

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

You can see here that there are two additional branches mentioned. The dev and sploits-dev branches. Let's check them out.

$ git checkout dev
Switched to branch 'dev'
Your branch is up to date with 'origin/dev'.
commit a97d0dbf8fd910ead6fcf648829ff55c1a629c8e (HEAD -> dev, origin/dev)
Author: Morla Porla <morla@overthewire.org>
Date:   Thu Apr 10 14:23:21 2025 +0000

    add data needed for development

commit 3910630172946c9ffb75842922e394b772c672bd
Author: Ben Dover <noone@overthewire.org>
Date:   Thu Apr 10 14:23:21 2025 +0000

    add gif2ascii

commit 3b8b91fc3c48f1a19d05670fd45d3e3f2621fcfa (origin/master, origin/HEAD, master)
Author: Ben Dover <noone@overthewire.org>
Date:   Thu Apr 10 14:23:21 2025 +0000

    fix username

commit 8d2ffeb5e45f87d0abb028aa796e3ebb63c5579c
Author: Ben Dover <noone@overthewire.org>
Date:   Thu Apr 10 14:23:21 2025 +0000

    initial commit of README.md

The git show command will print the changes made by the last commit, but you can alternatively, just read the README file while on the dev branch since the last commit contains the password.

commit a97d0dbf8fd910ead6fcf648829ff55c1a629c8e (HEAD -> dev, origin/dev)
Author: Morla Porla <morla@overthewire.org>
Date:   Thu Apr 10 14:23:21 2025 +0000

    add data needed for development

diff --git a/README.md b/README.md
index 1af21d3..bc6ad3d 100644
--- a/README.md
+++ b/README.md
@@ -4,5 +4,5 @@ Some notes for bandit30 of bandit.
 ## credentials

 - username: bandit30
-- password: <no passwords in production!>
+- password: [REDACTED BANDIT 30 PASSWORD]

The password only appears in the dev branch because it was only added in the dev branch, but never actually merged into master.

Level 30

There is a git repository at ssh://bandit30-git@localhost/home/bandit30-git/ repo via the port 2220. 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.

Clone the repository.

$ git clone ssh://bandit30-git@localhost:2220/home/bandit30-git/repo

If we run git log --all there doesn't appear to be anything unusual.

This time we'll need to look at something called Tags and a couple new commands.

Git CommandDescription
git-tag(1)create, list, delete or verify tags
git-show(1)show various git object i.e. blobs, commits, tags, and trees

Here we'll list the existing tags for the repo.

$ git tag
secret

Usually tags are used to mark specific commits, but if you try to git checkout this secret tag you'll get an error saying the reference is not a tree. This means the tag is just a BLOB (Binary Large OBject). You can read more about trees and blobs here.

To show the contents of a blob we can use the git show command which reveals the next password.

$ git show secret
[REDACTED LEVEL 31 PASSWORD]

Level 31

There is a git repository at ssh://bandit31-git@localhost/home/bandit31-git/repo via the port 2220. 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.

Clone the repo.

The README.md tells us to push a file to the remote with some file details.

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

We'll need a couple new commands to do that.

Git CommandDescription
git-status(1)display the status of the working tree
git-add(1)add files to the staging area
git-push(1)create a new commit based on the current changes and add a commit message

Next we need to create the key.txt file.

$ echo "May I come in?" > key.txt

Then add the key.txt to our index.

$ git add key.txt
The following paths are ignored by one of your .gitignore files:
key.txt
hint: Use -f if you really want to add them.
hint: Turn this message off by running
hint: "git config advice.addIgnoredFile false"

You can read more about the mentioned .gitignore files here, but in this case we can just use the recommended -f flag to add the file and try again.

$ git add -f key.txt

We can verify that the key.txt file has been added to the index with git status.

$ git status
On branch master
Your branch is up to date with 'origin/master'.

Changes to be committed:
  (use "git restore --staged <file>..." to unstage)
        new file:   key.txt

Now we need to commit this change with the git commit command. This applies our changes to the master branch.

$ git commit -m "Add key.txt"
[master a1f8c06] Add key.txt
 1 file changed, 1 insertion(+)
 create mode 100644 key.txt

And finally, we can use the git push command to push our new commit to the remote repository.

$ git push origin master
i !
Warning

Watch out! If the content of key.txt does not match the prescribed text exactly you may receive an error like this.

Enumerating objects: 4, done.
Counting objects: 100% (4/4), done.
Delta compression using up to 2 threads
Compressing objects: 100% (2/2), done.
Writing objects: 100% (3/3), 323 bytes | 323.00 KiB/s, done.
Total 3 (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: Wrong!
remote:
remote: .oOo.oOo.oOo.oOo.oOo.oOo.oOo.oOo.oOo.oOo.
remote:
To ssh://localhost:2220/home/bandit31-git/repo
 ! [remote rejected] master -> master (pre-receive hook declined)
error: failed to push some refs to 'ssh://localhost:2220/home/bandit31-git/repo'

If everything is in order, you'll get some feedback from the git server which will include the next password.

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: [REDACTED LEVEL32 PASSWORD]
remote:
remote: .oOo.oOo.oOo.oOo.oOo.oOo.oOo.oOo.oOo.oOo.
remote:
To ssh://localhost:2220/home/bandit31-git/repo
 ! [remote rejected] master -> master (pre-receive hook declined)
error: failed to push some refs to 'ssh://localhost:2220/home/bandit31-git/repo'

Level 32

After all this git stuff, it’s time for another escape. Good luck!

After logging in to bandit32 you'll find yourself in an "UPPERCASE" shell, all entered text appears to be converted to uppercase.

WELCOME TO THE UPPERCASE SHELL
>> cat /etc/passwd
sh: 1: CAT: Permission denied
>>

It's possible to reference some environment variables such as $PATH, $PWD, $SHELL, and $USER.

>> $PATH
sh: 1: /usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin:/usr/games:/usr/local/games:/snap/bin: not found
>> $PWD
sh: 1: /home/bandit32: Permission denied
>> $USER
sh: 1: bandit32: Permission denied

You see, just accessing the variable, the Uppercase Shell tries to execute the contents seemingly with the sh shell. This is great, but not enough.

You have to know about a special shell variable, $0. The $0 shell variable expands to the name of the shell or shell script being executed which in this case is sh.

Therefore, providing the $0 variable will execute the sh command dropping us into a normal shell.

>> $0
$

From here's it's just a matter of printing the next password from the file as we've done many times before.

$ cat /etc/bandit_pass/bandit33
[REDACTED LEVEL33 PASSWORD]

The End

Congratulations! You made it! I hope you had fun working through all these challenges. There were a couple tricky ones that I didn't get them all myself on the first time around.

If this wargame has piqued your interest I've also written a couple other walkthroughs.

If the OverTheWire wargames aren't quite your cup of tea though, then I highly recommend the picoCTF platform run by Carnegie Mellon University. They host an annual CTF event, but challenges from previous years are all available to practice. The picoCTF challenges cover a wide range of topics and levels of difficulty so that anyone can find something interesting.

Finally, a big thanks to the folks at OverTheWire for maintaining the infrastructure for the OverTheWire wargames. They're such a great resource for folks first dipping their toes into cybersecurity and CTFs.

Happy hacking!

  1. Just like almost every cryptographic system, it's possible for SSH to be used incorrectly, thus compromising it's security.

  2. SSL and TLS both commonly refer to Transport Layer Security which is a protocol designed for secure communications over computer networks. SSL (Secure Sockets Layer) is a legacy term referring to the precursor to TLS, but the terms are often used interchangeably. However, modern applications should not be communicating via SSL since its final version (SSL 3.0) was deprecated in 2015.

  3. Ports 1-1024 are privileged ports and can't be used without root permissions. That's because commonly known services are hosted on these ports and should ideally only be accessible by a system administrator. Anything from 1025 to 65535 should be fine though. If by chance you receive an error message of nc: Address already in use, then try another.

  4. Some programs, in particular archiving and compression utilities like tar, gzip, and bzip2 will refuse to operate on files without an appropriate file extension, but most programs will instead read the first few bytes of a file to determine it's type. Commonly known as magic numbers