|
How to Test for Format String
Vulnerabilities
Applies to
Compiled code (C/C++)
Summary
Testing for format string vulnerabilities involves
the following 4 steps:
1. Identify entry points.
2. Craft attack data for each entry point.
3. Pass attack data to each entry point.
4. Look for application crashes or corrupt output.
1. Identify entry points
Entry points are the means by which you can provide input to the
application under test. The following are common entry points:
- Public APIs
- Web service methods
- DCOM methods
- Network ports
- UI input fields
- File input (can take the form of
configuration, data, or serialization information)
- Registry input
As you explore the set of entry points look for
the ability to provide string input or data that may be used later in
output – either to stdout or to a log – using the printf() family of
functions or any other function that can take a format string. A format
string is a format specification used by functions such as printf and
scanf to replace variables within the string with parameter values
passed to the function. The set of functions that include format strings
includes:
- Sprintf
- _snprintf
- Printf
- Fprintf
- Scanf
- Fscanf
- Function #7
- Function #8
- etc.
Some examples of string input entry points are:
- Web application UI input field such as
username, password, or search box.
- Thick client application UI input field
such as configuration options, search, or username
- Public API with a string parameter type
- String data in a file
- Entry Point #5
- Entry Point #6
- Etc.
There are a couple of common scenarios in which
input data may be used in a related to a function call that takes a
format string:
- File names – usually an error message of
the type (“Unable to find file %s”) is printed to the log or stdout.
- Logged entities – such as usernames,
request identifiers, and/or methods
For instance, the following code uses a format
string to print a file name:
snprintf(buf, BUFSIZE, “Error code %d: File %s not found”, code, s);
fprintf (stderr, buf);
2. Craft attack data for each entry
point
- Detailed information similar to #1
"Identify Entry Points"
3. Pass attack data to each entry
point.
- Detailed information similar to #1
"Identify Entry Points"
4. Look for application crashes or
corrupt output.
- Detailed information similar to #1
"Identify Entry Points"
Repro Example
Flawed Code
In this example, the user input is treated as a filename to be opened.
If it is not found, an error message is formatted, with the filename
embedded in it. Note the use of snprintf() would prevent a buffer
overflow attack. A Format string bug, however, still lurks in the code.
#define BUFSIZE 1024 #define ERR_FILE_NOT_FOUND 42
void error(int code, char *s) { char buf[BUFSIZE]; switch (code) { Case
ERR_FILE_NOT_FOUND: snprintf(buf, BUFSIZE, “Error code %d: File %s not
found”, code, s); } /* Log to standard Error.. */
fprintf (stderr, buf); /* Also, Possibly write contents of buf to a
logfile */ }
int main(int argc, char **argv) { /* attempt to open command line
argument as a file */ /* if unsuccessful, report an error */
error(ERR_FILE_NOT_FOUND, argv[1]);
Test Example
To exploit the flawed code above, the following attack string can be
used:
- Passing “Foo%x%x%x%x%x%x%x%x%x%x” as the command line argument
will cause a stack pop - revealing itself to the user as corrupt
output.
- Passing “Foo%n%n%n” as the command line argument will write into
arbitrary memory – revealing itself to the user as an application
crash.
See Also
Related Items
|