Command injection happens when untrusted user input reaches an operating system command without safe argument separation or validation. It is classified as CWE-78 and can give an attacker full control of the server. This guide covers vulnerable patterns, language-specific examples, detection rules, and secure fixes.
Command injection is a security vulnerability where an attacker manipulates the input to an application in a way that changes the OS-level command being executed. Instead of the intended command running, the attacker's payload runs instead — often with the privileges of the application process.
The root cause is always the same: user-controlled data flows into a shell execution API without safe separation of arguments from the command string. When a shell interprets the combined string, special characters like ;, &, |, and backticks allow injection of additional commands.
| Risky pattern | Safer pattern | Why |
|---|---|---|
| subprocess.run(..., shell=True) | Argument list, shell=False | Avoids shell interpretation |
| os.system(cmd_string) | subprocess.run([...], shell=False) | Bypasses shell entirely |
| exec(`cmd ${userInput}`) | execFile('cmd', [userInput]) | No shell string expansion |
| system(sprintf(buf, ...)) | execv/execvp with argv[] | Arguments never parsed by shell |
| exec.Command("sh", "-c", ...) | exec.Command("cmd", arg1, arg2) | Direct exec, no shell |
Vulnerable
import subprocess
user_input = request.args.get("filename")
# Dangerous: shell=True lets the shell interpret the input
subprocess.run(f"convert {user_input} output.pdf", shell=True)Secure
import subprocess
user_input = request.args.get("filename")
# Safe: argument list bypasses shell interpretation
subprocess.run(["convert", user_input, "output.pdf"], shell=False)Avoid shell=True. Pass arguments as a list.
Vulnerable
const { exec } = require("child_process");
const filename = req.query.file;
// Dangerous: user input interpolated into shell string
exec(`convert ${filename} output.pdf`, callback);Secure
const { execFile } = require("child_process");
const filename = req.query.file;
// Safe: execFile never invokes a shell
execFile("convert", [filename, "output.pdf"], callback);Use execFile instead of exec. Never interpolate user input into shell strings.
Vulnerable
char cmd[256]; snprintf(cmd, sizeof(cmd), "ls %s", user_input); // Dangerous: system() passes to shell system(cmd);
Secure
// Safe: execv bypasses shell entirely
char *args[] = {"ls", user_input, NULL};
execv("/bin/ls", args);Replace system() with execv/execvp. Never build shell command strings from user input.
Vulnerable
// Dangerous: shell string lets input escape
cmd := exec.Command("sh", "-c", "convert "+userInput+" output.pdf")
cmd.Run()Secure
// Safe: no shell, arguments passed directly
cmd := exec.Command("convert", userInput, "output.pdf")
cmd.Run()Pass arguments directly to exec.Command. Never use sh -c with user input.
Static analysis tools can trace taint from user-controlled sources to dangerous command execution sinks. Key source-sink pairs to detect:
subprocess.run/Popen with shell=True, os.system, os.popenexec, spawn with shell: truesystem(), popen()exec.Command("sh", "-c", ...)# Semgrep rule: detect shell=True with user-controlled input (Python)
rules:
- id: subprocess-shell-true-injection
patterns:
- pattern: subprocess.$FUNC(..., shell=True, ...)
- pattern-not: subprocess.$FUNC([...], shell=True, ...)
message: User-controlled input may reach subprocess with shell=True (CWE-78)
severity: ERROR
languages: [python]Orbis AppSec identifies dangerous taint flows from user-controlled sources to shell execution sinks and opens an automated fix pull request replacing the vulnerable pattern with a safe argument array.
Want Orbis AppSec to find and fix command injection in your GitHub repositories?
Try Orbis AppSecBrowse posts where Orbis AppSec detected and fixed command injection vulnerabilities in real open-source repositories:
View all command injection case studiesCommand injection is a vulnerability where attacker-controlled input changes the OS command executed by the application, potentially granting arbitrary command execution on the server.
CWE-78: Improper Neutralization of Special Elements used in an OS Command.
Avoid shell=True in subprocess calls, pass command arguments as a list, and validate user-controlled values against an allowlist before use.
Escaping is fragile — edge cases, encoding differences, and bypass techniques make it unreliable. Avoiding shell interpretation entirely by using argument arrays is the correct fix.
Yes. Semgrep and CodeQL both offer rules that trace taint from user input to dangerous sinks like subprocess, exec, and system calls.
Command injection falls under OWASP Top 10 A03:2021 – Injection.