Davkodi Cyber

Welcome to my Over The Wire Bandit Tutorial

Using the drop down you can select a level...

Level 0

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.