Bandit is a Linux wargame.
There are many writeups online, but most of them just show the solutions(commands) and not really explain what the commands mean and why you need to execute those commands.
So I decided to publish a detailed writeup.
You should know some basic terminal commands like ls, cat, gzip, however you can still follow along my writeups.
It will be a bit hard any might take more time than those who know linux terminal commands.
For writeups I recommend watching Anyascii’s writeup videos.
https://www.youtube.com/watch?v=-GXCqqdbzFM&list=PLDeiu_UsIPZaCf6zTvcQNOmLF7alb2J9R
There are millions of writeups and videos so feel free to find the one you think is best.
To solve the bandit wargame,s you first need to know how to connect a computer with ssh.
Here’s the man page for ssh.
DESCRIPTION
ssh (SSH client) is a program for logging into a remote machine and for executing commands on a remote machine. It is intended to pro‐
vide secure encrypted communications between two untrusted hosts over an insecure network. X11 connections, arbitrary TCP ports and
Unix-domain sockets can also be forwarded over the secure channel.
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 user must prove their identity to the remote machine using one of several methods (see below).
If a command is specified, it will be executed on the remote host instead of a login shell. A complete command line may be specified as
command, or it may have additional arguments. If supplied, the arguments will be appended to the command, separated by spaces, before it
is sent to the server to be executed.
-p port
Port to connect to on the remote host. This can be specified on a per-host basis in the configuration file.
What is ssh?
ssh allows you to connect to other remote computers.
When you ssh to the remote machine it, you need to enter the password.
For level1 it’s bandit0.
The -p in ssh means port. So were connecting to port number 2220.
ssh -p 2220 bandit0@bandit.labs.overthewire.org
backend: gibson-0
bandit0@bandit.labs.overthewire.org's password:
We just successfuly sshed into bandit0, that’ll be it for the first level.
Level0->Level1
Level1 tells you to read a file named readme in the home directory.
As you can see there is a readme file.
ls
readme
You can read the readme file using the cat command.
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: ZjLjTmM6FvvyRnrb2rfNWOZOTa6ip5If
We got the password for the next level.
Level1->Level2
This time we login to bandit1 using ssh with our previous password.
ssh -p 2220 bandit1@bandit.labs.overthewire.org
There is a file named -.
The filename is literally a dash.
ls
-
I tried reading the file - with cat -, but it waits for keyboard input.
The reason why cat - doesn’t read the file and waits for keyboard input is because the hyphen acts as a special filename telling cat to read from its stdin, and not from a file on disk.
Since there isn’t any pipe feeding data, it waits until you type something from your keyboard.
What you can do however, is use the relative path to specify the filename like this.
cat ./-
263JGJPfgU6LtdEvgfWU1XP5yac29mFx
Level2->Level3
ssh -p 2220 bandit2@bandit.labs.overthewire.org
In this level, the password is stored in --spaces in this filename--.
The filename, similar to last time starts with --.
ls
--spaces in this filename--
Again the cat --spaces in this filename-- doesn’t allow you to read the file
It’s because the double-dash (--) is a standard convention in command-line interfaces to signal the end of the command-line options.
cat --spaces in this filename--
cat: unrecognized option '--spaces'
Try 'cat --help' for more information
However we can use the relative path as we did in the previous level.
When you use the tab autocompletion, the spaces are escaped to \.
cat ./--spaces\ in\ this\ filename--
MNk8KNH3Usiio41PRUEoDFPqfxLPlSmx
Level3-> Level4
ssh -p 2220 bandit3@bandit.labs.overthewire.org
The password is in a hidden file under the inhere directory.
ls
inhere
Just running ls without any options will not show you the hidden files, but passing the ls -l () option will.
Here’s the man apge for -l in ls.
By using the -l option we will be able to see much more information then, running ls without any options.
-l (The lowercase letter “ell”.) List files in the long format, as described in the The Long Format subsection below.
The Long Format
If the -l option is given, the following information is displayed for each file: file mode, number of links, owner name, group name, number of bytes in the file, abbreviated
month, day-of-month file was last modified, hour file last modified, minute file last modified, and the pathname. If the file or directory has extended attributes, the
permissions field printed by the -l option is followed by a '@' character. Otherwise, if the file or directory has extended security information (such as an access control
list), the permissions field printed by the -l option is followed by a '+' character. If the -% option is given, a '%' character follows the permissions field for dataless
files and directories, possibly replacing the '@' or '+' character.
As you can see, you can see previous files that didn’t appear before.
cd inhere/
ls -al
total 12
drwxr-xr-x 2 root root 4096 Oct 14 09:26 .
drwxr-xr-x 3 root root 4096 Oct 14 09:26 ..
-rw-r----- 1 bandit4 bandit3 33 Oct 14 09:26 ...Hiding-From-You
This looks like the password file ...Hiding-From-You.
cat ...Hiding-From-You
2WmrDFRmJIq3IPxneAaMGhap0pFhF3NJ
Level4-> Level5
ssh -p 2220 bandit4@bandit.labs.overthewire.org
There is an inhere directory.
ls
inhere
It’s almost identical to the previous level, but this time we need to find the only human-readable file.
So basically, we need to find the only ASCII-TEXT file, all the files except one are binary files.
cd inhere/
ls
-file00 -file01 -file02 -file03 -file04 -file05 -file06 -file07 -file08 -file09
You can check the type of a file using the file command.
At first I passed 9 arguments to the file command to inspect which one was the impostor, but typing all of the files were made my hands a bit painful.
Plus I needed to type ./ since the files started with a dash.
file ./-file00 ./-file01 ./-file02 ./-file03 ./-file04 ./-file05 ./-file06 ./-file07 ./-file08 ./-file09
./-file00: data
./-file01: data
./-file02: data
./-file03: data
./-file04: data
./-file05: data
./-file06: data
./-file07: ASCII text
./-file08: data
./-file09: data
Eventually I did find the odd one out which was -file07 and got the password but then thought if there were any other better ideas without running file on all the existing files.
cat ./-file07
4oQYVPkxZOOEOO5pTW81FB8j8lxXGUQw
It’s perfectly fine to get the password using file but I constantly thought if there were any better ways that didn’t require checking all of the files manually.
Then I found this.
https://stackoverflow.com/questions/9806944/grep-only-text-files
With the grep (-r recursively) -I(ignore binary) option we don’t need to manually inspect all 9 files.
Sweet!
grep -rI .
-file07:4oQYVPkxZOOEOO5pTW81FB8j8lxXGUQw
Level5-> Level6
ssh -p 2220 bandit5@bandit.labs.overthewire.org
This time we need to find a file that’s human readable, 1033 bytes in size and not executable in the inhere directory.
Yikes, there’s a lot of directories so manually inspecting each file turn into a nightmare.
cd inhere/
ls
maybehere00 maybehere02 maybehere04 maybehere06 maybehere08 maybehere10 maybehere12 maybehere14 maybehere16 maybehere18
maybehere01 maybehere03 maybehere05 maybehere07 maybehere09 maybehere11 maybehere13 maybehere15 maybehere17 maybehere19
I’ll give the answer to the problem right away, because this challenge is a bit hard if it’s you’re first time.
find -type f ! -executable -size 1033c
We can use the find command with -type f flag to specify only files, ! -executable to select non executable files, and -size 1033c to select files that are exactly 1033 bytes.
If you pass a - in front of 1033c you will select files smaller or equal to 1033 so do not pass a dash before 1033.
find -type f ! -executable -size 1033c
./maybehere07/.file2
Found the password.
cat ./maybehere07/.file2
HWasnPhtq9AVKe0dmk45nxy20cvUa6EG
We found the password, but I didn’t use one of the hints(that the file was human-readable. that that the challenge gave me.
Well you can run file right after our previous command.
By passing the -exec file {} + it will run the file command on the file that was found.
find -type f ! -executable -size 1033c -exec file {} + | grep ASCII
./maybehere07/.file2: ASCII text, with very long lines (1000)
Level6-> Level7
ssh -p 2220 bandit6@bandit.labs.overthewire.org
Now we need to file somewhere on the server that is owned by user bandit7, owned by group bandit6 and is 33 bytes.
I’ll give the answer to the problem right away, because this one’s a bit harder than the last one.
We need to search the entire root file system (/). Bya adding -user and -group , we can filter the output and show files owned by a specific user and group.
However, when running the command below you’ll get a whole bunch of errors saying permission denied.
The command that I executed seems to be correct, but the terminal output says something different.
find / -type f -user bandit7 -group bandit6 -size 33c
find: ‘/proc/tty/driver’: Permission denied
find: ‘/proc/1/task/1/fd’: Permission denied
find: ‘/proc/1/task/1/fdinfo’: Permission denied
find: ‘/proc/1/task/1/ns’: Permission denied
find: ‘/proc/1/fd’: Permission denied
find: ‘/proc/1/map_files’: Permission denied
...
Since there are so many Permission denied we need to filter these outputs.
One way of filtering these so called “errors” is to redirect them to a trash-can.
Linux systems typically refer /dev/null as their trash can.
I’m sure you guys know redirection.
Be cautious when redirecting, there shouldn’t be any spaces between the fd and the redirection operator.
find / -type f -user bandit7 -group bandit6 -size 33c 2> /dev/null
/var/lib/dpkg/info/bandit7.password
cat /var/lib/dpkg/info/bandit7.password
morbNTDkSW6jIlUc0ymOdMaLnOlFVAaj
Level7-> Level8
ssh -p 2220 bandit7@bandit.labs.overthewire.org
This time the password is in data.txt next to the word millionth.
A simple grep will capture the password.
cat data.txt | grep millionth
millionth dfwvzFQi4mU0wfNbFOe9RoWskMLg7eEc
Level8-> Level9
ssh -p 2220 bandit7@bandit.labs.overthewire.org
This time the password is in data.txt but it’s the only line of text not a duplicate(unique).
You can use the sort command to alphabetically sort the duplicates and then use the uniq command to select the only unique password.
Here’s the man page for uniq -u.
-u, --unique
Only output lines that are not repeated in the input.
uniq -u only prints unique lines.
sort data.txt | uniq -u
4CKMh1JI91bUIZZPXDqGanal4xvAg0JM
Level9-> Level10
ssh -p 2220 bandit9@bandit.labs.overthewire.org
This time you need to find one of the human-readable strings that start with "=" in data.txt.
file data.txt
data.txt: data
Use the strings command to inspect the strings and grep "=" since it precedes it.
strings data.txt | grep "="
FB`=
c\5D=
========== the
?/=l
=Uc1
=vG*2P
========== password
k=ezG
E========== is
=%r_
.?=Dm
O&A=n
5========== FGUW5ilLVJrxX9kMYMmlN4MgbpfMiqey
=*^Y
=L3jT
q<=,
'QHE=
+=NBf
It looks like FGUW5ilLVJrxX9kMYMmlN4MgbpfMiqey is the password.
Level10-> Level11
ssh -p 2220 bandit10@bandit.labs.overthewire.org
This time the password is in the data.txt but it’s base64 encoded.
base64 decoding will show the password.
The -d in base64 command means to decode.
Here’s the man page for base64 -d.
-d, -D, --decode
Decode incoming Base64 stream into binary data.
cat data.txt | base64 -d
The password is dtR173fZKb0RRsDFSGsg2RWnpNVj3qRr
Level11-> Level12
ssh -p 2220 bandit11@bandit.labs.overthewire.org
Since all lowercase and uppercase have been rotated by 13 positions we need to rotated it again by 13 characters.
This types of ciphers are called ROT-13.
cat data.txt
Gur cnffjbeq vf 7k16JArUVv5LxVuJfsSVdbbtaHGlw9D4
We can use the tr command(transliterate) to change characters.
Just be careful that A-M and a-m become N-Z and n-z but N-Z and n-z nees to be rotated back to A-M and a-m.
That’s why theres an A-M and a-m.
cat data.txt | tr 'A-Za-z' 'N-ZA-Mn-za-m'
The password is 7x16WNeHIi5YkIhWsfFIqoognUTyj9Q4
Level12-> Level13
ssh -p 2220 bandit12@bandit.labs.overthewire.org
We have a text file.
file data.txt
data.txt: ASCII text
Here’s the content of the file.
cat data.txt
00000000: 1f8b 0808 2817 ee68 0203 6461 7461 322e ....(..h..data2.
00000010: 6269 6e00 013c 02c3 fd42 5a68 3931 4159 bin..<...BZh91AY
00000020: 2653 59cc 46b5 2d00 0018 ffff da5f e6e3 &SY.F.-......_..
00000030: 9fcd f59d bc69 ddd7 f7ff a7e7 dbdd b59f .....i..........
00000040: fff7 cfdd ffbf bbdf ffff ff5e b001 3b58 ...........^..;X
00000050: 2406 8000 00d0 6834 6234 d000 6869 9000 $.....h4b4..hi..
00000060: 1a7a 8003 40d0 01a1 a006 8188 340d 1a68 .z..@.......4..h
00000070: d340 d189 e906 8f41 0346 4d94 40d1 91a0 .@.....A.FM.@...
00000080: 681a 0681 a068 0680 c400 3207 a269 a189 h....h....2..i..
00000090: a326 8000 c800 c81a 1883 1000 00d0 c023 .&.............#
000000a0: 4311 a034 30ca 6800 0680 0681 a680 6868 C..40.h.......hh
000000b0: d068 6868 c04c d400 0003 4d06 87a8 d000 .hhh.L....M.....
000000c0: 3086 8c20 3268 068d 000c 9a64 0698 8d04 0.. 2h.....d....
000000d0: 0600 6860 3541 2c85 c8e1 7bc9 479e e369 ..h`5A,...{.G..i
000000e0: 30a1 0250 82e9 64ef 9d40 312f 4bc8 b00f 0..P..d..@1/K...
000000f0: 0c8f 026c d5ca 1008 d7aa 336a ed8f bb7b ...l......3j...{
00000100: b43f d544 1658 824e a4af 9ce5 612e 8a27 .?.D.X.N....a..'
00000110: c303 0512 cbff dccd f42d 6866 ceec 8127 .........-hf...'
00000120: 5475 ed39 100b f897 7828 46e2 fdf3 efa7 Tu.9....x(F.....
00000130: 43b0 1701 a114 397a 1d81 8d1f 1f23 2ada C.....9z.....#*.
00000140: 9b18 ee4d d05d 4ae3 d032 e494 ae98 27b0 ...M.]J..2....'.
00000150: 30a0 533c 6696 60ad c546 70c4 322b 7174 0.S<f.`..Fp.2+qt
00000160: 8bb1 52c6 ed0a 267b 7165 208b 77fe 1294 ..R...&{qe .w...
00000170: 2280 3311 354f c68e e004 93e3 abf4 5a0a ".3.5O........Z.
00000180: a568 c894 27c2 9015 49bb 0147 c253 8e73 .h..'...I..G.S.s
00000190: 2fdd 90e1 6871 c692 1d67 5ebc a5f9 b8a1 /...hq...g^.....
000001a0: 3913 f073 1919 b628 9ae2 c1bf 15ee 493a 9..s...(......I:
000001b0: e375 4d23 71e0 4934 c7a2 15ff 985c a0ba .uM#q.I4.....\..
000001c0: 9e65 d613 313d 7cef 512a 32bf 835e 50d6 .e..1=|.Q*2..^P.
000001d0: a54f 57ba bceb 6944 03c8 8a50 3542 9140 .OW...iD...P5B.@
000001e0: eb51 0f4c 8a23 9401 0246 0457 d1c0 c33e .Q.L.#...F.W...>
000001f0: c328 2de7 3d1d 64be 4190 36b0 b803 4f80 .(-.=.d.A.6...O.
00000200: 40bc 3960 ac5e 13a9 3a77 0162 d662 7659 @.9`.^..:w.b.bvY
00000210: fdfd 9535 1188 8588 e8e5 a78d 9b24 c066 ...5.........$.f
00000220: 91c6 4212 fac6 4ed8 ce48 161f cc44 215f ..B...N..H...D!_
00000230: 330c 5ed7 2709 e578 6efd 3775 c703 8aa1 3.^.'..xn.7u....
00000240: 10b6 2c5d 16bf f352 c7ff c5dc 914e 1424 ..,]...R.....N.$
00000250: 3311 ad4b 40d0 18b2 373c 0200 00 3..K@...7<...
I first thought it was a bzip file because the file header had bzip file’s signature (BZh91AY), but it’s not a bzip file.
Since we don’t have permission to write or execute files we need to make a temporary directory where we can.
mktemp -d
/tmp/tmp.cqLdafZpA8
cd into the directory we just made and copy data.txt.
cd /tmp/tmp.cqLdafZpA8
cp ~/data.txt .
If you Google the first 8 characters of data.txt(1f8b 0808), it tells us that it’s the gzip file header.
So we’re dealing with a gzip file, but we simply can’t just change the file name from data.txt to data.gz because
the contents of a gzip file needs to be binary and right now data.txt is just ASCII text.
In order to change the content from ASCII text to binary we can use the xxd command.
Here’s the man page of xxd -r.
-r | -revert
Reverse operation: convert (or patch) hex dump into binary. If not writing to stdout, xxd writes into its output file without truncating it. Use the combina‐
tion -r -p to read plain hexadecimal dumps without line number information and without a particular column layout. Additional whitespace and line breaks are
allowed anywhere. Use the combination -r -b to read a bits dump instead of a hex dump.
xxd -r data.txt data
Now we’ve changed the format from ASCII text to binary and correctly saved it in a gzip file.
file data
data: gzip compressed data, was "data2.bin", last modified: Tue Oct 14 09:26:00 2025, max compression, from Unix, original size modulo 2^32 572
If you change the extension of the file to .gz(gzip) the name of the file will become red, which means your *nix system knows that the file is a gzip file.
mv data data.gz
You can deflate the file using the -d from the gzip command.
gzip -d data.gz
Here’s the man page of it.
-d --decompress --uncompress
Decompress.
Now it’s a bzip file.
file data
data: bzip2 compressed data, block size = 900k
Let’s change the file extension to a bzip file(.bz2).
Again the color of the file name will be red, if your computer recognizes the bzip file.
mv data data.bz2
You can decompress the flag with the -d flag, below is the man page.
-d --decompress
Force decompression. bzip2, bunzip2 and bzcat are really the same program, and the decision about what actions to take is done on the basis of which name is used. This flag overrides that
mechanism, and forces bzip2 to decompress.
You can also use gunzip to deflate gzip files and bunzip for bzip files but I always use the -d option instead because a lot of other commands use it to decode or deflate stuff as well for example base64, etc…
bzip2 -d data.bz2
Now we can repeat the process above, since it’s another gzip file.
file data
data: gzip compressed data, was "data4.bin", last modified: Tue Oct 14 09:26:00 2025, max compression, from Unix, original size modulo 2^32 20480
After unzipping the gzip file these you’ll get a tar archive.
file data
data: POSIX tar archive (GNU)
Read the wikiepdia page here to understand what a tar archive is
https://en.wikipedia.org/wiki/Tar_(computing).
The tar archive uses the .tar extension.
mv data data.tar
If you run ls on the data.tar file you can see the file name has changed red again.
To extract tar files I always use -xvf.
tar -xvf data.tar
Here’s the man page for each tack.
-x, --extract, --get
Extract files from an archive. Arguments are optional. When given, they specify names of the archive members to be extracted.
-v, --verbose
Verbosely list files processed. Each instance of this option on the command line increases the verbosity level by one. The maximum verbosity level is 3. For a detailed discussion of how var‐
ious verbosity levels affect tar's output, please refer to GNU Tar Manual, subsection 2.5.2 "The '--verbose' Option".
-f, --file=ARCHIVE
Use archive file or device ARCHIVE. If this option is not given, tar will first examine the environment variable `TAPE'. If it is set, its value will be used as the archive name. Otherwise,
tar will assume the compiled-in default. The default value can be inspected either using the --show-defaults option, or at the end of the tar --help output.
For your information you pretty much always have to use the -f option.
file data5.bin
data5.bin: POSIX tar archive (GNU)
Now we can repeat, after doing the same stuff over and over again you get the password.
cat data8
The password is FO5dwFsc0cbaIiH0h8J2eUks2vdTDwAn
Level13-> Level14
ssh -p 2220 bandit13@bandit.labs.overthewire.org
file sshkey.private
sshkey.private: PEM RSA private key
This time we are given a ssh private key, it looks like it’s base64 encoded.
cat sshkey.private
-----BEGIN RSA PRIVATE KEY-----
MIIEpAIBAAKCAQEAxkkOE83W2cOT7IWhFc9aPaaQmQDdgzuXCv+ppZHa++buSkN+
gg0tcr7Fw8NLGa5+Uzec2rEg0WmeevB13AIoYp0MZyETq46t+jk9puNwZwIt9XgB
ZufGtZEwWbFWw/vVLNwOXBe4UWStGRWzgPpEeSv5Tb1VjLZIBdGphTIK22Amz6Zb
ThMsiMnyJafEwJ/T8PQO3myS91vUHEuoOMAzoUID4kN0MEZ3+XahyK0HJVq68KsV
ObefXG1vvA3GAJ29kxJaqvRfgYnqZryWN7w3CHjNU4c/2Jkp+n8L0SnxaNA+WYA7
jiPyTF0is8uzMlYQ4l1Lzh/8/MpvhCQF8r22dwIDAQABAoIBAQC6dWBjhyEOzjeA
J3j/RWmap9M5zfJ/wb2bfidNpwbB8rsJ4sZIDZQ7XuIh4LfygoAQSS+bBw3RXvzE
pvJt3SmU8hIDuLsCjL1VnBY5pY7Bju8g8aR/3FyjyNAqx/TLfzlLYfOu7i9Jet67
xAh0tONG/u8FB5I3LAI2Vp6OviwvdWeC4nOxCthldpuPKNLA8rmMMVRTKQ+7T2VS
nXmwYckKUcUgzoVSpiNZaS0zUDypdpy2+tRH3MQa5kqN1YKjvF8RC47woOYCktsD
o3FFpGNFec9Taa3Msy+DfQQhHKZFKIL3bJDONtmrVvtYK40/yeU4aZ/HA2DQzwhe
ol1AfiEhAoGBAOnVjosBkm7sblK+n4IEwPxs8sOmhPnTDUy5WGrpSCrXOmsVIBUf
laL3ZGLx3xCIwtCnEucB9DvN2HZkupc/h6hTKUYLqXuyLD8njTrbRhLgbC9QrKrS
M1F2fSTxVqPtZDlDMwjNR04xHA/fKh8bXXyTMqOHNJTHHNhbh3McdURjAoGBANkU
1hqfnw7+aXncJ9bjysr1ZWbqOE5Nd8AFgfwaKuGTTVX2NsUQnCMWdOp+wFak40JH
PKWkJNdBG+ex0H9JNQsTK3X5PBMAS8AfX0GrKeuwKWA6erytVTqjOfLYcdp5+z9s
8DtVCxDuVsM+i4X8UqIGOlvGbtKEVokHPFXP1q/dAoGAcHg5YX7WEehCgCYTzpO+
xysX8ScM2qS6xuZ3MqUWAxUWkh7NGZvhe0sGy9iOdANzwKw7mUUFViaCMR/t54W1
GC83sOs3D7n5Mj8x3NdO8xFit7dT9a245TvaoYQ7KgmqpSg/ScKCw4c3eiLava+J
3btnJeSIU+8ZXq9XjPRpKwUCgYA7z6LiOQKxNeXH3qHXcnHok855maUj5fJNpPbY
iDkyZ8ySF8GlcFsky8Yw6fWCqfG3zDrohJ5l9JmEsBh7SadkwsZhvecQcS9t4vby
9/8X4jS0P8ibfcKS4nBP+dT81kkkg5Z5MohXBORA7VWx+ACohcDEkprsQ+w32xeD
qT1EvQKBgQDKm8ws2ByvSUVs9GjTilCajFqLJ0eVYzRPaY6f++Gv/UVfAPV4c+S0
kAWpXbv5tbkkzbS0eaLPTKgLzavXtQoTtKwrjpolHKIHUz6Wu+n4abfAIRFubOdN
/+aLoRQ0yBDRbdXMsZN/jvY44eM+xRLdRVyMmdPtP8belRi2E2aEzA==
-----END RSA PRIVATE KEY-----
If you’ve never used an ssh key file this is going to be pretty hard so I’ll just give the solution right away.
You can ssh into another machine using the ssh key with the ssh -i tack, below is the man page of it.
-i identity_file
Selects a file from which the identity (private key) for public key authentication is read. You can also specify a public key file to use the corresponding private key that is loaded in
ssh-agent(1) when the private key file is not present locally. The default is ~/.ssh/id_rsa, ~/.ssh/id_ecdsa, ~/.ssh/id_ecdsa_sk, ~/.ssh/id_ed25519, ~/.ssh/id_ed25519_sk and ~/.ssh/id_dsa.
Identity files may also be specified on a per-host basis in the configuration file. It is possible to have multiple -i options (and multiple identities specified in configuration files). If
no certificates have been explicitly specified by the CertificateFile directive, ssh will also try to load certificate information from the filename obtained by appending -cert.pub to identity
filenames.
But a problem arises when you try to ssh inside the remote server.
bandit13@bandit:~$ ssh -p 2220 -i sshkey.private bandit14@bandit.labs.overthewire.org
The authenticity of host '[bandit.labs.overthewire.org]:2220 ([127.0.0.1]:2220)' can't be established.
ED25519 key fingerprint is SHA256:C2ihUBV7ihnV1wUXRb4RrEcLfXC5CXlhmAAM/urerLY.
This key is not known by any other names.
Are you sure you want to continue connecting (yes/no/[fingerprint])? yes
Could not create directory '/home/bandit13/.ssh' (Permission denied).
Failed to add the host to the list of known hosts (/home/bandit13/.ssh/known_hosts).
_ _ _ _
| |__ __ _ _ __ __| (_) |_
| '_ \ / _` | '_ \ / _` | | __|
| |_) | (_| | | | | (_| | | |_
|_.__/ \__,_|_| |_|\__,_|_|\__|
This is an OverTheWire game server.
More information on http://www.overthewire.org/wargames
!!! You are trying to log into this SSH server with a password on port 2220 from localhost.
!!! Connecting from localhost is blocked to conserve resources.
!!! Please log out and log in again.
backend: gibson-0
Received disconnect from 127.0.0.1 port 2220:2: no authentication methods enabled
Disconnected from 127.0.0.1 port 2220
Not a lot of writeups explained this error.
The solution is to copy the sshkey to your local machine.
You then might get an error regarding permissions.
ssh -p 2220 -i sshkey.private bandit14@bandit.labs.overthewire.org
_ _ _ _
| |__ __ _ _ __ __| (_) |_
| '_ \ / _` | '_ \ / _` | | __|
| |_) | (_| | | | | (_| | | |_
|_.__/ \__,_|_| |_|\__,_|_|\__|
This is an OverTheWire game server.
More information on http://www.overthewire.org/wargames
backend: gibson-0
@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@
@ 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
Bandit is complaining that the sshkey.priavte has way more permissions that it’s supposed to have.
Changing the permission to owner-readonly then sshing will take you to the next level.
chmod 400 sshkey.private
We passed bandit13 but here are some questions I had after solving the level.
- Why is the sshkey.private base64 encoded?
It uses base64 to transport binary data.
base64 encodes binary data to 64 characters 09(10) + AZ,a-z(52), and ‘+’,’/’(2) and will occasionally use ‘=’ for paddings.
Since binary characters include characters that aren’t printable we need it to transfer it to format like UTF-8 or ASCII which a lot of systems can handle.
By the way base64 decoding the RSA key won’t print any meaningful text because the data itself is binary, I’ve actually tried decoding it but it was no use.
- Why does it use a RSA key?
RSA is one of the oldest assymetric encryption algorithms.
In short it uses a pair of keys.
The pair of keys are consisted of a private key and a public key.
The private key as the name suggets is private and should be kept to yourself.
It allows you to sign data, proving that you are the owner.
The private key is usually in ~/.ssh/id_rsa.
The public is given to any server you want to login.
It goes to the server’s ~/.ssh/authorized_keys to verify you own the private key.
The public key is usually in ~/.ssh/id_rsa.pub.
If you go inside your ~/.ssh folder there are other files such as the known_hosts files but I think this is all I can explain.
I’m not good at crypo or math.
Level14-> Level15
You don’t need to ssh this time, just continue from where you left last time.
To get to the next level I need to send the password for the current level to port 30000 on localhost.
Here’s the password of the current level.
cat /etc/bandit_pass/bandit14
MU4VWeTyJk8ROof1qqmcBPaLh7lDCPvS
Use the netcat(nc) command to connect to ip ports.
When using netcat you can pass the port number after the ip address.
You then need to send the password to localhost.
What exactly is localhost?
We usually communicate with other computers like Google’s server, or Disney … etc , but sometime we don’t want to communicate to other computers.
For example, when you open a web server you want the results to show on your computer.
In cases like this, instead of sending a request to otehr cmoputers you want to test your local development results we use an ip.address (127.0.0.1) aka localhost.
It’s a loopback address that points to your computer instead of sending it to other ip addresses.
nc localhost 30000
MU4VWeTyJk8ROof1qqmcBPaLh7lDCPvS
Correct!
8xCjnmgoKbGLhHFAZlGE5Tmu4M2tKJQo
For more information read this post.
https://www.reddit.com/r/learnpython/comments/3bgffc/meaning_of_localhost/
Level15-> Level16
ssh -p 2220 bandit15@bandit.labs.overthewire.org
In this level you need to send the current password to localhost on port 30001 using SSL/TLS encryption.
The openssl command allows you to connect to localhost using SSL/TLS.
openssl s_client -connect localhost:30001
CONNECTED(00000003)
Can't use SSL_get_servername
depth=0 CN = SnakeOil
verify error:num=18:self-signed certificate
verify return:1
depth=0 CN = SnakeOil
verify return:1
---
Certificate chain
0 s:CN = SnakeOil
i:CN = SnakeOil
a:PKEY: rsaEncryption, 4096 (bit); sigalg: RSA-SHA256
v:NotBefore: Jun 10 03:59:50 2024 GMT; NotAfter: Jun 8 03:59:50 2034 GMT
---
Server certificate
-----BEGIN CERTIFICATE-----
MIIFBzCCAu+gAwIBAgIUBLz7DBxA0IfojaL/WaJzE6Sbz7cwDQYJKoZIhvcNAQEL
BQAwEzERMA8GA1UEAwwIU25ha2VPaWwwHhcNMjQwNjEwMDM1OTUwWhcNMzQwNjA4
MDM1OTUwWjATMREwDwYDVQQDDAhTbmFrZU9pbDCCAiIwDQYJKoZIhvcNAQEBBQAD
ggIPADCCAgoCggIBANI+P5QXm9Bj21FIPsQqbqZRb5XmSZZJYaam7EIJ16Fxedf+
jXAv4d/FVqiEM4BuSNsNMeBMx2Gq0lAfN33h+RMTjRoMb8yBsZsC063MLfXCk4p+
09gtGP7BS6Iy5XdmfY/fPHvA3JDEScdlDDmd6Lsbdwhv93Q8M6POVO9sv4HuS4t/
jEjr+NhE+Bjr/wDbyg7GL71BP1WPZpQnRE4OzoSrt5+bZVLvODWUFwinB0fLaGRk
GmI0r5EUOUd7HpYyoIQbiNlePGfPpHRKnmdXTTEZEoxeWWAaM1VhPGqfrB/Pnca+
vAJX7iBOb3kHinmfVOScsG/YAUR94wSELeY+UlEWJaELVUntrJ5HeRDiTChiVQ++
wnnjNbepaW6shopybUF3XXfhIb4NvwLWpvoKFXVtcVjlOujF0snVvpE+MRT0wacy
tHtjZs7Ao7GYxDz6H8AdBLKJW67uQon37a4MI260ADFMS+2vEAbNSFP+f6ii5mrB
18cY64ZaF6oU8bjGK7BArDx56bRc3WFyuBIGWAFHEuB948BcshXY7baf5jjzPmgz
mq1zdRthQB31MOM2ii6vuTkheAvKfFf+llH4M9SnES4NSF2hj9NnHga9V08wfhYc
x0W6qu+S8HUdVF+V23yTvUNgz4Q+UoGs4sHSDEsIBFqNvInnpUmtNgcR2L5PAgMB
AAGjUzBRMB0GA1UdDgQWBBTPo8kfze4P9EgxNuyk7+xDGFtAYzAfBgNVHSMEGDAW
gBTPo8kfze4P9EgxNuyk7+xDGFtAYzAPBgNVHRMBAf8EBTADAQH/MA0GCSqGSIb3
DQEBCwUAA4ICAQAKHomtmcGqyiLnhziLe97Mq2+Sul5QgYVwfx/KYOXxv2T8ZmcR
Ae9XFhZT4jsAOUDK1OXx9aZgDGJHJLNEVTe9zWv1ONFfNxEBxQgP7hhmDBWdtj6d
taqEW/Jp06X+08BtnYK9NZsvDg2YRcvOHConeMjwvEL7tQK0m+GVyQfLYg6jnrhx
egH+abucTKxabFcWSE+Vk0uJYMqcbXvB4WNKz9vj4V5Hn7/DN4xIjFko+nREw6Oa
/AUFjNnO/FPjap+d68H1LdzMH3PSs+yjGid+6Zx9FCnt9qZydW13Miqg3nDnODXw
+Z682mQFjVlGPCA5ZOQbyMKY4tNazG2n8qy2famQT3+jF8Lb6a4NGbnpeWnLMkIu
jWLWIkA9MlbdNXuajiPNVyYIK9gdoBzbfaKwoOfSsLxEqlf8rio1GGcEV5Hlz5S2
txwI0xdW9MWeGWoiLbZSbRJH4TIBFFtoBG0LoEJi0C+UPwS8CDngJB4TyrZqEld3
rH87W+Et1t/Nepoc/Eoaux9PFp5VPXP+qwQGmhir/hv7OsgBhrkYuhkjxZ8+1uk7
tUWC/XM0mpLoxsq6vVl3AJaJe1ivdA9xLytsuG4iv02Juc593HXYR8yOpow0Eq2T
U5EyeuFg5RXYwAPi7ykw1PW7zAPL4MlonEVz+QXOSx6eyhimp1VZC11SCg==
-----END CERTIFICATE-----
subject=CN = SnakeOil
issuer=CN = SnakeOil
---
No client certificate CA names sent
Peer signing digest: SHA256
Peer signature type: RSA-PSS
Server Temp Key: X25519, 253 bits
---
SSL handshake has read 2103 bytes and written 373 bytes
Verification error: self-signed certificate
---
New, TLSv1.3, Cipher is TLS_AES_256_GCM_SHA384
Server public key is 4096 bit
Secure Renegotiation IS NOT supported
Compression: NONE
Expansion: NONE
No ALPN negotiated
Early data was not sent
Verify return code: 18 (self-signed certificate)
---
---
Post-Handshake New Session Ticket arrived:
SSL-Session:
Protocol : TLSv1.3
Cipher : TLS_AES_256_GCM_SHA384
Session-ID: C4EFDF313C8AC016458C8EFC521F4475BEE6E58DFDCA63BD975042626B476713
Session-ID-ctx:
Resumption PSK: A7509F0A8559D3F2EADA317C78B63B44BDF16DF9E7271E8FB954291C0CAF0F97EA692EB751DE4F88294AC82B50DF6B42
PSK identity: None
PSK identity hint: None
SRP username: None
TLS session ticket lifetime hint: 300 (seconds)
TLS session ticket:
0000 - 2a 06 c5 62 9a 2e e0 07-e1 99 e9 19 e0 c6 d4 e8 *..b............
0010 - 4e 35 30 ef bf ec 9b 3a-c9 54 ae 7d 34 a4 33 0f N50....:.T.}4.3.
0020 - 3a d0 ea 4b 47 cb e9 87-92 e9 3f 36 af cc 07 48 :..KG.....?6...H
0030 - 79 d1 87 c2 45 9d 4b 1b-c3 69 a6 cf 97 23 c7 f0 y...E.K..i...#..
0040 - 40 fa 47 e2 21 21 43 8c-64 a4 90 de cb 76 b0 e7 @.G.!!C.d....v..
0050 - 83 e0 83 5b 6e cd b8 6c-df 0e e4 90 27 1a 9e a4 ...[n..l....'...
0060 - cc 4d 0b df b9 83 b3 4f-41 d8 bc 94 12 06 ab 2a .M.....OA......*
0070 - 1b 70 bf c6 a0 6c df a4-60 b6 49 42 7e 1a 8c 25 .p...l..`.IB~..%
0080 - 21 c8 d5 59 e5 07 ca a4-63 3c a0 b2 31 03 1e 59 !..Y....c<..1..Y
0090 - cd 31 b4 85 42 59 2b e9-35 78 0a 7a 62 29 45 a7 .1..BY+.5x.zb)E.
00a0 - 01 55 80 c6 24 42 6c f6-9a 5e 11 f5 9d 1c 49 b0 .U..$Bl..^....I.
00b0 - 66 b6 f4 2c 0a 24 7b 8e-f7 e1 e4 a6 1c 70 02 0a f..,.${......p..
00c0 - 2e fe 41 eb 9f d3 67 96-f0 02 09 61 ca cf 30 f8 ..A...g....a..0.
00d0 - f8 4a 73 82 cf 7c 03 8a-75 ce 3e ab 03 cb 00 d9 .Js..|..u.>.....
Start Time: 1761790790
Timeout : 7200 (sec)
Verify return code: 18 (self-signed certificate)
Extended master secret: no
Max Early Data: 0
---
read R BLOCK
---
Post-Handshake New Session Ticket arrived:
SSL-Session:
Protocol : TLSv1.3
Cipher : TLS_AES_256_GCM_SHA384
Session-ID: 8793B5DE04AA018A70022B5D61E758773404AF6D5FD227965486F6D04C673724
Session-ID-ctx:
Resumption PSK: EDEA2C214681BCBDAC065B920E9CCFBA8A1A98F5B08BB3984FC8BA244F62F070C68CEE4D16E7E8CF18CF4B0BA5D68FAC
PSK identity: None
PSK identity hint: None
SRP username: None
TLS session ticket lifetime hint: 300 (seconds)
TLS session ticket:
0000 - 2a 06 c5 62 9a 2e e0 07-e1 99 e9 19 e0 c6 d4 e8 *..b............
0010 - 74 7d 51 e2 4f 10 0a c4-f1 d3 f4 ce 10 c1 97 56 t}Q.O..........V
0020 - af ee c9 5b a0 12 9b 6f-5b 50 03 69 94 96 bc 0f ...[...o[P.i....
0030 - 05 c2 66 2d 0a a2 89 09-07 7c 42 1d ec 7a 6c 32 ..f-.....|B..zl2
0040 - 09 31 5f 50 e8 6b b4 16-f8 ed a4 3f c1 e4 87 e4 .1_P.k.....?....
0050 - d1 13 e4 cb ae 32 48 da-d5 40 ec e6 a2 27 78 3e .....2H..@...'x>
0060 - ae e9 7f 31 ab bc c5 8a-1f 31 3f 01 46 08 82 48 ...1.....1?.F..H
0070 - 7b 40 a0 98 47 54 6f fe-6f 79 a5 09 34 46 ff 99 {@..GTo.oy..4F..
0080 - cc 74 77 ab bb 6d 6b bb-42 6d 38 f6 f1 ad 81 b2 .tw..mk.Bm8.....
0090 - c3 c1 00 60 50 79 58 33-f0 0d 54 ed 85 47 34 fe ...`PyX3..T..G4.
00a0 - 17 ab f5 bc 1b bd 8d ac-ec f5 2c a3 53 04 3d 17 ..........,.S.=.
00b0 - cf 43 08 57 14 df d5 3c-eb 0e ef 5b 0d a0 4f e6 .C.W...<...[..O.
00c0 - fe 38 3d d6 61 c4 a6 f7-97 98 35 e3 11 71 0f c1 .8=.a.....5..q..
00d0 - a4 6e 54 ca 13 7a 54 dc-0b bb bd e3 c4 91 43 74 .nT..zT.......Ct
Start Time: 1761790790
Timeout : 7200 (sec)
Verify return code: 18 (self-signed certificate)
Extended master secret: no
Max Early Data: 0
---
read R BLOCK
8xCjnmgoKbGLhHFAZlGE5Tmu4M2tKJQo
Correct!
kSkvUpMQ7lBYyCM4GBPvCvT1BfWRy0Dx
closed
I always like to use nc to connect to remote servers but apparently it doesn’t support TLS/SSL.
Here’s an easy explanation of TLS/SSL.
https://www.reddit.com/r/explainlikeimfive/comments/b0z43c/eli5_what_is_tlsssl/
Level16-> Level17
ssh -p 2220 bandit16@bandit.labs.overthewire.org
It’s almost the same as level16 but it looks like only one port from 31000~32000 accepts the password.
To find out which port number to use we will use nmap(Network Mapper).
Here’s the man page for nmap and the -p tack.
Nmap (“Network Mapper”) is an open source tool for network exploration and security auditing. It was designed to rapidly scan large networks, although it works fine against single hosts. Nmap uses raw
IP packets in novel ways to determine what hosts are available on the network, what services (application name and version) those hosts are offering, what operating systems (and OS versions) they are
running, what type of packet filters/firewalls are in use, and dozens of other characteristics. While Nmap is commonly used for security audits, many systems and network administrators find it useful
for routine tasks such as network inventory, managing service upgrade schedules, and monitoring host or service uptime.
The output from Nmap is a list of scanned targets, with supplemental information on each depending on the options used. Key among that information is the “interesting ports table”. That table lists
the port number and protocol, service name, and state. The state is either open, filtered, closed, or unfiltered. Open means that an application on the target machine is listening for
connections/packets on that port. Filtered means that a firewall, filter, or other network obstacle is blocking the port so that Nmap cannot tell whether it is open or closed. Closed ports have no
application listening on them, though they could open up at any time. Ports are classified as unfiltered when they are responsive to Nmap's probes, but Nmap cannot determine whether they are open or
closed. Nmap reports the state combinations open|filtered and closed|filtered when it cannot determine which of the two states describe a port. The port table may also include software version details
when version detection has been requested. When an IP protocol scan is requested (-sO), Nmap provides information on supported IP protocols rather than listening ports.
In addition to the interesting ports table, Nmap can provide further information on targets, including reverse DNS names, operating system guesses, device types, and MAC addresses.
A typical Nmap scan is shown in Example 1. The only Nmap arguments used in this example are -A, to enable OS and version detection, script scanning, and traceroute; -T4 for faster execution; and then
the hostname.
-p <port ranges>: Only scan specified ports
nmap -p 31000-32000 localhost
Starting Nmap 7.94SVN ( https://nmap.org ) at 2025-10-30 02:30 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.11 seconds
If you try port by port the only port that speaks SSL/TLS is 31790.
This time you’ll get a KEYUPDATE alert and fail.
openssl s_client -connect localhost:31790
...
read R BLOCK
kSkvUpMQ7lBYyCM4GBPvCvT1BfWRy0Dx
KEYUPDATE
Wrong! Please enter the correct current password.
closed
However by adding the -ign_eof flag at the end of the ssl command fixes the job.
openssl s_client -connect localhost:31790 -ign_eof
...
read R BLOCK
kSkvUpMQ7lBYyCM4GBPvCvT1BfWRy0Dx
Correct!
Unlike the previous levels you won’t get a password instead you get an RSA Key.
Copy your RSA key save it on your local computer and change the permissions as we did before.
Level17-> Level18
ssh -p 2220 -i bandit16.key bandit17@bandit.labs.overthewire.org
This time the password is the only different line that’s different between passwords.old and passwords.new.
The diff command shows the differences between files.
diff passwords.old passwords.new
42c42
< BMIOFKM7CRSLI97voLp3TD80NAq5exxk
---
> x2gLTTjFwMOhQ8oWNbMN362QKxfRqGlO
Understanding diff’s output is kind of hard.
According to this post the < denotes lines in the first file (passwords.old)
and the > denotes lines in the second file (passwords.new).
And the 42c42 means that the 42nd line in the first file changed to the 42nd line in the second file.
Using -u (unified) shows as if the two files were a single file.
diff -u passwords.old passwords.new
--- passwords.old 2025-10-14 09:26:06.650645470 +0000
+++ passwords.new 2025-10-14 09:26:06.654505233 +0000
@@ -39,7 +39,7 @@
gLZVUZRqJJJUWLKbeP8Lokwq6CbFCyxG
boG62bCXn3U4gPugAPZMLQ9PtfdX9TMV
hnGGKfEtEGm7NwcHFmxZzubaJ5LrXbbR
-BMIOFKM7CRSLI97voLp3TD80NAq5exxk
+x2gLTTjFwMOhQ8oWNbMN362QKxfRqGlO
87QCHMmaVMEz51K7aJvhbOqSR3OzdNKg
vuUCNxwqGpcAZtNaym57PCbxh3iWPmEv
lsUfIWgGXpNmnOuu7cR0PZXo2PJ6knhU
The line that starts with - is a different line that passwords.old has and the line that starts with a + is a line that only passwords.new has.
So the password that’s only in the passwords.new file is x2gLTTjFwMOhQ8oWNbMN362QKxfRqGlO.
Level18->Level19
I can’t login to bandit18 with the previous password for soem reason.
ssh -p 2220 bandit18@bandit.labs.overthewire.org
Someone modified the .bashrc file, and because of that it logs you out when trying to ssh.
Here’s the man page for ssh.
The ssh -t option allows you to specify a shell.
-t Force pseudo-terminal allocation. This can be used to execute arbitrary screen-based programs on a remote machine, which can be very useful, e.g. when implementing menu services. Multiple -t options force tty allocation, even if ssh has no local tty.
I sshed with and set the default shell to the Bourne Shell.
The Bourne Shell is one of the oldest shells pretty much prevalent on any *nix machine.
ssh -p 2220 bandit18@bandit.labs.overthewire.org -t "/bin/sh"
Now I’m logged in as bandit18.
id
uid=11018(bandit18) gid=11018(bandit18) groups=11018(bandit18)
The password is in the readme file.
cat readme
cGWpMaKXVwDUNgPAVJbWYuGHVn9zl3j8
Level19->Level20
ssh -p 2220 bandit19@bandit.labs.overthewire.org
A setuid binary is given and I need to read the password using the setuid binary.
ls -al bandit20-do
-rwsr-x--- 1 bandit20 bandit19 14884 Oct 14 09:26 bandit20-do
./bandit20-do
Run a command as another user.
Example: ./bandit20-do whoami
What is setuid?
Remember Linux or Unix has 3 groups, user, group and others.
The permission for the bandit20-do binary looks like this.
-rwsr-x--- 1 bandit20 bandit19 14884 Oct 14 09:26 bandit20-do
bandit20 is the owner, it can read, write to the file.
But it has the setuid bit (s) set.
The goal is to read /etc/bandit_pass/bandit20.
You can see that only bandit20 has permissions to read the file.
ls -al /etc/bandit_pass/bandit20
-r-------- 1 bandit20 bandit20 33 Oct 14 09:25 /etc/bandit_pass/bandit20
And sadly we are bandit19, which makes it impossible to read /etc/bandit_pass/bandit20.
id
uid=11019(bandit19) gid=11019(bandit19) groups=11019(bandit19)
In order to /etc/bandit_pass/bandit20 we need the have the uid of bandit20.
There isn’t a way to set the uid to bandit20, however when a setuid bit is set, Linux or Unix system elevates privileges so that even though we don’t have the uid of bandit20 it allows us to have privileges of the program’s owner (bandit20).
The uid when you run the command id is called RUID(Real User ID), you get this uid when you login to your *nix system and without a setuid bit set, this never changes.
However when you run a setuid binary the process changes its EUID(Effective User ID) which was the same as RUID(bandit19) to the owner of the special binary executable, which in our case is bandit20.
Therefore if we use the setuid binary we can read /etc/bandit_pass/bandit20 as if we are bandit20 although we aren’t.
./bandit20-do cat /etc/bandit_pass/bandit20
0qXahG8ZjOVMN9Ghs7iOWsCfZyXOUbYO
Setuid, especially setuid(0) is used a lot, because since 0 is the uid for root, running a setuid(0) binary grants us escalated privileges as if we were root although we’re not, which can cause damages to the system by tinkering on files only root should have permission to.
https://unix.stackexchange.com/questions/191940/difference-between-owner-root-and-ruid-euid
Level20->Level21
This time we get another setuid ELF file.
file suconnect
suconnect: setuid ELF 32-bit LSB executable, Intel 80386, version 1 (SYSV), dynamically linked, interpreter /lib/ld-linux.so.2, BuildID[sha1]=a95f034b2749e585fbeed4f260f85a4b150934c2, for GNU/Linux 3.2.0, not stripped
Here’s the usage of the setuid binary.
./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 bac
In order to get to the next level we need to connect to localhost on a port and send the password of bandit20.
We can use pass the bandit19 password with a pipe and use nc -l listen option to listen to a certain port.
Here’s the man page for nc -l.
-l Listen for an incoming connection rather than initiating a connection to a remote host. The destination and port to listen on can be specified either as non-optional arguments, or with options -s and -p respectively. Cannot be used together with -x or -z. Additionally, any timeouts specified with the -w option are ignored.
You need to run the nc command in the background using & because, without it the nc command will wait for an incoming connection which will block you from using your terminal.
That won’t enable you to run the setuid binary, which we need to run to make a TCP connection to localhost.
echo 0qXahG8ZjOVMN9Ghs7iOWsCfZyXOUbYO | nc -lp 1234 &
./suconnect 1234
Read: 0qXahG8ZjOVMN9Ghs7iOWsCfZyXOUbYO
Password matches, sending next password
EeoULMCra2q0dSkYj561DX7s1CpBuOBt
Level21->Level22
ssh -p 2220 bandit21@bandit.labs.overthewire.org
The description tells you to check the /etc/cron.d directory to figure out what commands are being executed.
cat cronjob_bandit22
@reboot bandit22 /usr/bin/cronjob_bandit22.sh &> /dev/null
* * * * * bandit22 /usr/bin/cronjob_bandit22.sh &> /dev/null
It runs a shellscript in the background and sends the output to /dev/null.
Let’s check what commands the shellscript is actually executing.
cat /usr/bin/cronjob_bandit22.sh
#!/bin/bash
chmod 644 /tmp/t7O6lds9S0RqQh9aMcz6ShpAoZKF7fgv
cat /etc/bandit_pass/bandit22 > /tmp/t7O6lds9S0RqQh9aMcz6ShpAoZKF7fgv
It changed the permission of a directory under /tmp and redirected the password to it.
Just reading the folder under /tmp will grant you the password.
cat /tmp/t7O6lds9S0RqQh9aMcz6ShpAoZKF7fgv
tRae0UfB9v0UzbCdn9cY0gQnds9GF58Q
Level22->Level23
ssh -p 2220 bandit21@bandit.labs.overthewire.org
The description tells us to check the /etc/cron.d directory again.
cat cronjob_bandit23
@reboot bandit23 /usr/bin/cronjob_bandit23.sh &> /dev/null
* * * * * bandit23 /usr/bin/cronjob_bandit23.sh &> /dev/null
Here’s the shellscript.
cat /usr/bin/cronjob_bandit23.sh
#!/bin/bash
myname=$(whoami)
mytarget=$(echo I am user $myname | md5sum | cut -d ' ' -f 1)
echo "Copying passwordfile /etc/bandit_pass/$myname to /tmp/$mytarget"
cat /etc/bandit_pass/$myname > /tmp/$mytarget
In order to understand the shellscript you need to know what the variable whoami, myname and mytarget is.
Then you can read the password since it redirects it using cat.
The whoami command returns who you are logged in as, we are currently bandit22 but, since we want to get the password of bandit23.
It would be a good idea to set myname to bandit23.
The intimidating part is figuring out the value of mytarget because it pipes twice with md5sum and the cut command.
The thing is you don’t have to understand each command, although it’s best to understand it.
We can just substitute bandit23 to myname.
echo I am user bandit23 | md5sum | cut -d ' ' -f 1
8ca319486bfbbc3663ea0fbe81326349
Since the value of mytarget is the directory under /tmp that holds the password we can read it using cat.
cat /tmp/8ca319486bfbbc3663ea0fbe81326349
0Zf11ioIjMVN551jX3CmStKLYqjk54Ga
When I first solved this level I got stuck, I though passing bandit22 would give me the password since my username is bandit22.
Level23->Level24
ssh -p 2220 bandit23@bandit.labs.overthewire.org
In this level we neet to check /etc/cron.d/ but we need to write a shellscript.
Here’s the crontab file.
cat /usr/bin/cronjob_bandit24.sh
#!/bin/bash
myname=$(whoami)
cd /var/spool/$myname/foo
echo "Executing and deleting all scripts in /var/spool/$myname/foo:"
for i in * .*;
do
if [ "$i" != "." -a "$i" != ".." ];
then
echo "Handling $i"
owner="$(stat --format "%U" ./$i)"
if [ "${owner}" = "bandit23" ]; then
timeout -s 9 60 ./$i
fi
rm -f ./$i
fi
done
I created a folder under /tmp.
mktemp -d
/tmp/tmp.bb5asiXtP5
cd /tmp/tmp.bb5asiXtP5
Then I wrote a shellscript like this and made a file named password.
#!/bin/bash
cat /etc/bandit_pass/bandit24 > /tmp/tmp.bb5asiXtP5/password
Then add permission to the directory and the bash script.
chmod +x solve.sh
chmod 777 -R /tmp/tmp.bb5asiXtP5
Copy the shell script to the directory where the crontab is running.
cp /tmp/tmp.bb5asiXtP5/solve.sh /var/spool/bandit24/foo
Wait for a minute and crontab will show you the password.
cat password
gb8KRRCsshuZXI0tUuR6ypOFjiZbf3G8
This level is pretty hard as well.
Level24->Level25
ssh -p 2220 bandit23@bandit.labs.overthewire.org
We need to connect to localhost on port 30002.
Then we need to pass bandit25’s password and a pincode , which is a 4 digit numeric code.
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.
gb8KRRCsshuZXI0tUuR6ypOFjiZbf3G8 1234
Wrong! Please enter the correct current password and pincode. Try again.
Since the pincodes can be anything from 0000~9999 connecting to localhost manually and entering the passcodes will practically be impossible.
To automate this we can make a shellscript.
mktemp -d
/tmp/tmp.6kBoMhU2mj
cd /tmp/tmp.6kBoMhU2mj
You can write a shellscript that generates all the possible chocies and save it into a file named pincodes.
#!/bin/bash
PASSWORD=gb8KRRCsshuZXI0tUuR6ypOFjiZbf3G8
for i in {0000..9999}; do
echo $PASSWORD $i >> pincodes
done
Add the execute permission and run the shellscript.
chmod +x solve.sh
./solve.sh
You’ll see that the file pincodes is created, then you can pipe the pincodes to netcat.
nc localhost 30002 < pincodes
It’ll show a lot of Wrong! Please enter the correct current password and pincode. Try again., but at the very end you’ll see the password for bandit25.
Correct!
The password of user bandit25 is iCi86ttT4KSNe1armKiwbQNmB3YJP3q4
If you only want to capture the password you can use the grep -v (invert) option.
nc localhost 30002 < pincodes | grep -v "Wrong! Please enter the correct current password and pincode. Try again."
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.
Correct!
The password of user bandit25 is iCi86ttT4KSNe1armKiwbQNmB3YJP3q4
Level25->Level26
ssh -p 2220 bandit25@bandit.labs.overthewire.org
A sshkeyfile (bandit26.sshkey) is given, since we can’t ssh from the bandit itself we need to copy the ssh key locally then ssh from there.
Change the permissions for the ssh key or else you’ll get complaints from bandit that you get way too much permission for the ssh key file.
chmod 400 bandit26.sshkey
ssh into bandit26 using the ssh key.
ssh -p 2220 -i bandit26.sshkey bandit26@bandit.labs.overthewire.org
For some reason bandit automatically closes the connection when we ssh into it.
Since it constantly disconnects us, let’s try sshing to bandit25 instead.
ssh -p 2220 bandit25@bandit.labs.overthewire.org
The command below shows what the default shell is when we login and unlike bandit25, bandit26 doens’t use bash it uses someting else called showtext (/usr/bin/showtext).
cat /etc/passwd | grep bandit26
bandit26:x:11026:11026:bandit level 26:/home/bandit26:/usr/bin/showtext
cat /etc/passwd | grep bandit25
bandit25:x:11025:11025:bandit level 25:/home/bandit25:/bin/bash
Let’s check out what showtext is.
It’s running a custom shellscript by loading text.txt and exporting it as a custom shell.
By using the more command it only shows a tiny part of the shell, that’s why it probably didn’t show the full terminal.
cat /usr/bin/showtext
#!/bin/sh
export TERM=linux
exec more ~/text.txt
exit 0
So in order for the more command to show the output on yout terminal make your teminall really small.
When you’re shell is running the more command, type v this will invoke vim.
Now that we are running vim we can then type :set shell=/bin/bash, this will set the system’s shell to bash.
Then type :shell in vim, this will open the default shell on the Linux machine.
Let’s check the uid or the user, you can check that it’s bandit26.
Since the uid or RUID is bandit26 we can read the password for bandit26.
You can check the system’s shell using thee shell variable $0.
id
uid=11026(bandit26) gid=11026(bandit26) groups=11026(bandit26)
cat /etc/bandit_pass/bandit26
s0773xxkk0MXfdqOfPRVr9L3jJBUOgCZ
echo $0
/bin/bash
Level26->Level27
We get a setuid binary again.
file bandit27-do
bandit27-do: setuid ELF 32-bit LSB executable, Intel 80386, version 1 (SYSV), dynamically linked, interpreter /lib/ld-linux.so.2, BuildID[sha1]=35d353cf6d732f515a73f50ed205265fe1e68f90, for GNU/Linux 3.2.0, not stripped
ls -al bandit27-do
-rwsr-x--- 1 bandit27 bandit26 14884 Oct 14 09:26 bandit27-do
Pretty similar to the previous setuid levels.
./bandit27-do cat /etc/bandit_pass/bandit27
upsNCc7vzaRDx6oZC6GiR6ERwe1MowGB
Level27->Level28
From Level27 we need to git clone the remote repository using ssh.
I’ve never git cloned with ssh before so it was a bit tricky for especially, I had some trouble where to write the port number.
git clone ssh://bandit27-git@bandit.labs.overthewire.org:2220/home/bandit27-git/repo
When we clone the remote repository locally, we get a directory named repo.
Inside it is a README file
Let’s cat the README file.
cat README
The password to the next level is: Yz9IpL0sBcCeuG7m9uQFt8ZNpS4HZRcN
Level28->Level29
You’ll also get this error a lot when you try to delete the repository after running git clone.
You get this prompt when you try to delete a file that is write-protected(read-only).
rm -r repo/
override r--r--r-- hwkim301/staff for repo/.git/objects/pack/pack-0ff3d40def5e19d999887ad50b7aa6578b7bd2d6.pack?
If you don’t want to see the prompt pop up you can use rm-rf which forces deletion.
Or you can give all the files in the /repo directory write permission and the delete the files without forcing deletion.
chmod -R u+w repo/
rm -r repo/
Or instead of entering yes after rm -r you can pipe it as well.
yes | rm -r repo/
Lol I went down the rabbit hole again.
git clone ssh://bandit28-git@bandit.labs.overthewire.org:2220/home/bandit28-git/repo
Here’s the README file.
cat README.md
# Bandit Notes
Some notes for level29 of bandit.
## credentials
- username: bandit29
- password: xxxxxxxxxx
The README file was the only file I could find in the /repo directory.
Since git is a version control system we can go back in time to see how the files in the directory were modified in the past.
The git log shows the logs of how the file has changed.
git log
commit b0354c7be30f500854c5fc971c57e9cbe632fef6 (HEAD -> master, origin/master, origin/HEAD)
Author: Morla Porla <morla@overthewire.org>
Date: Tue Oct 14 09:26:19 2025 +0000
fix info leak
commit d0cf2ab7dd7ebc6075b59102a980155268f0fe8f
Author: Morla Porla <morla@overthewire.org>
Date: Tue Oct 14 09:26:19 2025 +0000
add missing data
commit bd6bc3a57f81518bb2ce63f5816607a754ba730d
Author: Ben Dover <noone@overthewire.org>
Date: Tue Oct 14 09:26:18 2025 +0000
It looks like the previous commit showed the password.
Someone probably updated the file so it wouldn’t contain the passsword.
Although, we can’t see the password right now because the new commit got rid of the password, we can got back to previous commits using the git checkout command.
Since the second commit text is add missing data checking out to that commit will likely show us the password.
git checkout d0cf2ab7dd7ebc6075b59102a980155268f0fe8f
Note: switching to 'd0cf2ab7dd7ebc6075b59102a980155268f0fe8f'.
You are in 'detached HEAD' state. You can look around, make experimental
changes and commit them, and you can discard any commits you make in this
state without impacting any branches by switching back to a branch.
If you want to create a new branch to retain commits you create, you may
do so (now or later) by using -c with the switch command. Example:
git switch -c <new-branch-name>
Or undo this operation with:
git switch -
Turn off this advice by setting config variable advice.detachedHead to false
HEAD is now at d0cf2ab add missing data
Using the cat command will reveal the password.
cat README.md
# Bandit Notes
Some notes for level29 of bandit.
## credentials
- username: bandit29
- password: 4pT1t5DENaYuqnqvadYs1oE4QLCdjmJ7
Level29->Level30
git clone ssh://bandit29-git@bandit.labs.overthewire.org:2220/home/bandit29-git/repo
This time, the README file doesn’t contain any passwords.
cat README.md
# Bandit Notes
Some notes for bandit30 of bandit.
## credentials
- username: bandit30
- password: <no passwords in production!>
Here’s the result of git log, there is a previous commit.
git log
commit b879c94bd4641ebb8b5470258b3a41debb25f7c2 (HEAD -> master, origin/master, origin/HEAD)
Author: Ben Dover <noone@overthewire.org>
Date: Tue Oct 14 09:26:20 2025 +0000
fix username
commit 358fb1e671f460043ff5bd372e8d87e228dc148d
Author: Ben Dover <noone@overthewire.org>
Date: Tue Oct 14 09:26:20 2025 +0000
initial commit of README.md
git checkout 358fb1e671f460043ff5bd372e8d87e228dc148d
Note: switching to '358fb1e671f460043ff5bd372e8d87e228dc148d'.
You are in 'detached HEAD' state. You can look around, make experimental
changes and commit them, and you can discard any commits you make in this
state without impacting any branches by switching back to a branch.
If you want to create a new branch to retain commits you create, you may
do so (now or later) by using -c with the switch command. Example:
git switch -c <new-branch-name>
Or undo this operation with:
git switch -
Turn off this advice by setting config variable advice.detachedHead to false
HEAD is now at 358fb1e initial commit of README.md
Sadly, checking out and going to the previous commits and reading the README file doesn’t show the password at all.
cat README.md
# Bandit Notes
Some notes for bandit30 of bandit.
## credentials
- username: bandit29
- password: <no passwords in production!>
Now we’ll have to use other git commands.
This time we can use the git branch command.
For more information you should read this.
git branch -a
* (HEAD detached at 358fb1e)
master
remotes/origin/HEAD -> origin/master
remotes/origin/dev
remotes/origin/master
remotes/origin/sploits-dev
Then try checking out to other branches, as try reading the README file.
git checkout remotes/origin/dev
cat README.md
# Bandit Notes
Some notes for bandit30 of bandit.
## credentials
- username: bandit30
- password: qp30ex3VLz5MDG1n91YowTv4Q8l7CDZL
Level30->Level31
git clone ssh://bandit30-git@bandit.labs.overthewire.org:2220/home/bandit30-git/repo
There isn’t much we can do with the README.md file.
cat README.md
just an epmty file... muahaha
Let’s try some other git commands like git log and git show.
git log
commit d604df2303c973b8e0565c60e4c29d3801445299 (HEAD -> master, origin/master, origin/HEAD)
Author: Ben Dover <noone@overthewire.org>
Date: Tue Oct 14 09:26:28 2025 +0000
git show
commit d604df2303c973b8e0565c60e4c29d3801445299 (HEAD -> master, origin/master, origin/HEAD)
Author: Ben Dover <noone@overthewire.org>
Date: Tue Oct 14 09:26:28 2025 +0000
initial commit of README.md
diff --git a/README.md b/README.md
new file mode 100644
index 0000000..029ba42
--- /dev/null
+++ b/README.md
@@ -0,0 +1 @@
+just an epmty file... muahaha
There isn’t much too do and I’ll just show the answer.
You need to use the git tag command.
What is a git tag?
According to Reddit, a tag is a label that points to a commit that doesn’t move.
git tag
secret
By using the git show command you can get the password.
git show secret
fb5S2xb7bRyFmAvQYQGEqsbhVyJqhnDy
Level31->Level32
git clone ssh://bandit31-git@bandit.labs.overthewire.org:2220/home/bandit31-git/repo
Here’s the README file, according to the README file we need to git push to the remote repository.
cat README.md
This time your task is to push a file to the remote repository.
Details:
File name: key.txt
Content: 'May I come in?'
Branch: master
We need to push the key.txt and the content of the file should be 'May I come in?'.
echo 'May I come in?' > key.txt
To upload a file to a remote repository the following steps are the usual procedure.
- git add
The git add command sends files to the staging area.
- git commit
The git commit command permanently store changes made to the files we selected using git add as a node in the git tree.
- git push
The git push command copies the local commits that do not exist in the remote repository to the remoe repository.
https://stackoverflow.com/questions/26005031/what-does-git-push-do-exactly
However when trying to git push it gets an error.
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.
It looks like the .gitignore file is blocking us from adding the key.txt we need to push.
A .gitignore file tells git which files not to track.
https://stackoverflow.com/questions/27850222/what-is-gitignore
The .gitignore file currently doesn’t track any txt files.
cat .gitignore
*.txt
I got rid of the .gitignore file
rm .gitignore
Now we can git add key.txt without any problems.
git add key.txt
Then we need to git commit the file.
git commit -m 'push key.txt to remote'
Finally, we can git push to origin master.
git push origin master
Although we failed to push key.txt we get the password to the next level.
git push origin master
_ _ _ _
| |__ __ _ _ __ __| (_) |_
| '_ \ / _` | '_ \ / _` | | __|
| |_) | (_| | | | | (_| | | |_
|_.__/ \__,_|_| |_|\__,_|_|\__|
This is an OverTheWire game server.
More information on http://www.overthewire.org/wargames
backend: gibson-0
bandit31-git@bandit.labs.overthewire.org's password:
Enumerating objects: 4, done.
Counting objects: 100% (4/4), done.
Delta compression using up to 12 threads
Compressing objects: 100% (2/2), done.
Writing objects: 100% (3/3), 329 bytes | 329.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: Well done! Here is the password for the next level:
remote: 3O9RfhqyAlVBEZpVb6LYStshZoqoSx5K
remote:
remote: .oOo.oOo.oOo.oOo.oOo.oOo.oOo.oOo.oOo.oOo.
remote:
To ssh://bandit.labs.overthewire.org:2220/home/bandit31-git/repo
! [remote rejected] master -> master (pre-receive hook declined)
error: failed to push some refs to 'ssh://bandit.labs.overthewire.org:2220/home/bandit31-git/repo'
Level32->Level33
Now we’re back to sshing to the remote server, no more git cloning.
ssh -p 2220 bandit32@bandit.labs.overthewire.org
This time we get an uppercase shell that, interprets all the commands as CAPITALS.
WELCOME TO THE UPPERCASE SHELL
>>
Since the shell transforms all the commands to capitals we can’t use the uppercase shell.
>> cat /etc/bandit_pass/bandit32
sh: 1: CAT: Permission denied
In shell scripting we can use the $0 variable to invoke the default shell.
https://unix.stackexchange.com/questions/280454/what-is-the-meaning-of-0-in-the-bash-shell
You can see how executing $0 gave us a $ sign.
>> $0
$
The default shell bandit uses is the Bourne Shell.
echo $0
sh
Since our uid is bandit33 we can read the password for bandit33.
$ id
uid=11033(bandit33) gid=11032(bandit32) groups=11032(bandit32)
cat /etc/bandit_pass/bandit33
tQdtbs5D5i2vJwkO8mEyYEyTL8izoeJ0
Level33->Level34
That’s a wrap!.
Successfully completed the bandit wargame.
ssh -p 2220 bandit33@bandit.labs.overthewire.org
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!