CWE-78 · OWASP A03:2021

Command Injection: Examples, Impact, Detection, and Fixes

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.

What is command injection?

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.

Common vulnerable patterns

Risky patternSafer patternWhy
subprocess.run(..., shell=True)Argument list, shell=FalseAvoids 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

Examples by language

Python

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.

Node.js

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.

C

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.

Go

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.

How to detect command injection

Static analysis tools can trace taint from user-controlled sources to dangerous command execution sinks. Key source-sink pairs to detect:

  • Python: HTTP request parameters → subprocess.run/Popen with shell=True, os.system, os.popen
  • Node.js: Request parameters → exec, spawn with shell: true
  • C: User input → system(), popen()
  • Go: Request context → 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]

How Orbis AppSec detects command injection

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.

Source:HTTP request parameter / env variable
Sink:subprocess.run(..., shell=True), os.system()
Missing control:Allowlist validation, argument separation
CWE:CWE-78
Fix:Argument list, shell=False, input validation

Want Orbis AppSec to find and fix command injection in your GitHub repositories?

Try Orbis AppSec

Related CWEs

Real-world Orbis AppSec case studies

Browse posts where Orbis AppSec detected and fixed command injection vulnerabilities in real open-source repositories:

View all command injection case studies

FAQ

What is command injection?

Command injection is a vulnerability where attacker-controlled input changes the OS command executed by the application, potentially granting arbitrary command execution on the server.

What CWE is command injection?

CWE-78: Improper Neutralization of Special Elements used in an OS Command.

How do you prevent command injection in Python?

Avoid shell=True in subprocess calls, pass command arguments as a list, and validate user-controlled values against an allowlist before use.

Is escaping shell characters enough to prevent command injection?

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.

Can static analysis tools detect command injection?

Yes. Semgrep and CodeQL both offer rules that trace taint from user input to dangerous sinks like subprocess, exec, and system calls.

What is the OWASP classification for command injection?

Command injection falls under OWASP Top 10 A03:2021 – Injection.

References