---
title: "Understanding Software Vulnerabilities: Injection Attacks, Race Conditions, and Format String Attacks"
author: ["Z. Cliffe Schreuders"]
license: "CC BY-SA 4.0"
description: "Learn advanced software vulnerability concepts including command injection, race conditions, and format string attacks. Master validation, sanitization, and secure coding practices through hands-on exercises."
overview: |
  Software vulnerabilities can have severe consequences, stemming from design and implementation problems. These problems can range from simple memory errors and bounds checking, like buffer overflows, to more complex issues such as race conditions, format string attacks, and misconfigured security mechanisms. In this lab you will learn to identify and understand these programming flaws through hands-on exercises.

  You will explore command injection vulnerabilities. You'll be tasked with creating, compiling, and running and exploiting a C program susceptible to command injection. You will investigate concepts including validation and sanitization, essential for securing your code against malicious input. You will also learn how to use the diff and patch commands to create and apply code patches. You will also investigate and attack time of check to time of use race conditions. Lastly, you'll explore format string attacks, examining their dangers and vulnerabilities.

  By the end of this lab, you will have acquired a deeper understanding of software vulnerabilities and learned practical techniques to identify, prevent, and mitigate these issues in your own code. These skills are invaluable for anyone involved in software development and cybersecurity.

  In your home directory you will find some binaries representing challenges to complete to receive the flags.
tags: ["software-security", "injection-attacks", "race-conditions", "format-strings", "validation", "sanitization", "ctf"]
categories: ["software_security_exploitation"]
lab_sheet_url: "https://docs.google.com/document/d/1GKmNARyF2-RQ-jK1_w4Y7V9vNtXsJvMmvXSbnadyEoE/edit?usp=sharing"
type: ["ctf-lab", "lab-sheet"]
difficulty: "intermediate"
cybok:
  - ka: "SS"
    topic: "Categories of Vulnerabilities"
    keywords: ["race condition vulnerabilities", "structured output generation vulnerabilities", "Format string attacks"]
  - ka: "SS"
    topic: "Prevention of Vulnerabilities"
    keywords: ["race condition mitigations", "structured output generations mitigations"]
---


## Recap: programming errors {#recap:-programming-errors}

It is hard to write secure code. Small programming mistakes can result in software vulnerabilities with serious consequences. The two main categories of software flaws are those caused by:

* design problems: such as, a programmer not thinking through the kind of authentication required

* implementation problems: such as, a programmer accidentally introducing a bug by using an insecure library method or trying to store too much data into a variable

Common programming errors that lead to security flaws include:

* Memory errors and bounds checking, such as buffer overflows

* Using input without sanitising it: this can lead to flaws such as command injection

* Race conditions

* Misconfigured/used access controls and other security mechanisms

* Misuse of pointers and strings

*This lab* focuses on looking at and understanding many of these programming flaws.

## Un-sanitised input and structured output generation vulnerabilities: command injection {#un-sanitised-input-and-structured-output-generation-vulnerabilities-command-injection}

Many programs dynamically generate structured output that then gets processed further. For example: A SQL statement sent to a database, or command sent to a shell; HTML consumed by a browser. Design or implementation flaws can result in dynamically generating output that does not match the intended functionality. This is known as a structured output generation vulnerability. An example of this is being vulnerable to command injection.

A program is vulnerable to command injection if you can change the behaviour of software by inserting commands into input that get interpreted as commands for the program to execute. The result is similar to the outcome of the previous lab, although with command injection an attacker does not need to overflow into a variable: they simply enter data into a variable that is misused by the programmer.

==action: Open a terminal console==.

==action: Move to a new directory for our code:==

```bash
mkdir ~/code
```

```bash
cd ~/code
```

==action: Create and edit a new file "injectionattack.c":==

```bash
vi injectionattack.c
```

> Note: **Reminder**: Vi is 'modal': it has an insert mode, where you can type text into the file, and normal mode, where what you type is interpreted as commands. ==action: Press the "i" key to enter "insert mode"==. 

==action: Type the below C code:==

```c
#include <stdio.h>
#include <stdlib.h>
int main() {
        char name [20];
        char command [100];
        printf("What is your first name?\n");
        scanf("%19[^\n]s", &name);
        sprintf(command, "echo Hello %s; echo The time is "
        "currently:; date", name);
        system(command);
}
```

==action: Exit back to "normal mode" by pressing the Esc key==. Now to ==action: exit and save the file press the ":" key, followed by "wq" (write quit), and press Enter==.

> Tip: Alternatively if you want to copy and paste, rather than using vi, run `cat > injectionattack.c` and paste into the terminal, then press `Ctrl-D` to end the file.

==action: Compile the program:==

```bash
gcc injectionattack.c -g -m32 -o injection
```

==action: Run the program:==

```bash
./injection
```

==action: Try entering your own name, and confirm that the program works as expected in this case==.

==action: Try entering a long input, and confirm that the program works as expected in this case==.

Note that this code is vulnerable to command injection. To understand the attack, it is important to understand the way this code works. The sprintf line builds a string based on a format string: it is like printf, except that it writes to a string variable rather than to the console. It is used in this case to create the output for the user, by creating Bash commands, which the next line executes, by passing this through to the Bash shell. The final command that is stored in command looks like:

```bash
echo Hello Cliffe; echo The time is currently:; date
```

> Tip: The semicolon (";") is used in Bash to separate commands, so this is effectively going to run three commands via Bash:

```bash
echo Hello Cliffe
```

```bash
echo The time is currently:
```

```bash
date
```

==action: Add a comment above each line of code, with an explanation of its purpose. For example, "// this line reads 19 characters from the user, and stores them in name followed by a terminating \\0 character"==.

> Note: Note that the user can type up to 19 characters, and they will be included in the above command sequence.

> Question: Can you think of a way of subverting the behaviour of this program, so that it prints the contents of /etc/passwd?

==action: Confirm the command injection vulnerability...==

==action: Run the program:==

```bash
./injection
```

> Action: **Challenge:** Enter an input that makes the program print out /etc/passwd

**Solution:**

> Warning: **SPOILER ALERT!  SPOILER ALERT!  SPOILER ALERT!**

> Note: Since the semicolon can be used to separate commands, you can simply include a semicolon in your input, which starts a new Bash command.

==action: You can exploit this vulnerability as follows:==

```bash
./injection
```

```
;cat /etc/passwd
```

> Note: This makes the final command run by Bash:

```bash
echo Hello ;cat /etc/passwd; echo The time is currently:; date
```

> Warning: End of spoiler section

Make sure you understand the answers to these questions:

> Question: What is the maximum amount of damage that this attack against this program could theoretically cause?

> Question: Would the security threat be higher if the program was: setuid? What about a server that people connected to over a network?

Note that this attack is the same way that SQL injection works, the difference is that here we have commands sent to bash, rather than to a Web server database. Command injection attacks can apply to any type of programming, web, application, system, or otherwise, whenever input from a user is used as part of an interpreted command.

The solution is that all data that comes from an untrusted source must be validated and sanitised before use.

> Question: What is an untrusted source? Examples?

> Question: What sources would you trust enough that you wouldn't check it before processing?

## Validation {#validation}

Validation involves checking that data is in the format you expect. 

==action: Create and edit a new file "injectionattack_validated.c":==

```bash
vi injectionattack_validated.c
```

==action: Enter the below C code:==

```c
#define IS_VALID 1
#define NOT_VALID 0
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <ctype.h>

int validate(char* input) {
        int i;
        for(i=0; i < strlen(input); i++) {
                if(!isalpha(input[i])) {
                        return NOT_VALID;
                }
        }
        return IS_VALID;
}

int main() {
        char name [20];
        char command [100];
        printf("What is your first name?\n");
        scanf("%19[^\n]s", &name);
        if(validate(name) == IS_VALID) {
                sprintf(command, "echo Hello %s; echo The time is currently:;"
                  "date", name);
                system(command);
        } else {
                printf("Invalid input!\n");
        }
}
```

The above code adds validation to the previous code, by examining each character in the input, and if any character is not a letter, it refuses to run the command.

==action: Add a comment above each new line of code, with an explanation of its purpose==.

==action: Compile and run the program==.

==action: Attempt your command injection, and confirm that the attack no longer works==.

## Creating code patches using diff {#creating-code-patches-using-diff}

The common Unix commands diff and patch can be used to apply delta changes to a file. A delta describes the changes made, rather than an entire file.

The diff command outputs a delta (or "diff"), that can be applied by someone else to a suitably similar file, using patch.

==action: Create a patch:==

```bash
diff -u injectionattack.c injectionattack_validated.c > injectionattack.addvalidation.patch
```

This has directed the output from diff into a new file, which adds validation to the original code.

==action: View the diff:==

```bash
less injectionattack.addvalidation.patch
```

==action: Make a copy of injectionattack.c called injectionattack_update.c:==

```bash
cp injectionattack.c injectionattack_update.c
```

==action: Apply your patch to the copy:==

```bash
patch injectionattack_update.c < injectionattack.addvalidation.patch
```

==action: View the updated version:==

```bash
less injectionattack_update.c
```

You have successfully generated a patch, and used it to update an old version of the code to apply a set of changes.

## Sanitisation {#sanitisation}

Sanitisation involves *removing* any potentially dangerous formatting / content from a variable. This can 'fix' the input, to make it safe for use.

==action: Make a copy of injectionattack_validated.c called injectionattack_sanitied.c:==

```bash
cp injectionattack_validated.c injectionattack_sanitised.c
```

==action: **Edit injectionattack_sanitied.c to sanitise the input**, so that it is safe for use, and prints the name input, even if the user enters invalid characters, such as ";"==.

> Note: For example, if the user enters ";/etc/password" this could be changed to "etcpasswd" or "--etc-password".

> Hint: replacing any invalid character with "**-**" is probably the easiest solution.

==action: Compile, run, and test your program==.

==action: Once you are satisfied with your solution, create a patch:==

```bash
diff -u injectionattack_validated.c injectionattack_sanitised.c > injectionattack.addsanitisation.patch
```

## Denylist (blacklist) vs allowlist (whitelist) {#denylist-blacklist-vs-allowlist-whitelist}

==action: Try entering this as input to you and your classmate's new version of the program:==

```bash
./injectionattack_sanitised
```

```
& cat /etc/passwd
```

If this attack succeeds, even after you updated the code to defend against the previous ";" attack, then this means you likely based your solution on denylisting certain values, rather than checking against an allowlist of acceptable values.

The safest approach is to remove everything you don't actively expect, rather than removing what you know you don't want. (The same goes for validation, which should check for a set of valid characters, rather than a set of invalid characters.)

> Question: Why is checking against a whitelist the safer approach?

## CTF challenge {#ctf-challenge}

==action: Browse the challenges directory==

```bash
ls /home/==edit:username==/challenges
```

> Tip: When you run the program it will give you instructions and hints on how to solve the challenge.

==action: Run the challenge (always after changing to the directory first):==

```bash
cd ~/challenges/Ch_BashInjection_1
```

> Tip: The ~ (tilde symbol) represents your home directory.


```bash
./Ch_BashInjection_1
```

You can apply the techniques you have learned above to solve the challenge, to determine the password that will provide you with a flag.

> Flag: **Challenge**: perform command injection against this version.

> Hint: The input string is surrounded by quotes.

> Hint: Think about what the resulting command will look like to bash. Also think about how you can use single quotes (`'`).


## Race conditions {#race-conditions}

If you rely on something on the system to stay in the same state between two lines of code, you probably have a race condition. It is hard not to be dependent on the sequence or timing of other events, but getting this wrong can have security implications. 

Time of check to time of use, *TOCTTOU (“tock too”),* flaws can cause security vulnerabilities. TOCTTOU occurs when something changes between when a condition is checked, and the resulting action happens. For example, if a program checks that a file exists (or someone has permissions to it), then goes on to do something. This could introduce a security flaw, since an attacker could make carefully timed changes to the system between these two events.

==action: Create and edit a new file "race_condition.c":==

```bash
cd ~/code
```

```bash
vi race_condition.c
```

==action: Enter the below C code:==

```c
#include <stdio.h>
#include <stdlib.h>
#include <sys/stat.h>
#include <unistd.h>
int main()
{
        char tmpname [20] = "/tmp/not_so_random";
        char command [100];

        struct stat buf;
        int ok = stat(tmpname, &buf);

        sleep(10);

        if(ok == 0) {
                printf("Temporary file already exists, "
                  "we need a new name...");
        } else {
                printf("File does not exist, writing...");
                /* set umask to remove permissions from all new files */
                umask( S_IRGRP | S_IWGRP | S_IROTH | S_IWOTH );

                sprintf(command, "echo Saved file > %s", tmpname);
                system(command);
        }
}
```

==action: Compile the program:==

```bash
gcc race_condition.c -o race_condition
```

This code checks that a temporary file named /tmp/not_so_random does not exist before creating the file. The umask is set before creation so that the new file is only readable and writable by the current user.

The 10 second delay in the code between the check and subsequent action is an exaggeration (the actual delay would be *much* shorter), but it helps us explore the flaw.

==action: Check the program works by running it (and wait for the 10 seconds for it to run):==

```bash
./race_condition
```

==action: Check the file has been created:==

```bash
ls -l /tmp/not_so_random
```

```bash
cat /tmp/not_so_random
```

==action: Open a separate console window or tab for your "Second" user (*named "second"*)==.

**As second user:** (with username "second")

```bash
su - second
```

> Note: The second user has the same password: tiaspbiqe2r

==action: Check that the second user can't access the file==

```bash
cat /tmp/not_so_random
```

**As your main user:**

==action: Remove the file:==

```bash
rm /tmp/not_so_random
```

> Question: Consider this. What if something happens between the check and the following code? What if a file was put there in between?

==action: Run the program as your main user:==

```bash
./race_condition
```

***Quickly** switch to the other second user console.*

**As second user:**

==action: While the program is running and sleeping:==

```bash
umask 000; touch /tmp/not_so_random
```

**As your main user:**

==action: Wait for the program to end==.

> Question: What permissions does the file now have, and who can access it?

```bash
ls -l /tmp/not_so_random
```

```bash
cat /tmp/not_so_random
```

**As second user:**

```bash
cat /tmp/not_so_random
```

The very subtle timing flaw in the program means that if an attacker can create a file at just the right time, they can make the program act in unexpected ways that violate security goals. 

There is a whole category of attacks that use symlinks to make files with these kinds of race conditions to write to any file that the victim can access, which some defenses exist for: [Hard Link/Soft Link Protection](https://danwalsh.livejournal.com/64493.html).

## Preventing race conditions by using exception handling {#preventing-race-conditions-by-using-exception-handling}

> Note: One way of reducing these types of errors is to use exception handling rather than checking conditions separately. For example, use an "open" function call, with a flag to tell the function to return an error if the file exists.

==action: **Modify race_condition.c so that it opens the file using a parameter that tells it to fail if the file exists.**==

> Hint: `man open`, and write to the file using C library calls rather than the `system()` function call

==action: Compile, run, and test that this fixes the race condition==.

Look up and read about other types of race conditions such as those caused by multiple threads sharing the same data.

## Format string attacks {#format-string-attacks}

Some functions such as printf() receive a format string, followed by multiple variables to display, as specified in the format string. 

For example, to print the a message that includes a string (text) and an integer (whole number), the C code would be:

```c
printf("Hello %s, you entered %d", string, number);
```

When printf() is run, it reads the format string, then looks to the stack for the data to replace the format string values (as per the way parameters are passed to functions). The "%s" is replaced by a string from the stack (input), and the "%d" is replaced by an integer (number).

An overview of format string specifiers:

* `%d`: signed integer (**d**ecimal)

* `%f`: double (real **f**loating point number)

* `%x`: (he**x**adecimal -- "%08x" can be used to print 8 digits/bytes of memory)

* `%s`: (**s**tring)

* `%n`: number of bytes written by printf (writes to memory)

The correct way of printing only the string, is:

```c
printf("%s", string);
```

> Warning: The lazy and vulnerable (**bad**) way would be to use code like this:

```c
printf(string);
```

> Warning: If a lazy programmer passes user input *as the format string*, an attacker can use clever tricks to view the contents of the stack, or even write to memory! This can lead to serious security vulnerabilities.

==action: Create and edit a new file "format_string_attack.c":==

```bash
vi format_string_attack.c
```

==action: Enter the below C code:==

```c
#include <stdio.h>
#include <string.h>
int main() {
        int allowed_access = 0;
        int secret_number = 42;
        char name [100];
        printf("What is your first name?\n");
        fgets(name, sizeof(name), stdin);
        printf("You entered:\n");
        printf(name); // oops!
        if(strcmp(name, "secret\n") == 0) {
                allowed_access = 1;
        }
        if(allowed_access != 0) {
                printf("\nThe secret number is %d\n", secret_number);
        } else {
                printf("\nNot telling you the secret\n");
        }
}
```

==action: Compile this program:==

```bash
gcc format_string_attack.c -g -m32 -o format_string_attack
```

==action: Try running the program, and enter the name "Bob"==.

==action: Run it again and enter "secret"==.

Note that the program is designed so that only someone called "secret" will be told the secret number. Ignoring the obviously weak authentication, lets focus on how we can exploit the line in bold — "print(name);" — using a format string attack.

> Question: Consider this: What happens if the user enters a format string specifier?

==action: Try running the program, and enter the name "**%d**"==.

This results in a call to printf() which prompts it to read a number off the stack. Unfortunately there is no number passed as a parameter to printf(); however, printf() does not know this, and diligently reads from the stack, and displays a mysterious number.

==action: Try running the program, and enter the name:==

```
"AAAA %x %x %x %x %x %x %x %x %x %x %x %x %x %x %x %x %x %x %x %x %x %x %x %x %x %x %x %x %x %x %x". (%x appears 31 times).
```

![][image-3]

The result is that printf() works its way back down the stack, reading values and printing them in hex form. Note that the "41414141" represents the first "AAAA" from the input, which shows that we are successfully tricking the program into displaying the stack. The values before the 0x41414141 are not intended for printf(); but printf() works its way through whatever is on the stack, as though they were parameters passed to printf().

Q: So what does this mean? A: An attacker can read values off the stack! 

In this case the output includes the secret_number, 0x2a which equals decimal 42! Following that is the value 0, which specifies that we don't have permission to access that information.

> Question: What format string specifier can be used to write arbitrary data?

## Format String CTF challenges {#format-string-ctf-challenges}

==action: Browse the challenges directory==

```bash
ls /home/==edit:username==/challenges
```

> Tip: When you run each program it will give you instructions and hints on how to solve the challenge.

==action: Run each of the challenges (always after changing to the directory first):==

```bash
cd ~/challenges/Ch3_Format0_Leak
```

```bash
./Ch3_Format0_Leak
```

> Flag: You can apply the techniques you have learned above (and in this case some reading about format strings) to solve the challenges, to determine the password that will provide you with a flag. Submit the flags to Hacktivity!

## Conclusion {#conclusion}

At this point you have:

* Exploited command injection errors

* Written C code for doing sanitisation

* Used diff and patch to create and apply delta changes

* Exploited code with time of check to time of use race conditions

* Coded a solution based on exception handling

* Exploited format string vulnerabilities to read data from the stack

Well done\!

[image-3]: {{ site.baseurl }}/assets/images/software_security_exploitation/2_race_conditions_format_str/image-3.png
