Defining, Understanding and Preventing Buffer Overflow Attacks
Buffer Overflows have been a critical and powerful attack vector used for decades by cybercriminals to exploit applications. Though not as common as attack methods such as XSS (cross site scripting) or SQL Injection, buffer overflow attacks can allow custom code execution in a system, typically after crashing system services. This is done by filling a buffer with data until there is no memory space left, causing code to “overflow” into adjacent memory locations. Web applications accepting input from a user can allow malicious cybercriminals to send more data into the server’s allocated buffers. This is possible because applications set up fixed size data buffers, which provide spaces of memory (in RAM) designed to act as temporary storage containers for data. Buffers are typically used when an application needs to store data temporarily (in buffers) as the data is moved through the application. Buffers can also be used when input/output hardware devices receive or send data. Buffers are physical addresses in memory where data is to be stored or a reference pointing to an address in memory. A very simple buffer overflow example is the use of a char buffer array in C, which may allocate more data to the buffer then it was designed to hold.
Despite being regarded as a somewhat archaic attack vector, buffer overflow attacks are used to exploit modern systems even today, while certain systems continue to remain vulnerable to the attacks. According to the latest hacking news, security firm Senrio recently discovered that many Internet of Things (IoT) devices are vulnerable to buffer overflows (Eslam Medhat, 2017). Other known application and operating system buffer overflow vulnerabilities include security bugs in the Windows OS, iOS, Android OS and Adobe Reader software applications.
Well Known Buffer Overflow Attacks
Throughout the years, there have been many minor and major buffer overflow attacks that have brought attention to the attack vector. Such attacks could be used against minor web application servers or large corporate computer systems to gain unauthorized, root privilege, crash systems, execute malware, etc.
The Morris Worm Attack in 1988
The Morris Worm attack was one of the first complex cyberattacks carried out using malware code (a worm) to instigate both a Denial of Service (DoS) and Buffer Overflow attack. The worm attacked a buffer that was declared as the first local variable in the main function, which ultimately resulted in opening up a reverse shell. Unintentionally, as noted above, the attack crashed systems on ARPANET via a DoS. As a worm, the malware did not require human activation or execution, as it both infected computer systems and self-replicated by simply being on a host system.
While the infection was a first of its kind, it also saw the first criminal conviction of a felony under the 1986 Computer Fraud and Abuse Act.
SQL Slammer Attack in 2003
The SQL Slammer attack was an infamous 2003 worm cyberattack conducted against unpatched systems that were using Microsoft’s SQL Server Database System. The worm caused DNS servers to go down, ISPs to lose internet connections, etc. mainly by overloading network systems. The worm resided in memory and caused SQL servers to output an endless loop to other systems. Thus the worm created a type of DoS attack as well, and targeted a buffer overflow vulnerability in MS SQL Servers.
Buffer Overflow and The OWASP Top 10 Vulnerabilities List
Buffer overflows were a primary security vulnerability among web applications, hence making the OWASP Top 10 security list for multiple years (2003 and 2004). Due to increasing use of secure frameworks immune to buffer overflows, all OWASP Top 10 lists after 2004 omit the attack vector. Secure coding practices, managed code frameworks, and programming languages that typically conduct automatic garbage collection (memory management) make Buffer Overflow attacks harder to exploit.
Managed, or interpreted, programming languages (e.g. C#, Java, Python, etc.) have built in buffer overflow protection, in the form of garbage collection/boundary checks and code management. Most application’s written today use these languages, reducing the attack surface. Buffer overflow vulnerabilities are still commonly found in operating systems (which are primarily written in C/C++), browsers, browser extensions, and many Adobe applications. Everything from Microsoft Edge to the Microsoft .NET framework (via remote code execution) and Adobe Flash Player fall prey to buffer overflow vulnerabilities.
What is a Buffer Overflow Attack?
Buffer overflow conditions occur when a program creates a fixed size object in memory, and then stores too much information in the object. When an application processes data provided by an attacker, there is not enough space to store the extra data, causing the program to overwrite adjacent memory locations. For example, imagine that you have a bucket that can hold a gallon of water. What happens if you try to pour 5 gallons of water into the bucket? The bucket will overflow, spill onto the floor, and leave a mess to cleanup. This is exactly what buffer overflow attacks do to your application’s memory.
When a buffer overflow occurs, custom (malicious) code allows a cybercriminal to crash a system, and in some cases take control of it by executing commands against the system.
Types of Buffer Overflow Attacks
While a buffer is a generic term describing a set portion of memory for data allocation, specific types of memory storages can be allocated, called the heap or the stack. The stack is a statically allocated storage unit of memory associated with local variables (objects) that go out of scope after program execution, while the heap is a dynamically allocated storage unit of memory associated with global variables. While stack objects allow for automatic re-allocation after program execution, heap memory addresses must be de-allocated by the programmer manually. For both, memory allocation occurs at run-time.
Stack Based Buffer Overflow Attack
A stack based buffer overflow is where the buffer is allocated in the static application memory stack as a fixed size. Writing too much data into the buffer overwrites the fixed memory location. This vulnerability occurs because of insufficient bounds checking. Writing too much data into the buffer causes the out of bounds exception, typically a program crash, and possibly remote code execution.
This type of attack is related to inefficient memory management in that the stack (buffer) has been allocated a certain amount of memory, and a user inputs data into the buffer that is greater than the amount of memory that the stack was designed to hold. Both efficient memory management and boundary checking (via the optimized GCC compiler at compile time for C/C++) can mitigate the issue and prevent cybercriminals from running custom code in the system remotely.
Heap Based Buffer Overflow Attack
Dynamically allocated memory space is managed by the “heap”. Consider an application that created a dynamically sized buffer on the heap at run time. Similar to stack-based attacks, this vulnerability occurs because of insufficient bounds checking when writing data to the heap. Writing too much data into the buffer can allow attackers to control program execution, manipulate heap data structures, and overwrite function pointers to attacker controlled instructions.
At the same time, in the event of an engineer not manually de-allocating objects or pointers to memory addresses in the heap, a memory leak can result in a heap overflow in attempting to write data to the heap that has no memory (storage space) left. Hence memory management and boundary checks are the two primary reasons for stack or heap overflows. It is important to note, however, that in terms of the heap, languages with automatic garbage collection (C/C++ don’t come with GC included) will manage memory on the heap automatically by de-allocating unused objects/data from memory addresses.
Though the size of the stack is determined upon execution (the memory is allocated at run-time) and generally doesn’t change, the size of the heap can grow as the operator calls upon the OS for more memory resources (hence being dynamic). This means that while the stack may be overridden by data inputs (e.g. an endless loop call) creating a stack overflow, the heap can grow to accommodate the system. However, common memory leaks associated with the heap can essentially take up memory resources, which stops the heap from accommodating the system’s needs, possibly resulting in a system crash.
What Systems Are Vulnerable to a Buffer Overflow Attack?
Typically systems built on languages that do not come packaged with garbage collection and are unmanaged languages – C and C++ specifically. Applications and systems built on these languages very commonly have buffer overflow vulnerabilities when engineers have not managed memory correctly or perform boundary checking (via debugging and the use of optimized GCC).
At the same time, certain systems are very difficult to attack via a buffer overflow. These include systems built on languages that include automatic garbage collection (memory management), and managed code languages. Managed code written by development teams runs inside a protected virtual environment (Java Virtual Machine, .NET Common Language Runtime are both examples) that provide memory management, array bounds checking, type safety, and overflow protection. However, it is important to realize that managed frameworks (Java, .NET, and Python) themselves call into operating system libraries that are vulnerable to buffer overflow (C / C++). Buffer overflow vulnerabilities can occur when passing unvalidated data into the framework’s APIs that interface with the operating system.
In line with above, even safe languages such as Java, Python, and the .NET framework have published buffer overflow vulnerability bugs associated with their frameworks, according to CVE Details. This can range from a heap-overflow in the JRE (Java Run-time environment), to an integer overflow in Python, to a directory services protocol buffer overflow in the .NET Framework.
What Damage Can be Caused by a Buffer Overflow Attack?
Writing too much data into the buffer can allow attackers to control program execution, manipulate heap data structures, and overwrite function pointers to attacker controlled instructions. In addition to this, system crashes, a DoS or even DDoS attack, and remote code execution can be used in combination with malware to escalate privileges and open a reverse shell. The end result is remote code execution, malware installation, and complete control of the system running the vulnerable code.
Windows desktop applications, Adobe applications, many browsers, etc. often have buffer overflow vulnerabilities associated with the use of C/C++. Microsoft Edge, the web browser packaged with Windows 10, has a known scripting engine buffer overflow vulnerability. With this bug, objects in memory are handled by MS Edge in such a way that a malicious person could corrupt the memory and execute his/her own code, allowing him/her to gain the rights of an authenticated user (root access to the system). With escalated privileges, the malicious person could create additional accounts, install a backdoor, shut down the system, delete data, install programs (e.g. malware), and more.
Different MS Windows Server software packages have also been susceptible to buffer overflow attacks in the DHCP Service. This bug results in remote code execution when a malicious person sends specially designed packets to the server, resulting in memory corruption. The end result is control of the system and/or the crashing of the server.
Adobe applications are often susceptible to buffer overflow attacks and memory corruption via malicious inputs of data. Some examples include an Adobe Acrobat vulnerability that could result in a malicious PDF causing remote code execution, and an Adobe Flash Player vulnerability that could result in remote code execution due to the use of both a stack-overflow (associated with a bug in the application’s use of regular expressions) and social engineering.
Essentially, when a cybercriminal uses a buffer overflow against a corporate system and gains the ability to run remote code, he/she has complete control of the system and can do almost whatever he/she desires, assuming that such a person has root privileges.
Checking for Buffer Overflow Vulnerabilities
Conducting a code review is a good way to do a preliminary check for the existence of buffer overflow vulnerabilities. In addition to this, compilers usually conduct boundary checks at compile-time to ensure that buffer overflows cannot be performed. For example, the strict C++ compiler option provides applications using C++ with defense against stack overflow issues. Typically, for optimized compilers will report an error if problems associated with a possible buffer overflow exist.
Security engineers also conduct code reviews (as mentioned), vulnerability scans, and penetration tests to determine if buffer overflow vulnerabilities exist. Vulnerability scans will scan static code (based on parameters) for known buffer overflow coding patterns, while penetration tests allow an ethical hacker to actively attempt to dynamically utilize buffer overflow attacks on a system at run-time.
Preventing Buffer Overflow Attacks
Operating systems use Address Space Layout Randomization, or ASLR, which is a memory protection feature that randomizes the application’s memory, making it difficult for an attacker to predict its location. Operating systems also use Data Execution Prevention, or DEP, which is a security feature that separates executable and non-executable data into different memory locations. Only data placed into an executable location is allowed to run by the operating system. Buffers placed in non-executable memory locations reduce the damage potential of an attack.
In addition to using ASLR and DEP – when it comes to other software applications – compiler tools, such as StackShield and StackGuard, help with buffer overflow prevention. The use of safe functions by engineers and the utilization of code audits, etc. can further ensure that buffer overflow vulnerabilities are mitigated.
At the Application Layer Strict Input Validation on All dynamic Data
As has been noted, buffer overflows occur due to malicious user inputs, either over a network/web application (from the client side to a server) or via a non-web software application. Thus one of the most important methods of mitigation is via strict input validation. Some additional processes associated with input validation that can be used are:
- String Length Checks – before assigning string data to buffers, engineers should make sure that the data is not larger than the buffer’s capacity
- Length Checks – engineers should enforce strict length checks on dynamic data before the data is parsed by the server
- Bounds Checks – engineers should make sure that the size of inputs will fit into a buffer before writing data into the buffer. Check array indexes and ensure they fall in the valid range.
- Overflow checks – When performing mathematical operations, validate the numeric value ranges before the calculation and ensure that the return result fits into the variable it is being assigned to.
- Avoiding the use of dangerous functions (e.g. strcat, strcpy)
- Languages with direct memory access (C/C++) have deprecated unsafe functions in favor of secure implementations that are immune to buffer overflow.
- Applications written in C should use the secure methods provided by the Safe C Runtime library, also known as Safe CRT.
- Applications written in C++ should use the Standard Template Library classes, often called STL, which automatically detects overflow conditions and throws an exception.
The use of strict input validation on all dynamic data will ensure that only validated, safe inputs are processed by the application, which will ensure that buffers are not overflowed with data.