Login using SSH! To start the Bandit wargame you have to use the ssh command
along with the provided credentials to login. The command to do this would be:
ssh [email protected] -p 2220.
bandit0 is the username, bandit.labs.overthewire.org is the host, and
-p 2220 specifies the port.
Level 0->1
The password for the next level is stored in a file called readme in the home
directory. To get the password I simply used the cat command to display its
contents: cat readme.
Level 1->2
In this challenge the password is stored in a file called - (dash). A simple
cat - will not work because the dash is interpreted as standard input
rather than a filename. To specify it as a file I used the path explicitly:
cat ./-. The ./ tells the shell that - is a file in the current
directory.
Level 2->3
The password is stored in a file whose name contains spaces. To cat a file with
spaces in its name you need to wrap the name in quotes so the shell treats it
as a single argument. I used: cat ./"--spaces in this filename--".
Level 3->4
The password is hidden inside the inhere directory. I used cd inhere to
navigate there, then ls -a to reveal hidden files — files whose names
start with a dot are hidden by default. This revealed a file called
...Hiding-From-You. I read it with cat ./...Hiding-From-You.
Level 4->5
The password is stored in the only human-readable file across several files in
the inhere directory. Rather than cat-ing each one individually, I used
find . -type f -exec file {} + which runs the file command on every
file and reports its type. This quickly identified two ASCII text files:
./inhere/-file07 and a few shell config files. I then used
cat inhere/-file07 to retrieve the password.
Level 5->6
The password file has three properties: human-readable, 1033 bytes in size, and
not executable. I used find with flags to match all three:
find . -type f -size 1033c ! -executable -exec file {} +.
The -size 1033c flag specifies 1033 bytes (c = bytes) and
! -executable excludes executable files. This pointed me to
./inhere/maybehere07/.file2, which I read with cat.
Level 6->7
The file is stored somewhere on the server and is owned by user bandit7, group
bandit6, and is 33 bytes in size. I searched the whole filesystem with:
find / -size 33c -user bandit7 -group bandit6 2> /dev/null.
The 2> /dev/null redirects stderr to /dev/null, which removes all the
permission denied noise and leaves only the result. The file was at
/var/lib/dpkg/info/bandit7.password.
Level 7->8
The password is stored in data.txt next to the word millionth. Since the file
is large, I used grep to find the right line:
cat data.txt | grep millionth. This pipes the file contents into grep
which searches for and prints any line containing the word millionth.
Level 8->9
The password is the only line in data.txt that appears exactly once.
The uniq command can find unique lines, but it only compares adjacent
lines so the file must be sorted first. I used:
sort data.txt | uniq -u. The -u flag tells uniq to only print
lines that are not repeated.
Level 9->10
The password is one of the few human-readable strings in an otherwise binary
file, and is preceded by several = characters. I used
strings data.txt | grep = to extract all printable strings from the
file and then filter for lines containing =, which narrowed it down to the
password.
Level 10->11
The data.txt file contains base64 encoded data. I used
strings data.txt | base64 -d to extract the readable content and
decode it. The base64 -d flag decodes the base64 encoded string back
to plaintext, revealing the password.
Level 11->12
The password has been encoded with ROT13, a simple substitution cipher that
shifts every letter by 13 positions. The tr command (translate) can
map one set of characters to another. I used:
cat data.txt | tr 'A-Za-z' 'N-ZA-Mn-za-m'. This maps A to N, B to O,
and so on through the alphabet, effectively reversing the ROT13
encoding.
Level 12->13
The file data.txt is a hex dump of a file that has been compressed multiple
times. First I created a working directory with mktemp -d and copied
data.txt into it. I then used xxd -r data.txt > out.bin to reverse the
hex dump back to binary. From there I repeatedly used file out.bin to
check the compression type and decompressed accordingly — gzip files with
mv out.bin out.gz && gzip -d out.gz, bzip2 files with
bzip2 -d, and tar archives with tar -xf. I kept repeating this
process until I reached the original uncompressed file containing the
password.
Level 13->14
This level does not give a password directly — instead it provides a private
SSH key for logging into bandit14. I copied the key to a local file called
sshkey.private and then used the -i option to authenticate with it:
ssh [email protected] -p 2220 -i sshkey.private.
The -i flag specifies the identity file (private key) to use instead
of a password.
[RSA Private Key - omitted]
Level 14->15
Now logged in as bandit14 I could read the current level's password directly
from cat /etc/bandit_pass/bandit14. To get the next password I
submitted it to a service running on port 30000 using netcat:
nc localhost 30000. Pasting in the current password caused the server
to respond with the next one.
Level 15->16
Similar to the previous level but the service requires an SSL connection.
I used the openssl client to connect:
openssl s_client -crlf -connect localhost:30001. The -crlf
flag translates line feeds to CR+LF which some servers require. After the
connection was established I pasted in the current password and the server
returned the next one. Reference: OpenSSL Cookbook Chapter 2.2.
Level 16->17
First I needed to find which port in the range 31000-32000 was running an SSL
service. I used nmap to scan: nmap localhost -p 31000-32000 -sV -sC.
This identified port 31790 as the target. I connected with:
openssl s_client -crlf -connect localhost:31790 -ign_eof. The
-ign_eof flag tells openssl to ignore EOF signals and keep the TLS
connection alive instead of closing it. After submitting the current password
the server returned an RSA private key to use for the next level.
[RSA Private Key - omitted]
Level 17->18
There are two files in the home directory: passwords.old and passwords.new.
The password is the only line that differs between them. I used the diff
command to find it: diff passwords.old passwords.new. The line marked
with > is the new line added in passwords.new, which is the
password.
Level 18->19
The .bashrc has been modified to print "Byebye!" and immediately close the
connection whenever you log in, making normal SSH sessions useless. To get
around this I appended a command directly to the SSH call to spawn a shell
before .bashrc could run:
ssh [email protected] -p 2220 /bin/bash --noprofile --norc.
The --noprofile and --norc flags tell bash to skip loading any
startup files, bypassing the logout trap. From there I ran
cat readme to get the password.
Level 19->20
There is a setuid binary in the home directory called bandit20-do. A setuid
binary runs with the file owner's permissions rather than the calling user's,
meaning it executes as bandit20. I used it to read the password file that
bandit19 normally cannot access:
./bandit20-do cat /etc/bandit_pass/bandit20.
Level 20->21
There is a setuid binary called suconnect that connects to a local port, reads
a password, and returns the next one if correct. To use it I needed two
terminal sessions running simultaneously, so I used tmux to split the
terminal. In one pane I started a netcat listener:
nc -lvnp 4444. In the other I ran ./suconnect 4444. When
suconnect connected to the listener I pasted the current level's password
into the nc pane and suconnect sent back the next password.
Level 21->22
A cron job is running a script as bandit22 every minute. I navigated to
cd /etc/cron.d and read cat cronjob_bandit22 to see what was
scheduled. The cron script sets permissions on a temp file and writes
bandit22's password into it:
/tmp/t7O6lds9S0RqQh9aMcz6ShpAoZKF7fgv. Since the script already
ran and made the file world-readable I just used cat to read it directly.
Level 22->23
Another cron job, but this time the script uses md5sum to generate the temp
filename dynamically. Reading the script at
/usr/bin/cronjob_bandit23.sh showed it hashes the string
"I am user $myname" and writes the password to /tmp/<hash>. To find
bandit23's file I ran the same hash logic myself:
echo I am user bandit23 | md5sum | cut -d ' ' -f 1. This gave me
the filename, and I used cat to read the password from that path in
/tmp.
Level 23->24
A cron job runs as bandit24 and executes then deletes every script placed in
/var/spool/bandit24/foo each minute. I created a temp directory with
mktemp -d, then wrote a script that copies bandit24's password to a
file in that directory. I made both the script and the temp directory
world-writable with chmod 777, moved the script into
/var/spool/bandit24/foo, and waited for the cron job to run it. After a
minute the password appeared in my temp directory.
Level 24->25
A daemon on port 30002 accepts the current password along with a secret 4-digit
PIN and returns the next password if both are correct. Since there are only
10000 possible PINs I brute-forced them with a loop piped into netcat:
{
for i in {0..9999}; do
echo "[PASSWORD] $i"
done
} | nc localhost 30002
Note: if the correct PIN is less than 1000 the loop still works because the
server accepts the numeric value, not zero-padded format.
Level 25->26
Bandit26 does not use /bin/bash as its shell — it uses a restricted custom
shell based on the more pager. I was given an RSA private key to SSH
in. I pasted the key into a local file and logged in with:
ssh [email protected] -p 2220 -i sshkey.private.
The connection opens but closes immediately because more exits once it has
displayed the welcome message.
[RSA Private Key - omitted]
Level 26->27
Since bandit26 uses more as its shell, I needed to exploit the way more
works. By shrinking the terminal window small enough, more is forced to
paginate the output and stays open instead of exiting. While more was paused
I pressed v to drop into vim. From vim I ran
:set shell=/bin/bash to override the restricted shell, then
:shell to spawn a full bash session. With a proper shell I ran
./bandit27-do cat /etc/bandit_pass/bandit27 to get the next
password.
Level 27->28
The password is stored in a git repository. I cloned it with:
git clone ssh://[email protected]:2220/home/bandit27-git/repo,
entered the current password when prompted, then navigated into the repo and
read cat README to find the password. Reference: Git From the Bottom
Up.
Level 28->29
Same process to clone the repo. This time the README showed a placeholder
instead of the real password. I used git log -p to view the full
commit history including diffs for every commit. This revealed that the
password had been added in an earlier commit and then redacted in a later
one — it was still visible in the log history.
Level 29->30
After cloning the repo the README indicated the password was not in the
production branch. I used git branch -a to list all local and remote
branches. This showed several remote branches besides main. I switched to one
with git switch <branch> and found the README in that branch
contained the actual password.
Level 30->31
This challenge involves git tags rather than branches. After cloning, none of
the branches had the password. I ran git tag to list all tags in the
repo, which revealed one tag. I then used git show <tagname> to
inspect it, which printed the password directly.
Level 31->32
The task was to push a file called key.txt containing a specific string to the
remote master branch. The problem was that a .gitignore file in the
repo contained *.txt, which prevented any .txt file from being staged.
I removed the *.txt line from .gitignore, then added and pushed the file:
git add -f key.txt, git commit -m "add file",
git push -u origin master. The remote server responded with the next
password.
Level 32->33
After logging in, the shell converts everything typed to uppercase, causing
every command to fail with "command not found". The trick is to use
$0, which is a special shell variable that expands to the name of the
running shell itself — and since it starts with $ rather than a letter, it is
not uppercased. Typing $0 spawns a normal bash shell. From there I
navigated to cd /home/bandit33 and ran cat README.txt to
finish the wargame.