CWE-134 is the use of an externally-controlled format string. The product calls a function that accepts a format string as an argument, but the format string comes from an external source. A function built to print text is instead handed instructions chosen by the attacker.
MITRE lists C, C++ and Perl, plus "not language-specific", as platforms. The classic victims are logging and error message code, where a developer wants to print a variable and takes a shortcut.
How it happens
Format functions such as printf, sprintf, fprintf and syslog read a template string and replace markers like %s, %d and %x with the arguments that follow. The template is a tiny program. Some markers read values from the stack. The %n marker writes a number of bytes to an address.
If the template is attacker controlled, the attacker can add markers, and the function will go looking for arguments that were never passed.
// Vulnerable: user input is the format string
void log_request(const char *user_agent) {
printf(user_agent);
}
If user_agent contains %x %x %x %x, the function prints values from the stack. If it contains %s with nothing to match, the program may follow a garbage pointer and crash. With %n, a writer can modify memory.
The fix is a constant format string, with the data as an argument.
// Fixed: format is a literal, data is just data
void log_request(const char *user_agent) {
printf("%s", user_agent);
}
MITRE's observed examples show how persistent this is. CVE-2001-0717 and CVE-2002-0573 are both a bad call to the syslog function. CVE-2002-1788 is format strings in NNTP server responses. CVE-2002-1825 is a format string in a Perl program, a reminder that this is not only a C problem.
The same trap exists in wrapper functions. If you write void log_msg(const char *fmt, ...) and later call log_msg(user_input), you have rebuilt the bug one layer up.
What an attacker gains
MITRE lists two groups of consequences.
- Information disclosure. Format string problems allow reading memory, which can severely simplify exploitation of the program. Stack contents may include pointers, secrets or other data.
- Memory modification and code execution. They can result in arbitrary code execution, buffer overflows, denial of service or incorrect data representation.
The combination of read and write makes this weakness powerful. The read shows where things are, and the write changes them.
How to find it
MITRE's detection notes are specific.
- Static analysis. Source analysis tools can often find it with data flow techniques that keep false positives down. Look for any call where the format argument is not a string literal.
- Black box testing. MITRE says format strings often occur in rarely-occurring error conditions such as error message logging, so black box methods have difficulty finding them. It is highly likely that many latent issues exist in executables without source.
- Binary and bytecode analysis. MITRE notes weakness analysis using disassemblers and source code analysis can be highly cost effective for compiled code you cannot rebuild.
Compilers help. GCC and Clang warn about non-literal format strings with -Wformat-security and -Wformat-nonliteral. Turn these on and treat them as errors.
A quick manual test is to send %x%x%x%x or %s%s%s%s in every text field and header your service logs, and watch for strange output or crashes. Error paths deserve extra attention, since that is where MITRE says these bugs hide.
How to fix it
The record has no mitigation list. These steps follow from the definition and common practice.
- Always use a literal format string and pass external data as an argument:
printf("%s", s),syslog(LOG_INFO, "%s", msg). - Turn on format security warnings in the compiler and fail the build on them.
- Annotate your own logging wrappers with the format attribute, so the compiler checks callers:
__attribute__((format(printf, 1, 2))). - Avoid user-controlled strings in places that later feed a formatter, including log message text.
- In Perl and other languages, check which functions treat their first argument as a template.
- Prefer APIs where the template and data are separate by design.
// Wrapper that the compiler can check
void log_msg(const char *fmt, ...) __attribute__((format(printf, 1, 2)));
Catching it in code review
A static code scan is a good fit for this weakness, because the pattern is mechanical: a formatter whose first argument is not a literal. The demo below shows what a SAST scan report looks like and how findings are triaged.
Click through it at your own pace, or open it full screen.
Quick check
Why is printf(user_input) dangerous?
The input is treated as a template. Markers like percent x and percent s make the function read values the program did not provide, and percent n can write to memory.
Why do these bugs survive testing?
MITRE notes they often sit in rarely run error paths such as error message logging, so ordinary black box testing may never reach them.
What is the simplest fix?
Use a constant format string and pass the data as an argument, for example printf with the template percent s and the input as the second argument.