CWE-242: Use of Inherently Dangerous Function
Overview
Use of inherently dangerous functions occurs when code uses functions known to be unsafe (strcpy, gets, eval, exec, system) that lack bounds checking, enable code injection, or bypass security controls, leading to buffer overflows, code execution, or injection attacks.
Risk
High: Dangerous functions cause buffer overflows (strcpy, sprintf), arbitrary code execution (eval, exec, system), format string bugs (printf with user input), race conditions (tmpnam), and SQL injection (string concatenation). These are commonly exploited in attacks.
Remediation Steps
Core principle: Avoid dangerous APIs/functions; replace with safe alternatives and enforce bans.
Locate Dangerous Function Usage
When reviewing security scan results:
- Find unsafe C functions: strcpy, sprintf, gets, strcat, scanf
- Check dynamic code execution: eval(), exec(), system(), shell_exec()
- Identify format string bugs: printf/sprintf with user input as format
- Look for SQL string building: String concatenation for queries
- Check temp file functions: tmpnam, mktemp (race conditions)
Common dangerous functions:
// C/C++ - Buffer overflow risks
strcpy(dest, src); // No bounds checking
sprintf(buf, "%s", user); // No size limit
gets(buffer); // Always unsafe
strcat(dest, src); // No bounds checking
scanf("%s", buf); // No size limit
// Dynamic code execution
eval(user_input); // JavaScript/PHP/Python
exec(command); // Python/PHP
system(cmd); // C/PHP/Ruby
shell_exec($cmd); // PHP
// Format strings
printf(user_input); // Format string vulnerability
syslog(LOG_INFO, user); // Format string vulnerability
Replace with Safe Alternatives (Primary Defense)
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
// VULNERABLE - strcpy has no bounds checking
void copy_string_bad(const char *src) {
char dest[64];
// Attack: src longer than 64 bytes = buffer overflow
strcpy(dest, src); // NO BOUNDS CHECKING!
}
// SECURE - use strncpy with explicit size
void copy_string_safe(const char *src) {
char dest[64];
// Copy at most sizeof(dest)-1 bytes
strncpy(dest, src, sizeof(dest) - 1);
// Always null-terminate
dest[sizeof(dest) - 1] = '\0';
}
// BETTER - use strlcpy (BSD) if available
void copy_string_better(const char *src) {
char dest[64];
// strlcpy always null-terminates and returns source length
size_t len = strlcpy(dest, src, sizeof(dest));
if (len >= sizeof(dest)) {
// Source was truncated
fprintf(stderr, "String truncated\n");
}
}
// BEST - use C11 strcpy_s if available
void copy_string_best(const char *src) {
char dest[64];
// strcpy_s checks bounds and returns error
errno_t result = strcpy_s(dest, sizeof(dest), src);
if (result != 0) {
fprintf(stderr, "String copy failed\n");
return;
}
}
// VULNERABLE - sprintf has no size limit
void format_string_bad(const char *username) {
char message[100];
// Attack: username > 93 chars = buffer overflow
sprintf(message, "Hello, %s!", username);
}
// SECURE - snprintf with size limit
void format_string_safe(const char *username) {
char message[100];
// snprintf limits output to buffer size
int written = snprintf(message, sizeof(message), "Hello, %s!", username);
if (written >= sizeof(message)) {
fprintf(stderr, "Output truncated\n");
}
}
// VULNERABLE - gets() is always unsafe
void read_input_bad() {
char buffer[100];
// NEVER USE gets() - no way to limit input!
gets(buffer); // Removed from C11 standard
}
// SECURE - use fgets with size limit
void read_input_safe() {
char buffer[100];
// fgets limits input to buffer size
if (fgets(buffer, sizeof(buffer), stdin)) {
// Remove trailing newline
buffer[strcspn(buffer, "\n")] = 0;
}
}
Function replacement table: | Dangerous | Safe Alternative | |-----------|------------------| | strcpy | strncpy, strlcpy, strcpy_s | | sprintf | snprintf, sprintf_s | | strcat | strncat, strlcat, strcat_s | | gets | fgets | | scanf | fgets + sscanf, scanf_s | | tmpnam | mkstemp |
Avoid Dynamic Code Execution
// VULNERABLE - eval with user input
function calculate_bad(expression) {
// Attack: expression = "process.exit()" or "require('fs').unlinkSync('/')"
const result = eval(expression); // ARBITRARY CODE EXECUTION!
return result;
}
// SECURE - use safe parser
const math = require('mathjs');
function calculate_safe(expression) {
try {
// mathjs parser only allows math operations
const result = math.evaluate(expression);
return result;
} catch (e) {
throw new Error('Invalid expression');
}
}
// VULNERABLE - system() with user input
const { exec } = require('child_process');
function process_file_bad(filename) {
// Attack: filename = "file.txt; rm -rf /"
exec(`cat ${filename}`, (error, stdout) => {
// COMMAND INJECTION!
});
}
// SECURE - use array form (no shell)
const { execFile } = require('child_process');
function process_file_safe(filename) {
// Validate filename first
if (!/^[a-zA-Z0-9_.-]+$/.test(filename)) {
throw new Error('Invalid filename');
}
// Use execFile with array (no shell interpretation)
execFile('cat', [filename], (error, stdout) => {
if (error) {
console.error('Error:', error);
return;
}
console.log(stdout);
});
}
// BETTER - use Node.js fs module directly
const fs = require('fs');
function process_file_better(filename) {
// No shell involved at all
fs.readFile(filename, 'utf8', (err, data) => {
if (err) {
console.error('Error:', err);
return;
}
console.log(data);
});
}
Fix Format String Vulnerabilities
#include <syslog.h>
// VULNERABLE - user input as format string
void log_message_bad(const char *user_message) {
// Attack: user_message = "%s%s%s%s%s%s" = crash or info leak
syslog(LOG_INFO, user_message); // FORMAT STRING BUG!
}
// SECURE - use %s format specifier
void log_message_safe(const char *user_message) {
// User input is data, not format string
syslog(LOG_INFO, "%s", user_message);
}
// VULNERABLE - printf with user input
void display_error_bad(const char *error) {
// Attack: error = "%x%x%x%x" = memory disclosure
printf(error); // FORMAT STRING BUG!
}
// SECURE - explicit format string
void display_error_safe(const char *error) {
printf("%s\n", error);
}
Use Safe Temporary File Creation
#include <stdlib.h>
#include <unistd.h>
#include <fcntl.h>
// VULNERABLE - tmpnam has race condition
void create_temp_bad() {
char tmpfile[L_tmpnam];
// Race condition: file could be created between tmpnam and fopen
tmpnam(tmpfile);
FILE *f = fopen(tmpfile, "w"); // RACE CONDITION!
}
// SECURE - mkstemp atomically creates file
void create_temp_safe() {
char template[] = "/tmp/myapp-XXXXXX";
// mkstemp creates file atomically with unique name
int fd = mkstemp(template);
if (fd == -1) {
perror("mkstemp failed");
return;
}
// Use file descriptor
FILE *f = fdopen(fd, "w");
// Remember to unlink when done
unlink(template);
}
Test for Dangerous Function Usage
// Test buffer overflow prevention
#include <assert.h>
#include <string.h>
void test_safe_string_copy() {
char dest[10];
const char *long_string = "This is a very long string that exceeds buffer";
// Test 1: Short string fits
const char *short_string = "hello";
strncpy(dest, short_string, sizeof(dest) - 1);
dest[sizeof(dest) - 1] = '\0';
assert(strcmp(dest, "hello") == 0);
printf("✓ Short string copy safe\n");
// Test 2: Long string truncated
strncpy(dest, long_string, sizeof(dest) - 1);
dest[sizeof(dest) - 1] = '\0';
assert(strlen(dest) == 9); // Truncated to fit
printf("✓ Long string truncated safely\n");
// Test 3: Always null-terminated
assert(dest[9] == '\0');
printf("✓ String always null-terminated\n");
}
void test_safe_format() {
char buffer[20];
// Test 1: Normal case
int written = snprintf(buffer, sizeof(buffer), "Count: %d", 42);
assert(written < sizeof(buffer));
assert(strcmp(buffer, "Count: 42") == 0);
printf("✓ snprintf normal case\n");
// Test 2: Truncation detected
written = snprintf(buffer, sizeof(buffer), "Very long message: %d", 12345);
assert(written >= sizeof(buffer)); // Indicates truncation
assert(buffer[sizeof(buffer) - 1] == '\0'); // Still null-terminated
printf("✓ snprintf truncation handled\n");
}
int main() {
test_safe_string_copy();
test_safe_format();
printf("All dangerous function replacement tests passed!\n");
return 0;
}
Static analysis checks:
# Enable compiler warnings for dangerous functions
gcc -Wall -Wextra -Wformat-security -Werror=format-security \
-D_FORTIFY_SOURCE=2 program.c
# Use clang static analyzer
clang --analyze program.c
# Use cppcheck
cppcheck --enable=warning,style program.c
# Bandit for Python (checks eval, exec, etc.)
bandit -r .
# ESLint for JavaScript (no-eval rule)
eslint --rule 'no-eval: error' .
Common Vulnerable Patterns
- strcpy() without bounds checking
- gets() - always unsafe, no bounds
- sprintf() without size limits
- eval() with user input
- system() with unsanitized input
Security Checklist
- No strcpy - replaced with strncpy/strlcpy/strcpy_s
- No sprintf - replaced with snprintf
- No gets - replaced with fgets
- No eval() with user input
- No system()/exec() with user input
- No user input as format strings
- No tmpnam - replaced with mkstemp
- Compiler warnings enabled and treated as errors
- Static analysis tools report no dangerous functions