flawopen.com/Command Injection/C/C++

Command Injection in C/C++

Critical CWE-78 Draft — pending review
ELI5

Imagine a train operator who announces stations. If an attacker slips a note saying 'Next stop Central; derail train', command injection is the system executing the derail instruction because it didn't separate the station name from the train controls.

Key terms on this page
system() & popen()
C runtime functions that invoke /bin/sh -c with the passed string, exposing full shell metacharacter parsing.
execve() & execvp()
POSIX system calls that execute a binary directly with an array of pointer arguments, completely bypassing the shell.

What's happening

In C and C++, system(const char *command) and popen() are the simplest ways to run a sub-process, but both invoke /bin/sh -c. Concatenating user buffers with sprintf or strcat results in full remote command execution.

Real-world impact

Embedded devices, IoT firmware, and networking appliances (routers, firewalls, IP cameras) overwhelmingly suffer from command injection in C-based CGI binaries and web management interfaces.

CISA KEV (Known Exploited Vulnerabilities) Catalog.

Vulnerable vs. fixed

VULNERABLE
// system() invokes /bin/sh with sprintf buffer
#include 
#include 

void ping_host(const char *user_input) {
    char buffer[256];
    // Attacker input: "127.0.0.1; reboot"
    snprintf(buffer, sizeof(buffer), "ping -c 1 %s", user_input);
    system(buffer);
}
FIXED
// fork() and execvp() execute binary directly without a shell
#include 
#include 

void ping_host(const char *user_input) {
    pid_t pid = fork();
    if (pid == 0) {
        // Child process: arguments are discrete pointers
        char *args[] = {"ping", "-c", "1", (char *)user_input, NULL};
        execvp("ping", args);
        _exit(1);
    } else {
        waitpid(pid, NULL, 0);
    }
}

Why the fix works

execvp passes the pointer array directly to the Linux/Unix kernel. The kernel replaces the child process image with ping; no shell interpreter exists, so metacharacters have no effect.

Gotchas

popen() is identical to system()

popen(cmd, "r") has the exact same vulnerability as system() because it also launches /bin/sh to manage the pipe.

Common misconceptions

"C++ std::string prevents command injection"

std::string prevents buffer overflows, but passing an std::string.c_str() to system() is still 100% vulnerable to command injection.

How to check if you're affected

grep -rn "system(" --include="*.c" --include="*.cpp" . grep -rn "popen(" --include="*.c" --include="*.cpp" .
Enable Clang Static Analyzer rule security.insecureAPI.system in your compiler flags.

Prevention checklist

FAQ

How do I capture stdout in C without popen?

Create an OS pipe using pipe(), redirect stdout in the child process using dup2(), and read from the pipe in the parent process after fork().

References

View in: Python JavaScript Go Java PHP C# Ruby C/C++ Rust Kotlin Swift Solidity (N/A)
Also see: SQL Injection XSS Path Traversal Insecure Deserialization