Buffer-overflow vulnerabilities are back in the news. Buffer overflows, as this tip from InformIT explains, usually target poorly constructed C/C++ code. Elfriede Dustin, Douglas McDiarmid and Jeff Rashka, the writers of this article, provide some tests to see if your system is susceptible to a buffer overflow.
One common security hole exploited on Web systems is the buffer overflow, used mostly against compiled executables, particularly operating system tools and utilities. A buffer overflow occurs when the length of a program or a function input exceeds the space allocated to store it. C/C++ programs are particularly vulnerable to this kind of attack, as developers often declare variables that reside on the program's stack. For example, consider the following C/C++ code:
void createFullName(char* firstName, char* lastName)
strcat(fullName, " ");
This C++ code simply takes the supplied first and last names and puts them together, separated by a space. Of particular importance is the fullName variable. The way it is declared causes it to reside on the stack. The problem is that this variable can easily exceed 1,024 characters whenever firstName or lastName or both values are too long.
In most cases, this situation will simply cause a program crash as the stack is corrupted by the strcpy or strcat function calls. However, if these arguments are carefully crafted, they can in fact be used to send malicious code to the program, embedded in the first- and last-name arguments. If the arguments manage to overflow the fullName stack variable, they can cause the execution of this code by manipulating the return address, which also resides on the stack.
The return address is a hidden piece of data that resides on the stack with the rest of the variables passed to a function. Depending on the specific language, compiled programs place this data on the stack prior to calling the function. That way, the program knows where to go when the function is finished.
By overflowing one of the variables on the stack, a malicious input can overwrite the return address, as it exists on the stack as well. In the example of createFullName, by overflowing the firstName or lastName inputs with precisely the correct number of characters, the return address can be overwritten and made to point back to a specific place in the data that was supplied in the firstName or lastName inputs. With a little creativity, this data can be executable program code, with malicious intent. Because the return address is simply a pointer to code, the return address is pulled off the stack when the function completes and is used as the place to start executing the next sequence of instructions. Unfortunately, the next sequence of instructions is code written by the attacker and will be blindly executed by the server.
Once the malicious program argument has been submitted and the input buffer has been successfully overflowed, the attacker effectively has his or her own code executing on the site's Web server machine. Depending on the buffer size, a lot of bad things can happen. The code could read the contents of the password file, e-mail the file, make changes to configuration files, start up a TELNET session, or even connect to another Web server and download a larger, more damaging program, such as a Trojan horse.
Preventing buffer overflows consists mainly of checking the length of user-supplied input variables. In the previous example, limiting the size of the firstName and the lastName inputs to 511 bytes would protect against overflows (511*2 = 1,022, plus one for the space and one for the terminating "null" totals 1,024). In addition, using the strncpy and the strncat functions instead of strcpy and strcat is also advised, as the former two functions limit the number of characters copied into the buffer. Keep in mind that it is not realistic to restrict the input on the Web page or form, as a malicious user could simply alter the page on his or her local disk and remove the length restrictions on the form fields or simply access the URL of the form action directly, without using the form itself. The only sure way to prevent buffer overflows is to examine and to reject excessively long inputs in all Web system components.
Again, the most common source of buffer overflows is C/C++ code, particularly when string manipulation is involved. Scripting languages, such as Perl and Java code are less of a risk, as they use dynamic memory management to allocate space for variables. This does not mean that input lengths should be ignored when using scripting languages or Java. It is still possible that these variables could be passed to other programs that are susceptible to buffer overflows.
Sometimes, variables are simply "passed through" a program. For example, a Perl script could be created that takes a form input, such as a customer name, and simply hands it off to another program, possibly one written in C++ or one that came with the operating system that was probably also written in C or C++. The second program in the chain may be susceptible to buffer overflows, so the attacker could still cause damage. It just wouldn't affect the Perl script, as it is passing it through. Therefore, it's important to always check input lengths regardless of language, function and so on.
Testing for buffer overflows
Much like testing component inputs for dangerous metacharacters, testing buffer overflows requires that the component actively check the lengths of all inputs. Component inputs are either HTML form fields or name/value pairs. A name/value pair is a field name and a value, separated by = on the URL line and is formatted as a name/value pair. A sample set of input might look something like name=Norma, orderID=12345.
When an input string that exceeds the maximum length is detected, the component should return a predetermined error page. The successful display of the error page represents a successful test result. The security test procedure is concerned with verifying that an overflow condition cannot be created, by supplying long inputs to the component. The error message produced is simply a way to confirm that the component has properly detected the invalid input and then aborted the activity.
- The list of Web system components should be augmented with the maximum length of each input that the component will accept. If the engineering techniques described in Chapter 2 are used, these lengths should be documented in the interface use case descriptions. If the input length exceeds this value, the component must return the predetermined error page. If the error page is not returned, a security flaw exists.
- For each input to the component, supply an input string that exceeds the maximum size by one character. This should return the predetermined error page. Also try the test with an input string that is exactly equal to the maximum size of the field and watch the behavior of the component with this input. Boundary conditions often cause problems when dealing with buffers and string operations. Any printable character is acceptable as input, as long as it is within the valid set of characters that the component is willing to process. A long string of A characters will usually suffice.
Typically this type of test must be performed either by saving the HTML page to disk and manually editing the value attribute of an input field or by using an automated testing tool to send in the data. As with metacharacter testing, make sure that each field is tested one at a time so any problematic fields can be isolated.
Read more of this article at InformIT. Registration is required, but it's free.