The Bandit wargame is aimed at absolute beginners. It teaches the basics needed to play other wargames — SSH, file manipulation, piping, permissions, cron, git, and more. 33 levels, from zero to shell escape.
Connect to the Bandit server and read the readme file in the home directory.
$ ssh bandit0@bandit.labs.overthewire.org -p 2220
bandit0@bandit:~$ cat readme
NH2SXQwcBdpmTEzi3bvBHMM9H66vVXjL
The filename is a single dash (-). Prefix with ./ to avoid it being interpreted as stdin.
bandit1@bandit:~$ cat ./-
rRGizSaX8Mk1RTb1CNQoXTcYZWU6lgzi Escape spaces with backslashes or wrap the filename in quotes.
bandit2@bandit:~$ cat ./spaces\ in\ this\ filename
aBZ0W5EmUfAf7kHTQeOwd8bauFJ2lAiG
The file is hidden (dotfile) inside the inhere directory.
bandit3@bandit:~/inhere$ cat .hidden
2EW7BBsr6aMMoJ2HjW067dm8EgX26xNe
Multiple files in inhere/. Grep recursively for ASCII content.
bandit4@bandit:~/inhere$ grep -Ri [a-z]
-file07:lrIWWI6bB37kxfiCQZqUdOIYfr6eEeqR Find the file that is exactly 1033 bytes.
bandit5@bandit:~/inhere$ find ./ -size 1033c -exec "cat" {} +
P4L4vucdmLnm8I7Vl7jG1ApGSfjYKqJU Search the entire filesystem for a file owned by user bandit7, group bandit6, 33 bytes.
bandit6@bandit:~$ find / -group bandit6 -user bandit7 -size 33c -exec "cat" {} + 2>/dev/null
z7WtoNQU2XfjmMtWA8u5rN4vzqu4v99S
The password is on the line containing the word "millionth" in data.txt.
bandit7@bandit:~$ cat data.txt | grep millionth
millionth TESKZC0XvTetK0S9xNwm25STk5iWrBvP
Find the line that occurs only once in data.txt.
bandit8@bandit:~$ grep -vf <(sort data.txt | uniq -d) data.txt
EN632PlfYiZbn3PhVK3XOGSlNInNE00t
Extract human-readable strings and grep for lines starting with ==.
bandit9@bandit:~$ strings data.txt | grep ^==
========== password
========== is
========== G7w8LIi6J3kTb8A7j9LgrywtEUlyyp6s bandit10@bandit:~$ cat data.txt | base64 -d
The password is 6zPeziLdR2RKNdNYFNb6nVCKzphlXHBM bandit11@bandit:~$ cat data.txt | rot13
The password is JVNBBFSmZwKKOP0XbFXOoW8chDz5yVRv A hex dump that has been compressed multiple times. Reverse the hex dump, then repeatedly decompress (gzip, bzip2, tar) until you reach the ASCII password.
# reverse the hex dump
bandit12@bandit:/tmp/13$ cat data.txt | xxd -r > data
bandit12@bandit:/tmp/13$ file data
data: gzip compressed data, was "data2.bin"
# round 1 — gzip
bandit12@bandit:/tmp/13$ mv data data2.gz
bandit12@bandit:/tmp/13$ gunzip data2.gz
bandit12@bandit:/tmp/13$ file data2
data2: bzip2 compressed data, block size = 900k
# round 2 — bzip2
bandit12@bandit:/tmp/13$ mv data2 data3.bz
bandit12@bandit:/tmp/13$ bzip2 -d data3.bz
bandit12@bandit:/tmp/13$ file data3
data3: gzip compressed data, was "data4.bin"
# round 3 — gzip
bandit12@bandit:/tmp/13$ mv data3 data4.gz
bandit12@bandit:/tmp/13$ gunzip data4.gz
bandit12@bandit:/tmp/13$ file data4
data4: POSIX tar archive (GNU)
# round 4 — tar
bandit12@bandit:/tmp/13$ mv data4 data5.tar
bandit12@bandit:/tmp/13$ tar -xf data5.tar
bandit12@bandit:/tmp/13$ file data5.bin
data5.bin: POSIX tar archive (GNU)
# round 5 — tar
bandit12@bandit:/tmp/13$ mv data5.bin data6.tar
bandit12@bandit:/tmp/13$ tar -xf data6.tar
bandit12@bandit:/tmp/13$ file data6.bin
data6.bin: bzip2 compressed data, block size = 900k
# round 6 — bzip2
bandit12@bandit:/tmp/13$ mv data6.bin data6.bz
bandit12@bandit:/tmp/13$ bzip2 -d data6.bz
bandit12@bandit:/tmp/13$ file data6
data6: POSIX tar archive (GNU)
# round 7 — tar
bandit12@bandit:/tmp/13$ mv data6 data6.tar
bandit12@bandit:/tmp/13$ tar -xf data6.tar
bandit12@bandit:/tmp/13$ file data8.bin
data8.bin: gzip compressed data, was "data9.bin"
# round 8 — gzip (final)
bandit12@bandit:/tmp/13$ mv data8.bin data8.gz
bandit12@bandit:/tmp/13$ gunzip -d data8.gz
bandit12@bandit:/tmp/13$ file data8
data8: ASCII text
bandit12@bandit:/tmp/13$ cat data8
The password is wbWdlBxEir4CaE8LaPhauuOo6pwRmrDw No password this time — use the private key found in the home directory to SSH as bandit14.
bandit13@bandit:~$ ls
sshkey.private
$ chmod 600 id_rsa
$ ssh -i ./.ssh/id_rsa bandit14@bandit.labs.overthewire.org -p 2220 Submit the current level's password to localhost port 30000 to receive the next password.
bandit14@bandit:/$ cat /etc/bandit_pass/bandit14
fGrHPx402xGC7U7rXKDaxiWFTOiF0ENq
bandit14@bandit:/$ nc localhost 30000
fGrHPx402xGC7U7rXKDaxiWFTOiF0ENq
Correct!
jN2kgmIXJ6fShzhT2avhotn4Zcka6tnt Same concept as level 14 but the connection must be over SSL.
bandit15@bandit:~$ openssl s_client -ign_eof -connect localhost:30001
---
read R BLOCK
jN2kgmIXJ6fShzhT2avhotn4Zcka6tnt
Correct!
JQttfApK4SeyHwDlI9SXGR50qclOAil1 Scan ports 31000–32000, identify the SSL service that is not an echo server, and submit the password. The correct port returns an RSA private key for the next level.
bandit16@bandit:~$ nmap -p 31000-32000 localhost
PORT STATE SERVICE
31046/tcp open unknown
31518/tcp open unknown
31691/tcp open unknown
31790/tcp open unknown
31960/tcp open unknown
bandit16@bandit:~$ nmap -A -p 31046,31518,31691,31790,31960 localhost
...
31790/tcp open ssl/unknown
→ "Wrong! Please enter the correct current password"
# port 31790 is the target (not an echo server)
bandit16@bandit:~$ openssl s_client -connect localhost:31790 -ign_eof
read R BLOCK
JQttfApK4SeyHwDlI9SXGR50qclOAil1
Correct!
-----BEGIN RSA PRIVATE KEY-----
MIIEogIBAAKCAQEAvmOkuifmMg6HL2YPIOjon6iWfbp7c3jx34YkYWqUH57SUdyJ
... (save as id_rsa, chmod 600, SSH as bandit17)
-----END RSA PRIVATE KEY-----
$ ssh -i ./.ssh/id_rsa bandit17@bandit.labs.overthewire.org -p 2220
Use diff to find the changed line between two password files.
Level 18's .bashrc kicks you out on login, so execute commands directly via SSH.
bandit17@bandit:~$ diff passwords.new passwords.old
42c42
< hga5tuuCLF6fFzUpnagiMN8ssu9LFrdg
---
> f9wS9ZUDvZoo3PooHgYuuWdawDFvGld2 $ ssh bandit18@bandit.labs.overthewire.org -p 2220 "cat readme"
awhqfNnAbc1naukrpqDYcF95h7HoMTrC A setuid binary runs commands as bandit20. Use it to read the password file.
bandit19@bandit:~$ ./bandit20-do whoami
bandit20
bandit19@bandit:~$ ./bandit20-do cat /etc/bandit_pass/bandit20
VxCazJaVykI6W36BkBU0mJTCM8rR95XT
The suconnect binary connects to a given port on localhost.
If it receives the current password, it sends back the next one.
Use two terminals — one running nc as a listener.
bandit20@bandit:~$ ./suconnect 9093
Read: VxCazJaVykI6W36BkBU0mJTCM8rR95XT
Password matches, sending next password bandit20@bandit:~$ nc -nlvp 9093
Listening on 0.0.0.0 9093
Connection received on 127.0.0.1 53696
VxCazJaVykI6W36BkBU0mJTCM8rR95XT
NvEJF7oVjkddltPSrdKEFOllh9V1IBcq
A cron job writes the bandit22 password to a file in /tmp.
bandit21@bandit:~$ cat /etc/cron.d/cronjob_bandit22
@reboot bandit22 /usr/bin/cronjob_bandit22.sh &> /dev/null
bandit21@bandit:~$ cat /usr/bin/cronjob_bandit22.sh
#!/bin/bash
chmod 644 /tmp/t7O6lds9S0RqQh9aMcz6ShpAoZKF7fgv
cat /etc/bandit_pass/bandit22 > /tmp/t7O6lds9S0RqQh9aMcz6ShpAoZKF7fgv
bandit21@bandit:~$ cat /tmp/t7O6lds9S0RqQh9aMcz6ShpAoZKF7fgv
WdDozAdTM2z9DiFEQ2mGlwngMfj4EZff
The cron script hashes "I am user $myname" with md5sum to derive the tmp path.
Substitute bandit23 to find the correct file.
bandit22@bandit:~$ 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
bandit22@bandit:~$ echo I am user bandit23 | md5sum | cut -d ' ' -f 1
8ca319486bfbbc3663ea0fbe81326349
bandit22@bandit:~$ cat /tmp/8ca319486bfbbc3663ea0fbe81326349
QYw0Y2aiA672PsMmh9puTQuhoz8SyR2G
The cron job executes and deletes scripts owned by bandit23 from /var/spool/bandit24/foo.
Write a script that copies the next password to a readable location.
bandit23@bandit:/tmp/shell23$ cat script.sh
#!/bin/bash
cat /etc/bandit_pass/bandit24 >> /tmp/shell23/bandit24.txt
bandit23@bandit:/tmp/shell23$ chmod 777 script.sh
bandit23@bandit:/tmp/shell23$ cp script.sh /var/spool/bandit24/foo
# wait for cron to execute...
bandit23@bandit:/tmp/shell23$ cat bandit24.txt
VAfGXJ1PBSsPSnvsjI8p759leLZ9GGar A daemon on port 30002 requires the level 24 password plus a 4-digit pin. Brute force all 10,000 combinations.
#!/bin/bash
for i in {0000..9999}; do
echo "VAfGXJ1PBSsPSnvsjI8p759leLZ9GGar" $i
done | nc localhost 30002 | grep -v "Wrong" bandit24@bandit:/tmp/bf$ ./script.sh
Correct!
The password of user bandit25 is p7TaowMYrmu23Ol8hiZh9UvD0O9hpx8d
Bandit26's shell is not /bin/bash — it runs
more ~/text.txt then exits.
Shrink the terminal so more pauses, press v to enter vim,
then :set shell=/bin/bash followed by :shell.
bandit25@bandit:~$ cat /etc/passwd | grep bandit26
bandit26:x:11026:11026:bandit level 26:/home/bandit26:/usr/bin/showtext
bandit25@bandit:~$ cat /usr/bin/showtext
#!/bin/sh
export TERM=linux
exec more ~/text.txt
exit 0
# SSH with the key, shrink terminal so more pauses at --More--(83%)
# press v → opens vim
# :set shell=/bin/bash
# :shell
bandit26@bandit:~$ cat /etc/bandit_pass/bandit26
c7GvcKlw9mC7aUQaPx7nwFstuAIBw1o1 Same pattern as level 19. A setuid binary runs commands as bandit27.
bandit26@bandit:~$ ./bandit27-do whoami
bandit27
bandit26@bandit:~$ ./bandit27-do cat /etc/bandit_pass/bandit27
YnQpBuifNMas1hcUFk70ZmqkhUU2EuaS Clone the git repo and read the README.
bandit27@bandit:/tmp/sahin$ git clone ssh://bandit27-git@localhost:2220/home/bandit27-git/repo
...
Receiving objects: 100% (3/3), done.
bandit27@bandit:/tmp/sahin/repo$ cat README
The password to the next level is: AVanL161y9rsbcJIsFHuw35rjaOM19nR The password was removed in a later commit ("fix info leak"). Check out the earlier commit to read it.
bandit28@bandit:/tmp/sahin28/repo$ git log --oneline
104db85 (HEAD) fix info leak
6c3c5e4 add missing data
cd3b97e initial commit of README.md
bandit28@bandit:/tmp/sahin28/repo$ git checkout 6c3c5e4
bandit28@bandit:/tmp/sahin28/repo$ cat README.md
...
- username: bandit29
- password: tQKvmcwNYcFS6vmPHIUSI3ShmsrQZK8S
No password in git history — check other branches. The dev branch has the credentials.
bandit29@bandit:/tmp/sahin29/repo$ git show-ref
0afe350 refs/heads/master
0afe350 refs/remotes/origin/HEAD
fbbce0e refs/remotes/origin/dev
0afe350 refs/remotes/origin/master
746e423 refs/remotes/origin/sploits-dev
bandit29@bandit:/tmp/sahin29/repo$ git checkout fbbce0e
HEAD is now at fbbce0e add data needed for development
bandit29@bandit:/tmp/sahin29/repo$ cat README.md
...
- username: bandit30
- password: xbhV3HpNGlTIdnjUrdAlPzc2L6y9EOnS No useful commits or branches. The password is stored in a git tag.
bandit30@bandit:/tmp/sahin30/repo$ git tag
secret
bandit30@bandit:/tmp/sahin30/repo$ git show secret
OoffzGDlzhAlerFJ2cAiz1D41JW1Mhmt
The README asks you to push a file named key.txt with content
"May I come in?" to master. The .gitignore blocks *.txt,
so force-add it.
bandit31@bandit:/tmp/sahin31/repo$ echo "May I come in?" > key.txt
bandit31@bandit:/tmp/sahin31/repo$ git add -f key.txt
bandit31@bandit:/tmp/sahin31/repo$ git commit -m "key.txt"
bandit31@bandit:/tmp/sahin31/repo$ git push origin master
...
remote: .oOo.oOo.oOo.oOo.oOo.oOo.oOo.oOo.oOo.oOo.
remote:
remote: Well done! Here is the password for the next level:
remote: rmCBvG56y58BXzv98yZGdO7ATVL5dW8y
The "UPPERCASE SHELL" converts all input to uppercase, breaking every command.
Shell variables like $USER still expand.
The trick: $0 refers to the current shell binary — executing it spawns a proper shell.
>> $USER
sh: 1: bandit32: not found
>> $PATH
sh: 1: /usr/local/sbin:/usr/local/bin:...: not found
>> $SHELL
WELCOME TO THE UPPERCASE SHELL
# $0 expands to the shell binary — no uppercase issue
>> $0
$ whoami
bandit33
$ cat /etc/bandit_pass/bandit33
odHo63fHiFqcWWJG9rLiLDtPm45KzUKy