Buffer overflow

What is a buffer overflow?

Buffer overflow is a vulnerability that lets a malicious hacker inject data into program memory and execute it by giving more data in user input than the program is designed to handle. Another name for such vulnerabilities is buffer overrun.

Buffer overflow was declared the most dangerous vulnerability in the CWE (Common Weakness Enumeration) Top 25 list for 2021 and 2022, previously holding positions in the top 3.


Severity: very severe
Prevalence: discovered rarely
Scope: applications with direct memory allocation
Technical impact: execution of arbitrary code
Worst-case consequences: full system compromise
Quick fix: check user input length

How does buffer overflow work?

A buffer overflow vulnerability happens when you accept too much data as user input. The excess data corrupts space in adjacent memory. Since the program often uses this memory space when processing further programming instructions, such a vulnerability may allow an attacker to inject and execute their own commands, for example, a reverse shell to gain access to the underlying operating system.

For example, a program may require the user to enter an email address. To store the email address, the developer creates a string variable and allocates 64 bytes for the variable because they do not expect an email string to be longer than 64 characters. The program is vulnerable to buffer overflow if it doesn’t check whether the entered string actually fits in the 64-byte buffer. If an attacker enters 100 characters, the excess 36 characters could be stored in memory that is allocated to another variable. This would cause the value of that variable to change, altering program behavior.

Buffer overflow problems are restricted to lower-level programming languages such as C and C++ that rely on the developer to allocate memory. Most languages used in web and API development, including PHP, Java, JavaScript, Python, and Perl, are far less vulnerable to buffer overflow vulnerabilities since they handle memory allocation on the developer’s behalf. However, since several of them do allow direct memory modification, and applications written in these languages sometimes use core functions in C/C++, even web applications may occasionally be vulnerable to buffer overflows.

Types of buffer overflow attacks

There are two primary types of buffer overflow attacks: stack overflow and heap overflow.

  • Stack-based buffer overflow: The attacker inserts malicious data into the stack, which is the memory space provided to a program by the operating system primarily to store local variables and function return addresses. Data on the stack is stored and retrieved in an organized fashion (last-in-first-out), stack allocation is managed by the operating system, and access to the stack is fast.
  • Heap-based buffer overflow: The attacker inserts malicious data into the heap, which is the memory space used to store dynamic data. The amount of memory to be reserved is decided at runtime and heap memory is managed by the program, not the operating system. While access to the heap is slower compared to the stack, space on the heap is limited only by the amount of virtual memory.

Example of a buffer overflow attack

A C program uses the stack to store a set of data for every function. This set is called a stack frame and includes the function identifier, values of local variables, and the return address. Here is a simple source code example to explain how the stack works:

main() {
  int mv1;
  int mv2;
  func(); 
} 
void func() {
  int fv1;
  int fv2;
}

When you run the program, it starts with the main() function. The program stores the values of the main() function variables at the end of the stack (mv1 and mv2). Then the main() function calls the func() function, and it stores the values of its variables on the top of the stack (fv1 and fv2). When the func() function finishes running, the top of the stack is forgotten, the current function returns to main(), and the program has access to mv1 and mv2 again.

To make this possible, the program remembers the current position on the stack (stack pointer) and the memory location where it needs to return after the current function is finished (return address). The trick behind a stack overflow attack is to overwrite this return address so the program jumps instead to malicious code supplied by the attacker.

A malicious payload used to exploit a buffer overflow vulnerability is usually composed of three parts:

  • A chain of bytes that represents the NOP (no operation) instruction
  • A new return pointer that points to the NOP bytes
  • Arbitrary code (usually a shellcode) located somewhere in the middle of the chain of NOP bytes

When a buffer overflow occurs in our next example, it causes the program to jump to the chain of NOP bytes (instead of jumping back to the main() function). All the NOP bytes are ignored, but the program encounters executable code in the middle of them. This code executes an operating system shell, giving the attacker full access to the system.

Here is a very simple C program vulnerable to a stack overflow:

main(int argc, char *argv[]) {
  func(argv[1]);
} 
void func(char *v) { 
  char buffer[10];
  strcpy(buffer, v);
}

The strcpy function in the above example copies the command argument into the destination buffer variable without checking the argument string length. The program only allocates 10 bytes to the buffer string, so this strcpy call results in a buffer overflow vulnerability. If we compile this program as vulnprog, the following command-line call is harmless because the 10 input characters fit in the allocated 10-byte buffer:

$ vulnprog AAAAAAAAA

However, if we input more than 10 characters, the argument string will extend beyond the 10-byte buffer, causing a buffer overflow and overwriting subsequent bytes in memory with “A” characters:

$ vulnprog AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA

Potential consequences of buffer overflow attacks

Buffer overflow vulnerabilities are difficult to find and exploit. They are also not as common as other vulnerabilities. However, buffer overflow attacks may have very serious consequences:

  • In most cases, buffer overflow attacks lead to a simple memory segmentation fault. This causes the program to stop running, resulting in a denial-of-service (DoS) attack.
  • If the attacker is able to use a buffer overflow to inject arbitrary code and gain shell access, they may run any commands in the operating system. This allows them to attempt privilege elevation to get administrative access to the OS.

How to detect buffer overflow vulnerabilities?

Web applications and web pages are rarely susceptible to buffer overflow vulnerabilities because they are not written in C or C++. However, these flaws can still occur in underlying software such as web servers, web application servers, or interpreters.

The best way to detect buffer overflow vulnerabilities depends on whether they are already known or unknown.

  • If you do not develop software in C/C++, it may be enough to identify the exact version of the existing software you are using. If the identified version is susceptible to a buffer overflow, you can assume that your software is vulnerable. You can identify the version manually or use a suitable security tool, such as a software composition analysis (SCA) solution, a network scanner, or Acunetix by Invicti, which finds known buffer overflow vulnerabilities in web servers and more.
  • If you develop your own software in languages such as C/C++, you need to conduct manual penetration testing with the help of security researchers to find potential buffer overflow vulnerabilities.

How to prevent buffer overflow vulnerabilities?

Preventing buffer overflow errors requires all developers to check input length before using any functions that might cause an overflow to happen.

However, to err is human, and it is not uncommon for developers to forget this basic rule. Code reviewers might miss such errors as well. That is why the safest basic method in C is to completely avoid directly using the following five unsafe functions that can lead to a buffer overflow vulnerability: printf, sprintf, strcat, strcpy, and gets.

While the core C language provides only one safe alternative in the form of fgets (to be used instead of gets), various platforms have their non-standard implementations. For example, the Microsoft version of C includes sprintf_s, strcpy_s, and strcat_s. On Linux/UNIX systems, the best choice is to ban unsafe functions and enforce the use of the Safe C Library.

How to mitigate buffer overflow attacks?

You can mitigate buffer overflow attacks by using a compiler extension that provides canaries – special values that the compiler places on the stack between the buffer and the control data. When a buffer overflow occurs, the canary value gets corrupted first, which is immediately detected. Many compiler extensions use canaries, including StackGuard and ProPolice.

Another way to mitigate buffer overflow attacks is by configuring the operating system to use address space layout randomization (ASLR). For a buffer overflow attack to be possible, the attacker must first know the exact location of the buffer in memory. In the past, this was as simple as running a debugger on your local computer and checking the memory addresses. With ASLR on, the executable file may be loaded into many different and randomized memory locations. The attacker cannot easily predict the memory address to jump to, which greatly reduces the risk of a successful buffer overflow attack.

Another technique that helps prevent buffer overflow attacks is executable space protection (on Windows: data execution prevention – DEP). Thanks to this technique, the attacker cannot execute any code located in the memory space assigned to the stack or heap and, in some cases, also other areas. This makes it impossible to directly call a shellcode, though attackers can still try advanced tricks such as return-oriented programming.

On x86 architectures, attackers may try to evade protection mechanisms by using a ret2reg attack. To do this, they need to find a module (DLL) that is not protected by ASLR or DEP. If they can find a JMP ESP instruction (jump to stack, byte combination \FF\E4) in that module, they can use the location of this instruction as the return address. The program will jump to this location, execute the jump instruction, and jump to the current location of the stack, which is right after the return address (and thus before the shellcode).

Frequently asked questions

What is buffer overflow?

Buffer overflow is a cybersecurity issue that happens in specific programming languages, such as C and C++, and allows an attacker to run malicious code. Such an attack is possible if the software developer manually allocates the memory to hold a specific amount of data but fails to perform bounds checking, i.e. does not check if user input exceeds the allocated amount of memory. The excess data spills over into other areas of memory, causing errors or allowing for attacks.

 

Learn more about buffer overflow attacks from the founder of the hakin9 magazine, Piotr Sobolewski.

How dangerous is buffer overflow?

Buffer overflow vulnerabilities are not easy to find or exploit, and they can only occur in software that manually allocates program memory. Modern operating systems include built-in protection that greatly reduces the risk of successful buffer overflow exploits. However, if the runtime protection is not on and the attacker is able to achieve code execution, a buffer overflow may allow them to execute operating system commands and escalate the attack further.

 

Learn about cyberattacks similar to buffer overflow – integer overflow.

How to prevent buffer overflow?

Preventing buffer overflows requires programmers to adhere to the standard application security practice of never trusting any user input. In particular, when programming in C or C++ and using printf, sprintf, strcat, strcpy, or gets, it is crucial to make sure that input string lengths never exceed the size of their allocated buffer.

 

Read more about general best practices for secure development.

ClassificationID
CAPEC100
CWE787
WASC7
OWASP 2021

Related blog posts


Written by: Tomasz Andrzej Nidecki, reviewed by: Benjamin Daniel Mussler