Beware, Bandits!

As I mentioned in an earlier post, I recently moved back into a more technical role after spending some time away, doing management things.

Initially I hesitated to make my writeups public. The motd of every level specifically states the following:

Please play nice:

--- SNIP ---

  • again, DONT POST SPOILERS! This includes writeups of your solution on your blog or website!

I understand that sentiment, and in general I would be more than willing to adhere to their wish. However .. there are tons of writeups out there already, about this and other wargames designed by OverTheWire. Including ones with full passwords in them.

I’m quite aware that “Others have ignored your wishes already, so I might as well” is not exactly close to anything resembling a convincing argument. But in my assessment I feel there is no harm done by my writeup. I made sure that all passwords have been redacted.


Level 0

Using the supplied credentials I simply ssh’d into the machine and ran a cat README.md to get the password for the next level.

Level 1

The home directory of the user bandit1 contains a file, -. Simply trying to cat it won’t work, because the shell will interpret the hypen as a special character. Instead it’s necessary to specify the absolute or relative path to the file. In which case escaping the hyphen isn’t even necessary:

bandit1@bandit:~$ cat ./-
<the password>
bandit1@bandit:~$ cat ./\-
<the password>

Level 2

The home directory of the user bandit2 contains a file, spaces in this file name. In order to access it, all spaces need to be escaped:

cat spaces\ in\ this\ filename

In theory you don’t even have to know about this, since cat <tab> triggers autocompletion, escaping the whitespace for you.

Level 3

The home directory of the user bandit3 contains a directory, inhere, with a hidden file in it, hidden - cat inhere/.hidden thus suffices.

I have to admit, for a second I was wondering why this wasn’t level 1 or 2. But then I remembered that the concept of hidden files was a bit weird to me when I started out using Linux, so it makes sense.

Level 4

The home directory of the user bandit4 contains a directory, inhere, which contains ten files:

-rw-r----- 1 bandit5 bandit4 33 May  7  2020 -file00
-rw-r----- 1 bandit5 bandit4 33 May  7  2020 -file01
-rw-r----- 1 bandit5 bandit4 33 May  7  2020 -file02
-rw-r----- 1 bandit5 bandit4 33 May  7  2020 -file03
-rw-r----- 1 bandit5 bandit4 33 May  7  2020 -file04
-rw-r----- 1 bandit5 bandit4 33 May  7  2020 -file05
-rw-r----- 1 bandit5 bandit4 33 May  7  2020 -file06
-rw-r----- 1 bandit5 bandit4 33 May  7  2020 -file07
-rw-r----- 1 bandit5 bandit4 33 May  7  2020 -file08
-rw-r----- 1 bandit5 bandit4 33 May  7  2020 -file09

Only one of them contains the password, some of the others contain characters that might mess up your terminal - something that the authors specifically mention. Oddly enough my terminal emulator seems to have been able to handle it, because the following command to find the password for the next level didn’t cause any issues:

for file in $(ls inhere); do cat -- inhere/$file; echo "\n"; done

Level 5

The home directory of the user bandit5 contains a directory, inhere, which contains a number of further directories:

drwxr-x--- 2 root bandit5 4096 May  7  2020 maybehere00
drwxr-x--- 2 root bandit5 4096 May  7  2020 maybehere01
drwxr-x--- 2 root bandit5 4096 May  7  2020 maybehere02
drwxr-x--- 2 root bandit5 4096 May  7  2020 maybehere03
drwxr-x--- 2 root bandit5 4096 May  7  2020 maybehere04
... ... ... ... ...
drwxr-x--- 2 root bandit5 4096 May  7  2020 maybehere17
drwxr-x--- 2 root bandit5 4096 May  7  2020 maybehere18
drwxr-x--- 2 root bandit5 4096 May  7  2020 maybehere19

Fortunately, the file that contains the password for the next level has some defined characteristics:

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

Find to the rescue: find inhere/ -size 1033c. Find is a bit weird in that regard, because per default it looks for 512-byte blocks instead of bytes, which is why the suffix c needs to be appended.

Fortunately this is enough to find the file containing the password for the lext level, but if necessary the command to search for all of the criteria would be:

find inhere/ -size 1033c ! -executable

Okay, that’s not entirely true, since it’s missing out the part about being human-readable. I had no idea about how to search for that, and the manpage wasn’t helpful either.

Level 6

There are no files in the home directory of the user bandit6, but the website says:

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

Once again, find to the rescue:

find / -size 33c -user bandit7 -group bandit6 2> /dev/null

It’s not mandatory to redirect stderr to /dev/null, but you will receive a ton of permission-related errors if you don’t.

Level 7

The home directory for the user bandit7 contains a file data.txt which is quite long:

bandit7@bandit:~$ wc -l data.txt 
98567 data.txt

The website says:

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

Well, grep it is: cat data.txt | grep millionth. (If anyone ever gives you shit or talks about the ‘useless use of cat’-award, ignore them. They are bitter people. Either that or 13 and active on IRC.)

Level 8

The home directory of the user bandit8 contains a file data.txt, which is once again quite long. The webpage says:

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

That’s a classic case for either sort -u or uniq -u. However, I don’t entirely understand my solution. My guess would have been cat data.txt | uniq -u, or uniq -u data.txt. But that didn’t work.

On the other hand, sort data.txt | uniq -u did. And no matter how hard I think about it or what manpages I read, I’m not entirely sure why that’s the case. If someone happens to know, please hit me up via Twitter or mail!

Level 9

The home directory of the user bandit9 contains a file, data.txt, which is filled with all kinds of garbage. The website says:

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.

In theory, grep has a switch (-a) in order to process a binary file as text, but despite finding something, the output would still be pretty unreadable.

xxd allows for making a hexdump, which can in turn easily be processed by grep in a much more readable form:

bandit9@bandit:~$ xxd data.txt | grep "=="
00000460: 8588 c83d 3d3d 3d3d 3d3d 3d3d 3d20 7468  ...========== th
000014a0: 7cf0 86e1 d9f7 57c1 78bc f8c7 953d 3d3d  |.....W.x....===
000014b0: 3d3d 3d3d 3d3d 3d20 7061 7373 776f 7264  ======= password
00001b40: 5a29 3d3d 3d3d 3d3d 3d3d 3d3d 2069 73cc  Z)========== is.
00003f30: 2ee2 e131 b38d 0f48 b22c 7fc8 8226 3d3d  ...1...H.,...&==
00003f40: 3d3d 3d3d 3d3d 3d3d 2074 7275 4b4c 646a  ======== truKLdj

The last line shows what can be reasonably assumed to be part of the password for the next level. Which can be used to feed into grep once more: grep -a 'truKLdj' data.txt.

Bonus: Searching for all strings in the file, via strings, also shows you the password. But that’s just luck.

Level 10

The home directory for the user bandit10 contains a file, data.txt, with what looks like to be a string that has been encoded with base64 - base64 -d data.txt confirms that suspicion.

Level 11

The home directory for the user bandit11 contains a file, data.txt, with the following string in it:

Gur cnffjbeq vf 5Gr8L4qetPEsPk8htqjhRK8XSP6x2RHh

I know next to nothing about cryptography and cryptanalysis. But that reeks of ROT13 - and I have absolutely no idea how to deal with it in a shell.

The websites mentions the following commands as potentially being helpful for solving the level: grep, sort, uniq, strings, base64, tr, tar, gzip, bzip2, xxd.

After consulting multiple manpages I figured tr to be a good candidate. But my first attempt failed:

bandit11@bandit:~$ tr data.txt a-zc-a
tr: range-endpoints of 'c-a' are in reverse collating sequence order

Aside from the error that I caused by trying to start over at a after z, I had an error in my thinking. It’s ROT13 after all, meaning that my math was wrong. An a doesn’t turn into a c, it turns into an n.

bandit11@bandit:~$ cat data.txt | tr [a-n] [n-z]
Gur p]ssworq vs 5Gr8L4qrtPEsPx8utqwuRK8XSP6x2RHu

Better. But obviously not the .. p]ssworq. I also only now noticed, since I’m a sloppy reader, that the contained both uppercase and lowercase. And since this applies to the password as well I couldn’t simply convert everything to upper- or lowercase.

I have to admit that after another hour of tinkering and trying to understand how tr does substitution I looked for a solution on the Internet.

Whatever I tried, I always failed at translating the letters of the alphabet that come after m. I eventually got to a point of frustration over some obviously minor issue that I ended up writing a Python-script that manually replaced each character based on the value I assigned to it in a dictionary. Yes, I really manually created a dictionary of the alphabet. It’s as dumb as it sounds.

In the end it would have been a lot easier:

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

Level 12

The home directory for the user bandit12 contains a file, data.txt, which looks like it’s a hexdump of something. The website says:

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

I quickly found the reason for their recommendation to use /tmp: You can’t write into the home directory (something I would have known at this point if I would have bothered to actually read the motd ..). So I copied the file into my own directory under /tmp and proceeded to use xxd: xxd data.txt > 1tmp.

I looked at the first few bytes of the newly created file to find out what type of file it actually is. In that case the content was 1F8B08, which means it’s a GZIP archive file. Right about then I realised that xxd has the switch -r, allowing for binary output. Oh well.

Because gunzip is quite picky I had to add the appropriate suffix, .gz, and .. okay, to be quite honest, the rest of the level is pretty boring. You have to gunzip, bunzip2 and tar xf a bunch of times. In one line the solution is:

xxd -r data.txt > 1.gz; gunzip 1.gz; bunzip2 1; mv 1.out 1.out.gz; gunzip
1.out.gz; tar xf 1.out; tar xf data5.bin; bunzip2 data6.bin; tar xf
data6.bin.out; mv data8.bin data8.bin.gz; gunzip data8.bin.gz; cat data8.bin

Sidenote: Out of curiosity I took a peek into the folder /tmp/myname123 after I was done. It contained the solution because people were both lazy and didn’t bother to clean up afterwards.

Level 13

The website says about this level:

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

The home directory for the user bandit13 contains a file named sshkey.private, which can be used to log in as bandit14:

ssh -i sshkey.private bandit14@localhost 'cat /etc/bandit_pass/bandit14'

Level 14

The website says about this level:

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

There are a lot of ways how to do that, for example via telnet or via nc:

telnet localhost 30000

.. paste the password, hit enter & you will be presented with the password for the next level.

Level 15

The website says about this level:

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

Among the commands that might be needed to solve this level was s_client, and I immediately regretted some of my life choices. But alas ..

echo <password of the current level> | openssl s_client -connect localhost:30001

I got a successful connection, but no password. Which was confusing, since I was doing exactly what the level asked of me. On closer inspection I noticed that I wasn’t getting any response at all. Turns out that there was a reason for my loathing of s_client.

The default behaviour is to no retrieve / show the response it would get. For that one needs to add -ign_eof, which was exactly what was needed to get the password for the next level:

echo <password of the current level> | openssl s_client -connect localhost:30001 -ign_eof

Level 16

The website says about this level:

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

This means that this level consists of three different steps:

  • Finding which ports are listening for requests between 31200 and 32000
  • Find out which of the listening ports are listening for TLS-requests
  • Connect to the port listening for TLS-requests

There were five ports listening for requests within the specified range:

bandit16@bandit:~$ nmap localhost -p 31000-32000

Starting Nmap 7.40 ( https://nmap.org ) at 2022-01-21 17:19 CET
Nmap scan report for localhost (127.0.0.1)
Host is up (0.00022s latency).
Not shown: 996 closed ports
PORT      STATE SERVICE
31046/tcp open  unknown
31518/tcp open  unknown
31691/tcp open  unknown
31790/tcp open  unknown
31960/tcp open  unknown

I admittedly just went through them manually, because the it would have taken longer to write the for-loop instead of doing that - it was the server listening on port 31790 that would ultimately tell me the private key (not a password) for the next level:

echo <password of the current level> | openssl s_client -connect
localhost:31790 -ign_eof

Level 17

The home directory for bandit17 contains two files, passwords.old and passwords.new. According to the website, the password for the next level is in passwords.new, and is the only thing that has been changed between passwords.old and passwords.new. Alright then: diff passwords.old passwords.new.

Level 18

You can’t log into level 18 with the password, because, according to the website:

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.

Luckily, you don’t need a full shell:

ssh bandit18@bandit.overthewire.org 'cat ~/readme'

Level 19

The home directory for bandit19 contains a setuid-file, bandit20-do, which needs to be used to get to the password for the next level:

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

Level 20

The home directory for bandit20 contains a setuid-file, suconnect. The webpage contains the following explanation:

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

This description confused me for a moment. Eventually I realized that the challenge wanted me to create a service that listens on some port, replying with the password for the current level to every requet it receives.

ncat -lp 38000 -c 'echo <current password>' --keep-open

Connecting to the service with the given binary was a success:

bandit20@bandit:~$ ./suconnect 38000                                                                                                                  
Read: <current password>
Password matches, sending next password

I mean, technically it counted as a success. Practically I still didn’t get the password. I realised that I by supplying the echo-command I basically ‘clogged’ stdout. Piping the password into nc was the better option:

echo '<current password>' | nc -lp 38000

After connecting with the setuid-binary the password was printed.

Level 21

The website says about this level:

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.

Ha, I can just use crontab -l, that’s easy .. except that’s prohibited by the system. Dang. Instead I took a look at the content of /etc/cron.d:

bandit21@bandit:~$ ls -l /etc/cron.d
total 24
-rw-r--r-- 1 root root  62 May 14  2020 cronjob_bandit15_root
-rw-r--r-- 1 root root  62 Jul 11  2020 cronjob_bandit17_root
-rw-r--r-- 1 root root 120 May  7  2020 cronjob_bandit22
-rw-r--r-- 1 root root 122 May  7  2020 cronjob_bandit23
-rw-r--r-- 1 root root 120 May 14  2020 cronjob_bandit24
-rw-r--r-- 1 root root  62 May 14  2020 cronjob_bandit25_root

Since this is level 21, I suspected cronjob_bandit22 to be of interest:

bandit21@bandit:~$ cat /usr/bin/cronjob_bandit22.sh 
#!/bin/bash
chmod 644 /tmp/t7O6lds9S0RqQh9aMcz6ShpAoZKF7fgv
cat /etc/bandit_pass/bandit22 > /tmp/t7O6lds9S0RqQh9aMcz6ShpAoZKF7fgv

That suspicion proved to be correct, /tmp/t7O6lds9S0RqQh9aMcz6ShpAoZKF7fgv contained the password for the next level.

Level 22

According to the website, level 22 was about cronjobs once more:

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.

So the process was similar to the last one, beginning with looking at /etc/cron.d ..

bandit22@bandit:~$ ls -l /etc/cron.d
total 24
-rw-r--r-- 1 root root  62 May 14  2020 cronjob_bandit15_root
-rw-r--r-- 1 root root  62 Jul 11  2020 cronjob_bandit17_root
-rw-r--r-- 1 root root 120 May  7  2020 cronjob_bandit22
-rw-r--r-- 1 root root 122 May  7  2020 cronjob_bandit23
-rw-r--r-- 1 root root 120 May 14  2020 cronjob_bandit24
-rw-r--r-- 1 root root  62 May 14  2020 cronjob_bandit25_root

.. followed by looking at cronjob_bandit23, since this is the 22nd level:

bandit22@bandit:~$ cat /etc/cron.d/cronjob_bandit23 
@reboot bandit23 /usr/bin/cronjob_bandit23.sh  &> /dev/null
* * * * * bandit23 /usr/bin/cronjob_bandit23.sh  &> /dev/null

The content of usr/bin/cronjob_bandit23.sh is:

#!/bin/bash

myname=$(whoami)
mytarget=$(echo I am user $myname | md5sum | cut -d ' ' -f 1)

echo "Copying passwordfile /etc/bandit_pass/$myname to /tmp/$mytarget"

cat /etc/bandit_pass/$myname > /tmp/$mytarget

The script

  • Evaluates the username of the user executing the script
  • Generates the content of a variable called mytarget by echoing a sentence containing the current username, generating the md5sum of it and passing that on to cat, replacing the standard delimiter with a whitespace and only printing out the first section
  • Inserts the content of /etc/bandit_pass/$myname into /tmp/$mytarget.

Assuming that this cronjob is executed as the user bandit23 that would mean that there was a file with their password somewhere in /tmp. So I tried the following:

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

This proved to be the correct approach, the password for the next level is located in /tmp/8ca319486bfbbc3663ea0fbe81326349.

Level 23

The website says about this level:

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…

I’m really happy about the second note, because that sure as hell would have happened to me and caused utter confusion. Once more, a look at /etc/cron.d:

bandit23@bandit:~$ ls -l /etc/cron.d
total 24
-rw-r--r-- 1 root root  62 May 14  2020 cronjob_bandit15_root
-rw-r--r-- 1 root root  62 Jul 11  2020 cronjob_bandit17_root
-rw-r--r-- 1 root root 120 May  7  2020 cronjob_bandit22
-rw-r--r-- 1 root root 122 May  7  2020 cronjob_bandit23
-rw-r--r-- 1 root root 120 May 14  2020 cronjob_bandit24
-rw-r--r-- 1 root root  62 May 14  2020 cronjob_bandit25_root

And a look into cronjob_bandit24 despite me being relatively sure what I was going to find there:

bandit23@bandit:~$ cat /etc/cron.d/cronjob_bandit24
@reboot bandit24 /usr/bin/cronjob_bandit24.sh &> /dev/null
* * * * * bandit24 /usr/bin/cronjob_bandit24.sh &> /dev/null

The content of /usr/bin/cronjob_bandit24.sh is:

#!/bin/bash

myname=$(whoami)

cd /var/spool/$myname
echo "Executing and deleting all scripts in /var/spool/$myname:"
for i in * .*;
do
    if [ "$i" != "." -a "$i" != ".." ];
    then
        echo "Handling $i"
        owner="$(stat --format "%U" ./$i)"
        if [ "${owner}" = "bandit23" ]; then
            timeout -s 9 60 ./$i
        fi
        rm -f ./$i
    fi
done

The script

  • Determines the username of the user executing the script via whoami, stores that value in myname
  • Changes into the directory /var/spool/$myname
  • Iterates through all files in the directory
  • If the file is neither . nor .. the owner of the crrent file is evaluated and stored in owner.
  • If the owner of the current file is bandit23 the file is executed, but automatically killed by sending the SIGKILL-signal after 60 seconds
  • If the owner of the current file is not bandit23 the file is removed, without execution.

Luckily, /var/spool/bandit24 is world-writable, so I can save a script there that is then run by a cronjob with the privileges of bandit24. Since I know by now that all passwords are stored in /etc/bandit_pass I could thus get it to cat the file for bandit24. So I did the following:

mkdir /tmp/mypass
chmod 777 /tmp/mypass
echo 'cat /etc/bandit_pass/bandit24 /tmp/mypass/password.txt' > /var/spool/bandit24/mypass.sh
chmod 777 /var/spool/bandit24/mypass.sh
chown bandit23:bandit23 /var/spool/bandit24/mypass.sh

After, at worst, a minute of waiting, the password for the next level will be found in /tmp/mypass/password.txt.

Level 24

The website says about this level:

A daemon is listening on port 30002 and will give you the password for bandit25
if given the password for bandit24 and a secret numeric 4-digit pincode. There
is no way to retrieve the pincode except by going through all of the 10000
combinations, called brute-forcing.

My first attempt looked like this:

#!/bin/bash

CURRENT_PASSWORD='<current password>'

for i in {1000..9999}; do
    echo "$CURRENT_PASSWORD$i" | nc localhost 30002
done

I was greeted by this response:

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.  Fail!
You did not supply enough data. Try again.

So I made two mistakes here, one being the wrong format for my attempted string, the other one not exiting the session after submitting the credentials. So I made some minor changes:

#!/bin/bash

CURRENT_PASSWORD='<current password>'

for i in {1000..9999}; do
    echo "$CURRENT_PASSWORD $i" | timeout 1 nc localhost 30002
done

This eventually netted me the correct password for the next level. However: This is quite slow. To check all possible combinations, ignoring 0000 (which I checked for manually), this approach would take nearly three hours. It’s also quite prone to errors, there’s no guarantee that the connection and passing of content will succeed, despite it likely being the case. So in a real-world scenario where brute force is the only way, I’d go for a much more elaborate solution.

Level 25

The website for this level says:

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.

The quickest way to find out about the shell of a given user is /etc/passwd:

bandit25@bandit:~$ cat /etc/passwd | grep bandit26
bandit26:x:11026:11026:bandit level 26:/home/bandit26:/usr/bin/showtext

/usr/bin/showtext turns out to be a shellscript:

bandit25@bandit:~$ file /usr/bin/showtext 
/usr/bin/showtext: POSIX shell script, ASCII text executable

The content is short:

#!/bin/sh

export TERM=linux

more ~/text.txt
exit 0

I wasn’t entirely sure what the environment variable TERM was responsible for, so I looked it up:

The environment variable TERM contains a identifier for the text window’s capabilities. You can get a detailed list of these cababilities by using the ‘infocmp’ command, using ‘man 5 terminfo’ as a reference.

I thought about this long and hard, trying to come up with a way to exploit that. When that didn’t bare any fruits I thought about how to exploit the more-command. I had all kinds of ideas, but none really worked - and I would have been stuck there for another couple of hours if it weren’t for one accidentals ls:

bandit25@bandit:~$ ls
bandit26.sshkey

.. there was an ssh-key in my home directory all along.

Remember kids, always use your brain first before .. well, using your brain for pondering about solutions that entirely miss the problem you were given in the first place. Still, I wasn’t unsure how to proceed.

At this point I was relatively sure that the environment variable wasn’t the important point, so I took a look at the manpage for more.

more is a filter for paging through text one screenful at a time.  This version
is especially primitive.  Users should realize that less(1) provides more(1)
emulation plus extensive enhancements.

What caught my eye was “a filter for paging through text once screenful at a time”. So I decided to do something odd: I minimized my terminal emulator until it was barely able to show a single line of output and logged into bandit26 with the provided ssh-key.

That turned out to be the key, since more wasn’t able to print all the output at once to the screen I was put into the paging-mode. From there it’s possible to open vi, via the key v. And from there I could take a look at /etc/bandit_pass/bandit26, where the password for the next level was stored.

Level 26

I was really happy that I got the password for this level. So happy in fact, that I entirely forgot that the shell for bandit26 was still the script I exploited in the previous level. So what the website said about the level wasn’t really all that helpful at the first glance:

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

I know that it’s possible to execute shell commands from within vi, but a !ls ~/ didn’t produce any output. After some confusion I realised that this relied on a shell, and all that you have at this point is /usr/bin/showtext.

Luckily you can set the shell from within vi, set shell=/bin/bash - after entering :sh you are dropped into bash. The home directory for the user bandit26 contains an executable with the setuid-bit set, bandit27-do.

Running it with the id-command as parameter confirmed my suspicion:

bandit26@bandit:~$ ./bandit27-do id
uid=11026(bandit26) gid=11026(bandit26) euid=11027(bandit27) groups=11026(bandit26)

My effective user-id was 11027, that of bandit27. So all I needed to do was the following:

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

Level 27

What the website said about this level made it look surprising easy:

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

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

And that is exactly all that needs to be done:

bandit27@bandit:~$ mkdir /tmp/mygit
bandit27@bandit:~$ git clone ssh://bandit27-git@localhost/home/bandit27-git/repo /tmp/mygit
Cloning into '/tmp/mygit'...
remote: Counting objects: 3, done.
remote: Compressing objects: 100% (2/2), done.
remote: Total 3 (delta 0), reused 0 (delta 0)
Receiving objects: 100% (3/3), done.
bandit27@bandit:~$ cat /tmp/mygit/README
The password to the next level is: <REDACTED>

Level 28

The text for this level on the website was eerily similar to the one about the last level:

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

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

The repository contains a single file, README.md:

# Bandit Notes
Some notes for level29 of bandit.

## credentials

- username: bandit29
- password: xxxxxxxxxx

bandit29 both had a password-file and a regular shell, so .. there must be a password somewhere. I decided to take a look at the commit-log.

commit edd935d60906b33f0619605abd1689808ccdd5ee
Author: Morla Porla <morla@overthewire.org>
Date:   Thu May 7 20:14:49 2020 +0200

    fix info leak

commit c086d11a00c0648d095d04c089786efef5e01264
Author: Morla Porla <morla@overthewire.org>
Date:   Thu May 7 20:14:49 2020 +0200

    add missing data

commit de2ebe2d5fd1598cd547f4d56247e053be3fdc38
Author: Ben Dover <noone@overthewire.org>
Date:   Thu May 7 20:14:49 2020 +0200

    initial commit of README.md

The commit with the ID edd935d60906b33f0619605abd1689808ccdd5ee looked interesting, because it implied that there was an information leak present in commits before it - which was the case, as revealed by running git checkout c086d11a00c0648d095d04c089786efef5e01264 and taking a look at README.md. In this state, the file contained the password for the next level.

Level 29

Level 29 was once more challenging me through a git-repository. So after cloning it I took a look at the README.md:

# Bandit Notes
Some notes for bandit30 of bandit.

## credentials

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

Maybe there’s a development branch then ..

bandit29@bandit:/tmp/bandit29git/repo$ git branch -r
  origin/HEAD -> origin/master
  origin/dev
  origin/master
  origin/sploits-dev

.. yep, there is - switching over to it (via git checkout -b origin/dev) changed README.md to a state where it contained the password for the next level.

Level 30

Once again I was required to clone a git-repository, but I didn’t expect to be mocked by the README.md:

just an epmty file... muahaha

There was only a single commit, there was nothing in other branches. I was stuck, so I vented to a friend of mine, who mentioned that - on an obviously totally unrelated note - I might want to read up on a feature of git called tags. So I did:

Like most VCSs, Git has the ability to tag specific points in a repository’s
history as being important. Typically, people use this functionality to mark
release points (v1.0, v2.0 and so on). 
bandit30@bandit:/tmp/bandit30tmp$ git tag
secret
bandit30@bandit:/tmp/bandit30tmp$ git show secret
<redacted>

The password for the next level was hidden in the tag. I’m still not entirely sure what the point of tags is, despite the explanation.

Level 31

This time the README.md of the git-repository was precise and helpful:

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

The repository consisted of a single commit, a single branch, no tags - but it included a .gitignore file, exempting all .txt-files from version control.

So all that was necessary to receive the password for the next level was:

rm ~/.gitignore
echo 'May I come in?' > key.txt
git add key.txt
git commit -m ''
git push

Which resulted in the following response from the remote server:

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>
remote: 
remote: .oOo.oOo.oOo.oOo.oOo.oOo.oOo.oOo.oOo.oOo.
remote: 

Level 32

When logging into this level I was greeted by the following prompt:

WELCOME TO THE UPPERCASE SHELL
>>

The website says about this level:

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

After some experimenting it seemed to me that all my input was converted to uppercase before being passed to /usr/bin/sh for interpretation.

Purely by accident I vaguely remembered a wargame I once played where the challenge was similar. /bin/sh allows you to arbitrarily define $0, which would usually be the name of the invoking program / script. I have to admit that I have no idea why that is the case, but I decided to try anyway:

ssh bandit32@bandit.labs.overthewire.org '$0=/bin/bash'
bandit32@bandit.labs.overthewire.org's password: 
WELCOME TO THE UPPERCASE SHELL
>> $0

And it worked - I was dropped in a somewhat limited shell. I did some usual looking around, ls, pwdir and so on. When running id I noticed something that left me utterly confused:

id
uid=11033(bandit33) gid=11032(bandit32) groups=11032(bandit32)

For some reason my uid was bandit33. Which means I could do the following:

cat /etc/bandit_pass/bandit33

I’m still not entirely sure why I was logged in as bandit33. It worked but .. I don’t know, it’s a bit frustrating when I find a solution but don’t quite understand what’s happening. I plan on asking in the official IRC once I’m done with all levels. I will update this section of the writeup accordingly.

Level 33

This level proved to be easy. All that was necessary to do was a simple cat README.md:

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

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

If you have an idea for an awesome new level, please let us know!

Well, I suppose that’s it. I vaguely remember first going through these levels at least five or six years back, with 34 levels already in existence back then. So I’m not expecting any more levels to be released anytime soon.

I completed the game over the course of a prolonged weekend, with between one or two hours each day, ultimately spending around five hours on it. It’s an excellent game to get started with wargaming, or getting back into them in cases such as mine.

Most levels are trivial if you are somewhat experienced with using a shell. Which makes the few tougher nuts even more fun. All in all: Good fun, looking forward to the next!