I recently took the challenge of one of the OverTheWire Wargames which is Bandit. It tackled a lot of concepts on UNIX and Networking which could help in actually being able to play the other Wargames that they have. In the process of trying to progress through the levels in Bandit, I have learned a lot on the basics of UNIX/Linux and the Networking on these systems (and a little bit of Git as well) and I would like to share the concepts I have learned throughout the challenge
ssh
The first concept that was apparent throughout the levels to progress is ssh. The whole game was located on their SSH server, which players needed to access through their CLIs using the ssh command. Every level transition essentially boiled down to connecting to the next user with a password obtained from the previous level, so getting comfortable with the syntax ssh bandit0@bandit.labs.overthewire.org -p 2220 became second nature fairly quickly.
As the levels progressed, password-based authentication wasn't always the way in. Some levels handed over an RSA private key instead of a plaintext password, which meant using the -i flag to point ssh to that identity file (e.g. ssh -i bandit_key bandit14@bandit.labs.overthewire.org -p 2220). This tied directly into the broader concept of key-based authentication, where the private key proves identity in place of typing a password.
There were also levels where the goal wasn't just to log in, but to execute a specific command or drop into a different shell on the remote end as part of the solution. This is where the -t flag came in, forcing pseudo-terminal allocation so that interactive programs (like another shell) would behave correctly when invoked through ssh rather than just running non-interactively and returning.
Unix/Linux Commands, Features and Their Intricacies
This is where I had the most of the fun stuff and most of my learning happened when trying to perform these commands. These are some of the core commands and features I picked up when trying to progress through the levels and how it actually was used in some of the levels:
cat, ls, more
These commands are the bread and butter for viewing files and directory contents, including handling tricky filenames (spaces, dashes, hidden files) which was present on the levels. One of the most notable parts of the game in which I had to use this command was at Level 2 -> Level 3. The level contained a file named --spaces in this filename--, the problem of a hyphen on the filename appears which typically means that there will be a flag to be passed to the command which causes it to wait an extra flag input. There are also spaces on the file name so the way to solve those problems is to utilize ./ when calling the file and also use \ before the spaces to indicate that there are spaces on the file name like so:
These types of problems appear on the levels which really helped me master these concepts especially on the commands I have mentioned.
find
This command is for locating files based on size, permissions, type, and timestamps, which was crucial for levels that hid passwords deep in directory trees or filtered by file attributes. Level 5 -> Level 6 highlighted the power of this command when trying to find a certain file. That is because in this level, the file that needs to be found should be searched from many directories with many different files inside as well. With the given limits to where the password could be, the power of the find command was shown in solving that particular problem.
grep
This command searching file contents for patterns, especially useful when a password was buried among thousands of words and characters. grep was used in some of the problems by piping the cat command's output into grep like so:
Pipe commands
As said earlier, piping chains commands together (cmd1 | cmd2 | cmd3) to filter and transform output instead of relying on a single command to do everything. The grep example from earlier is a good illustration of this:
Here, cat data.txt reads and outputs the entire contents of the file, and instead of dumping all of that to the terminal, the pipe (|) takes that output and feeds it directly into grep as input. grep "millionth" then filters through every line it receives and only prints the lines that contain the word "millionth", discarding everything else. This meant that instead of scrolling through potentially thousands of lines of junk data, the pipe let two simple commands work together to instantly narrow down the output to just the relevant line, which was often where the password for the next level was hiding.
sort and uniq
These commands together is used to isolate unique lines, which came up in levels where the password was the one line in a file that didn't repeat. One level's data.txt contained thousands of duplicated lines with only a single line appearing once, so the approach was to first sort the file (since uniq only detects duplicates that are adjacent to each other) and then pipe that sorted output into uniq -u, which prints only the lines that have no duplicates:
strings
This command is used to extract readable text from binary files, useful when the password was embedded in something not meant to be read directly. One level's data.txt contained a mix of binary data and human-readable text, which meant cat would just spit out a mess of unreadable characters. Running strings on the file filtered out the unreadable contents and printed only the human-readable strings, but the output was still cluttered with other text fragments unrelated to the password. To narrow it down further, the output was piped into grep to search specifically for lines containing an equals sign, since the password in this file appeared preceded by several rows of =:
base64
This command was used to decode data, since some levels layered multiple encodings on top of each other. One level's data.txt contained a string that, when viewed with cat, was base64-encoded. Running it through base64 -d to decode it revealed the readable line, giving the password needed to progress to the next level:
chmod
This command is used in manipulating file permissions. It was used both for solving levels and for understanding why certain access was blocked or allowed. chmod came up in a few different contexts throughout the game:
- giving execute permissions to scripts or files that needed to be run
- restricting the permissions on private key files (since ssh refuses to use a key that's readable by others, requiring something like
chmod 600 key_filebefore it would work) - adjusting permissions on output files to make sure they could actually be written to or read from when a level's solution involved redirecting output to a file.
tr
Used to translating or substituting characters. I used this in a level where the password was hidden using a ROT13-style letter rotation. Viewing data.txt directly with cat showed scrambled, unreadable text. Piping that output into tr with the mapping 'A-Za-z' 'N-ZA-Mn-za-m' shifted every letter by 13 places, decoding it back into plain English:
diff
Used in comparing two files to spot the difference between them, useful when a level asked to compare two large, mostly-identical files. This level provided two password files, passwords.old and passwords.new, each containing thousands of lines. Running diff between them isolated the exact line that had changed:
Shells
Getting comfortable with how different shells behave, and at times needing to specify or switch shells to get a command to execute properly. Linux systems support multiple shell types (bash, sh, rbash, etc.), each with different behaviors, capabilities, and restrictions. One level specifically required logging into a different, more restricted shell than the default in order to interact with the level correctly.
setuid
Understanding how the setuid bit allows a program to run with the file owner's privileges rather than the executing user's, which was central to several privilege-escalation-style levels. Normally, when a program is executed, it runs with the permissions of the user who launched it. A binary with the setuid bit set, however, runs with the permissions of the file's owner instead, regardless of who actually executes it. This was the mechanism that made several levels solvable: the current user didn't have direct read access to the next level's password file, but a setuid binary owned by the next-level user could be executed to read or output that password on the user's behalf, effectively borrowing the owner's privileges just long enough to retrieve the credential needed to progress.
CRON/scripting
This concept is important in reading and understanding scheduled jobs and the scripts they run, including spotting vulnerabilities in how those scripts handled permissions or input. One of the most satisfying levels in the whole game involved a cron job that was set to automatically run on a fixed schedule. The task here wasn't just to find a password sitting in a file, but to actually read and analyze the script that cron was executing in order to understand what it did, what permissions it ran with, and where it pulled its input from. Once it became clear that the script would execute anything placed in a certain location with the privileges of the cron job's owner, the solution was to write a custom script designed to take advantage of that behavior, drop it where the cron job would pick it up, and let the next scheduled run execute it automatically. Waiting for cron to fire and seeing the payload execute with elevated privileges, handing over the next password, was one of the more rewarding moments for me.
Networking and Communication
scp
Securely copying files to and from the remote server, which was necessary for levels requiring local processing of a file. One level provided an RSA private key for authentication, which first needed its permissions locked down with chmod 600 sshkey.private before it could be used. From there, scp was used with the -i flag to authenticate with that key and pull a password file directly off the remote server's filesystem down to the local machine:
Once the file landed locally, a simple cat on the transferred file through scp revealed the password.
nmap
Used for scanning for open ports and identifying services running on a target machine. One level only stated that the next password was being served somewhere on a port within a specific range, with no further details on which exact port. Running nmap against that range on localhost scanned through the possibilities and returned the open ports:
This narrowed things down from a thousand possible ports to just five open ones, each running an unknown service. From there, it was a matter of connecting to each open port individually to inspect what was being served and figure out which one actually held the password.
netcat or nc
Both of these commands is a way to connect to arbitrary ports/services and as a lightweight way to send and receive raw data. One level was one of the most interesting to me out of the whole game. It involved a setuid binary, suconnect, that would connect locally to a given port, read a password from whoever was listening there, and if that password matched the current level's password, it would send back the next level's password in response. The challenge was that this required something to actually be listening on a port to feed suconnect the correct password in the first place.
To pull this off, I used tmux to split my terminal so I could run multiple things simultaneously: in one pane, nc -lp 1112 was set up to listen on a port and send out the current password using echo -n "password" | nc -lp 1112 &, running it in the background so the terminal stayed free. In the other pane, ./suconnect 1112 was run to connect to that same port, read the password being served, confirm the match, and print "Password matches, sending next password," followed by the actual password for the next level. Seeing both sides of that handshake play out across split panes made this one of the more hands-on, satisfying levels to work through, since it wasn't just about reading a file, it was about actively setting up the communication myself.
Brute forcing
Brute forcing is a method of solving a problem by systematically trying every possible combination or option until the correct one is found, rather than deducing or calculating the answer directly. This level required a known password plus a 4-digit pincode to authenticate over a port, but the pincode itself wasn't given, only that it was somewhere within the 0000-9999 range. Manually guessing wasn't feasible, so the approach was to script the brute force instead. A small bash script was written to loop through every possible 4-digit combination, appending the known password plus each candidate pincode to a file:
After making the script executable with chmod 700 scriptBRUTE.sh and running it, the resulting possibilities.txt contained every possible password-pincode combination. That file was then piped directly into nc to send each line to the listening service on port 30002. It eventually responded with "Correct!" once it hit the right pincode, revealing the password for the next level.
openssl
Used to communicate over encrypted SSL/TLS connections, since some levels served their next password over a port that expected SSL/TLS rather than plain text, making plain nc insufficient. One level required sending the current password to a service listening on port 30001, but that port specifically expected an SSL connection. The openssl s_client command handled establishing that encrypted connection, connecting to the target with -connect localhost:30001, using -ign_eof to keep the connection open after sending input instead of closing immediately, and redirecting the password file as input:
Git
git clone (SSH)
Cloning repositories over SSH rather than HTTPS, tying back into the SSH concepts learned earlier. Several of the later levels each provided a Git repository accessible over SSH on a specific port, and the password for that level was hidden somewhere within the repository's history rather than sitting in a plain file. Cloning it locally was the first step before any of the repository's contents or history could actually be inspected.
git diff
Inspecting changes between commits or branches to find where a password may have been added and later removed. In one level, the current state of the repository didn't contain the password at all, but comparing two commits with git diff <commit1> <commit2> revealed that a line containing the password had been added in an earlier commit and then deleted in a later one, exposing it despite no longer being present in the latest version of the files.
Git branching
Understanding that information can exist on branches other than the one currently checked out. Not every branch in a repository is necessarily the default one checked out after cloning, and one level required listing all branches with git branch -a and switching into a non-default branch to find a password that simply wasn't present anywhere on the main branch.
Git tagging
Recognizing that tags can point to commits containing information not visible in the default branch history. Tags can reference commits that aren't part of any branch's regular history at all, so a level here meant running git tag to list available tags and then checking out or inspecting the commit a particular tag pointed to, where the password had been stored.
Git history
Digging through past commits, since deleted or modified content often still exists somewhere in the log. Using git log to walk back through the full commit history made it possible to find commits that had been effectively "hidden" by later changes, reinforcing the idea that removing content from the latest version of a file doesn't erase it from the repository's history.
Git Pushing (with .gitignore)
Understanding how .gitignore controls what does and doesn't get tracked, and how that can be exploited or worked around when searching for hidden information. One level involved a .gitignore file configured to exclude the very file that contained the password, meaning a plain git add . and commit wouldn't track it. Getting around this meant removing the ignored file from the .gitignore so it could be committed and pushed, revealing that .gitignore only prevents automatic tracking and doesn't actually make a file's content inaccessible if you choose to override it.
Closing Thoughts
Working through Bandit gave me a much more hands-on understanding of UNIX fundamentals and networking basics than reading about them ever could. More importantly, it built the muscle memory and problem-solving instincts needed to approach the other OverTheWire Wargames and also system tinkering in general with more confidence.
Visit OverTheWire Wargames : Bandit to try it out for yourself!









Top comments (0)