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 tocat
, 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 inmyname
- 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 inowner
. - If the owner of the current file is
bandit23
the file is executed, but automatically killed by sending theSIGKILL
-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!