OverTheWire Bandit: Notes From Levels 0–12
A beginner-friendly walkthrough of OverTheWire Bandit levels 0–12 — SSH, hidden files, find, stderr, encodings, and a hexdump that keeps unfolding. Passwords redacted, per the rules.
Spoiler warning: This post walks through levels 0–12 of OverTheWire Bandit. If you're playing, try each level before reading. In line with OverTheWire's rules for content creators, no passwords are published here — every credential is redacted. Bandit is free; if it helps you, consider donating to OverTheWire.
Bandit is the beginner wargame from OverTheWire, and it's the first one I recommend to anyone who wants to get comfortable on the Linux command line. There's no GUI, no exploit framework, and no guessing game: you connect over SSH, solve a small file-system puzzle, and use the password you find to log in to the next level.
I worked through levels 0–12 over a few evenings. These are my notes — cleaned up, with the dead ends removed and the commands I actually used. The goal is not to memorize a list of answers. It's to show the rhythm: look at what you have, ask what it is, and pick the smallest tool that answers the question.
Before You Start
A few practical notes:
- The game's host is
bandit.labs.overthewire.organd the SSH port is2220, not the default22. - Your starting credentials are on the Bandit level 0 page. I'm not repeating the password here.
- Each level's password logs you in to the next level. The username changes:
bandit0,bandit1,bandit2, and so on. - A Linux/macOS terminal is enough. On Windows, use WSL.
- Read the error messages. They are usually telling you exactly what went wrong.
The login command looks like this:
ssh [email protected] -p 2220
The first time you connect, SSH asks whether you trust the host's fingerprint:
The authenticity of host '[bandit.labs.overthewire.org]:2220' can't be established.
ED25519 key fingerprint is SHA256:...
Are you sure you want to continue connecting (yes/no/[fingerprint])?
Type yes if the fingerprint matches what you expect (or if you accept the risk for a game host), then paste the password from the level page. The password is not echoed to the screen — that's normal.
When you're done, exit closes the session. Then log in as the next user with the password you found.
Level 0 — Log In and Read the README
Once you're in, look around:
ls
There's a single file called readme. Read it:
cat readme
The file explains the rules and ends with the password for bandit1. I've redacted it here:
The password you are looking for is: [redacted]
That's the whole loop. Log out, then log back in as bandit1 with that password.
Level 1 — A File Named -
The next level tells you the password is in a file called -. If you try the obvious thing, the shell seems to hang:
cat -
That's because many Unix tools treat a lone - as "read from standard input," not "read a file named -." You need to make it unambiguous by giving it a path:
ls -l
cat ./-
The ./ prefix says "the entry named - in the current directory." $PWD/- works too. I also checked what the file was before reading it:
du ./-
file ./-
file reports ASCII text, which is a good sign. cat then prints the next password, redacted again.
Level 2 — Spaces in the Filename
The file this time has spaces in its name, plus leading dashes:
ls
cat ./"--spaces in this filename--"
The quotes keep the whole name as one argument. Without them, the shell splits it into several arguments and cat complains. Tab completion is your friend here: type cat ./--sp and press Tab; the shell finishes the name and escapes the spaces for you.
Level 3 — Hidden Files
ls shows an inhere directory, but inside it looks empty:
cd inhere
ls
Nothing. The file is hidden, which on Unix simply means its name starts with a dot. Ask ls to include hidden entries:
ls -a
Now you can see ...Hiding-From-You alongside the usual . and ... The long form confirms it's a regular file:
ls -la
cat ...Hiding-From-You
The leading dots are part of the name; they don't mean "go up a directory" unless the name is exactly . or ...
Level 4 — The Human-Readable File
The inhere directory contains ten files named -file00 through -file09. The level says only one is human-readable. Before dumping all of them into the terminal, ask the system what they are:
file ./-file*
file identifies the type of each one. Exactly one is reported as ASCII text; the rest are data. Read that one:
cat ./-file07
(The usual answer is -file07; use whichever file file reports as ASCII text.)
If you do what I did first and run cat ./-file*, you'll get a screenful of binary garbage and possibly a messed-up terminal. The fix is reset, or just close the session and reconnect. file is the better first move.
Level 5 — Find by Size
This time the password is somewhere under inhere, in a file that is human-readable, exactly 1033 bytes, and not executable. There are twenty maybehereNN directories, so clicking through them is a waste of time.
find can filter on all of that:
find . -type f -size 1033c ! -executable
A quick decode:
-type fmeans regular files only.-size 1033cmeans exactly 1033 bytes; thecsuffix is bytes.! -executableexcludes files with any execute bit set.
My first pass used only find . -size 1033c, which found a single hit — but adding the other properties makes the command say what you mean. Once you have the path, confirm it and read it:
file ./maybehere07/.file2
cat ./maybehere07/.file2
Level 6 — The Whole Filesystem, and a Flood of Errors
Now the file can be anywhere on the server. The level gives you three properties: owned by user bandit7, owned by group bandit6, and 33 bytes in size. Search from the root:
find / -type f -size 33c -user bandit7 -group bandit6
The result is a wall of Permission denied messages. That's expected: as bandit6, you can't read every directory on the system. The important part is where those messages go. They're written to standard error (file descriptor 2), while the useful result goes to standard output (file descriptor 1).
You can throw the errors away:
find / -type f -size 33c -user bandit7 -group bandit6 2>/dev/null
Breaking that down:
2>redirects file descriptor 2 (stderr)./dev/nullis a special device that discards anything written to it.- The result is now readable:
./var/lib/dpkg/info/bandit7.password.
If you want to keep other errors visible and hide only the permission ones, merge stderr into stdout and filter:
find / -type f -size 33c -user bandit7 -group bandit6 2>&1 | grep -v 'Permission denied'
2>&1 means "send stderr to the same place as stdout," so grep can see both streams. grep -v inverts the match and drops the lines you don't want. Read the file:
cat ./var/lib/dpkg/info/bandit7.password
Level 7 — Grep the Millionth Word
data.txt is about 4 MB, with one word per line followed by a value. The password sits next to the word millionth. There's no reason to read the whole thing:
grep "millionth" data.txt
grep prints the matching line, and the value after the tab is the next password. In my notes I used cat data.txt | grep "millionth"; that works, but it starts an extra process for no reason. grep can read the file directly.
Level 8 — The Line That Appears Once
data.txt is full of repeated random strings. The password is the only line that occurs exactly once. The uniq command can find unique lines:
sort data.txt | uniq -u
The sort is not optional. uniq only compares adjacent lines, so duplicates that are far apart are invisible to it. Sorting brings identical lines together, and uniq -u prints only the lines that appear once.
A useful distinction: sort -u removes duplicates and prints each remaining line once. sort | uniq -u prints only the lines that had no duplicate at all. Those are different questions.
Level 9 — Strings in a Binary File
file data.txt says this is data, not text. cat would dump binary noise. The strings command extracts printable character sequences from a binary file, and the level says the password is one of the few human-readable strings, preceded by several = characters:
strings data.txt | grep "="
The output includes lines like ========== password and the password line itself. grep "=" is enough to narrow it down; the equals signs are part of the clue, not decoration.
Level 10 — Base64
data.txt is ASCII text, but it's encoded. file won't tell you which encoding; the level does. Base64 is a common way to represent binary data as text, and it is encoding, not encryption:
base64 -d data.txt
The -d flag decodes. The output is a readable sentence ending in the next password. If you see a string that ends in = or == and uses only letters, digits, +, and /, base64 is a good guess.
Level 11 — ROT13
Every letter in this file has been rotated by 13 positions. ROT13 is its own inverse: applying it twice returns the original. The tr command maps one set of characters to another:
tr 'A-Za-z' 'N-ZA-Mn-za-m' < data.txt
The mapping is A → N, B → O, and so on, wrapping around at Z; lowercase works the same way. I originally wrote cat data.txt | tr ..., which also works. Input redirection (<) is cleaner because tr doesn't need a file argument.
Level 12 — The Hexdump That Keeps Unfolding
This is the level that looks intimidating and is mostly patience. data.txt is a hexdump of a file that has been compressed repeatedly. Your job is to reverse the hexdump, then keep asking file what the result is and decompressing it with the matching tool.
First, make a working directory under /tmp with a name that isn't easy to guess, and copy the data there:
WORK=$(mktemp -d)
cp ~/data.txt "$WORK/"
cd "$WORK"
mktemp -d creates a unique directory and prints its name. The WORK variable saves it so you can clean it up later. Now reverse the hexdump:
xxd -r data.txt data1
file data1
xxd -r turns the hexdump back into binary. file says it's gzip data, so rename it to a .gz suffix and decompress:
mv data1 data.gz
gunzip data.gz
file data
gunzip removes the .gz file and writes data. file now says bzip2. Repeat the same pattern: match the tool to the type.
mv data data.bz2
bzip2 -d data.bz2
file data
Now it's gzip again:
mv data data.gz
gunzip data.gz
file data
This time it's a POSIX tar archive:
mv data data.tar
tar xf data.tar
file *
tar xf extracts the archive; file * shows what came out. It's data5.bin, and it's another tar archive:
mv data5.bin data5.tar
tar xf data5.tar
file *
Now data6.bin is bzip2:
mv data6.bin data6.bz2
bzip2 -d data6.bz2
file data6.bin
It comes back as a tar archive, so extract it:
mv data6.bin data6.tar
tar xf data6.tar
file *
data8.bin is gzip. One more round:
mv data8.bin data8.gz
gunzip data8.gz
file data8.bin
cat data8.bin
This time file says ASCII text, and cat prints the password sentence. I've redacted the value:
The password is [redacted]
The exact filenames may vary slightly between runs, but the rhythm doesn't: file → choose gunzip, bzip2 -d, or tar xf → rename if the tool insists on a suffix → repeat. The file command is the map; the extensions are only hints.
Two practical notes:
gziprefuses to decompress a file without a.gzsuffix unless you tell it otherwise. Renaming is the simplest fix.gunzipis just another name forgzip -d.- When you're done, clean up after yourself, as the OverTheWire rules ask:
cd ~ && rm -r "$WORK".
Level 13 — To Be Continued
Level 13 changes the pattern. Instead of handing you a password, it gives you a private SSH key that can log in as bandit14, and the next password lives in a file that only bandit14 can read. That means key permissions, ssh -i, and reading the error messages carefully — the level's hint file is there for a reason.
I haven't written that part up yet. Yes, this post ends on a cliffhanger. The next one will pick up at level 13.
What I Took Away
None of these levels required a clever exploit. They required knowing which small tool answers the question:
ls -afor hidden names.filebeforecat, especially when the "file" might not be text.findfor properties, not just names.2>and2>&1for separating useful output from noise.grep,sort,uniq,strings,base64, andtrfor filtering and decoding.xxd -rplusfilefor peeling back layers of compression.
The meta-skill is the same one that matters in security work: observe, form a small hypothesis, test it, and read the error when it fails. Bandit is a safe place to practice that loop.
Thanks to the OverTheWire community for keeping Bandit free and open. If you're playing, use the rules and the level pages, and don't post credentials — that's why every password above is redacted.
Happy hacking. Level 13 next.