CWE-77: Improper Neutralization of Special Elements used in OS Command (Command Injection)

CWE-77

Command Injection is one of the most direct and high-impact vulnerabilities in application security because it allows attackers to execute arbitrary operating system commands on the server.

CWE-77 occurs when software constructs and executes OS commands using unsanitized user input, allowing attackers to inject and run unintended commands.

In practical terms:

The application builds shell commands from user input without properly escaping or isolating that input.

This results in attackers moving from “influencing input” to controlling execution of the underlying system.

What Is OS Command Injection?

OS Command Injection happens when an application passes untrusted input into a system shell.

Unsafe example:

import os

filename = request.args["file"]
os.system("cat " + filename)

If an attacker supplies:

; rm -rf /

the executed command becomes:

cat ; rm -rf /

The system now executes unintended commands.

How Command Injection Actually Works

The vulnerability exists because the application allows input to be interpreted by a shell.

Attack Flow

  1. Application accepts user input
  2. Input is concatenated into OS command
  3. Shell interprets special characters
  4. Attacker injects additional commands
  5. Server executes attacker-controlled instructions

Visual: Command Injection Execution Flow

1. User Input Untrusted Data 2. Shell Concatenation No Sanitization 3. OS Execution Shell Interprets Input 4. Result Arbitrary Commands

Why Developers Still Get Command Injection Wrong

Misuse of Shell Execution APIs

Developers use:

  • os.system()
  • subprocess.call(shell=True)
  • Backtick execution
  • Inline shell commands

These all invoke a shell interpreter.

False Sense of Safety from Simple Filtering

Developers try to block characters like:

  • ;
  • &
  • |

Attackers quickly bypass these with encoding or alternate syntax.

Treating OS Commands Like Function Calls

Developers forget:

The shell is not a function call—it is a language interpreter.

Overtrusting Internal Tools

Scripts assumed “safe” become attack surfaces when exposed to user input.

Modern Exploitation Techniques

Command Chaining

Inject additional commands using:

  • ;
  • &&
  • ||
  • newline injection

Subshell Execution

Using:

  • $()
  • Backticks

Environment Manipulation

Influencing:

  • PATH resolution
  • command lookup
  • binary substitution

Blind Command Injection

No output returned, but attacker uses:

  • DNS callbacks
  • time delays
  • outbound connections

Visual: Command Injection Exploitation Chain

Command Injection System Takeover Data Exfiltration Lateral Movement Full Host Compromise

Framework-Specific Mitigations

Never Use Shell Interpretation

Unsafe:

os.system("ping " + host)

Safe:

subprocess.run(["ping", host], shell=False)

Use Argument Arrays, Not Strings

Avoid shell parsing entirely.

Validate Strictly (Allowlist)

Only permit expected inputs:

  • Hostnames
  • file identifiers
  • numeric IDs

Avoid Shell Unless Absolutely Necessary

If you must use it:

  • Escape properly
  • Minimize privileges
  • Isolate execution

Secure Coding Examples

Unsafe

exec("convert " + filename + " output.png");

Safer

execFile("convert", [filename, "output.png"]);

Better

Use dedicated libraries instead of shell tools:

image_processing.convert(file)

Defense in Depth

Minimize OS Command Usage

Prefer:

  • Native libraries
  • APIs
  • SDKs

Run With Least Privilege

Even if injection occurs, impact is reduced.

Monitor Process Execution

Watch for:

  • Unexpected child processes
  • Shell invocation patterns
  • Suspicious command arguments

Treat All Input as Untrusted

Even “internal” input sources can be compromised.

Final Thoughts

Command Injection is severe because it collapses the boundary between application logic and operating system control.

It persists because:

  • Shell commands are convenient
  • Developers underestimate shell parsing complexity
  • Legacy patterns rely on string concatenation
  • Input validation is mistaken for command safety

The core lesson is simple:

Never allow untrusted input to be interpreted by a shell.

Once attackers reach the OS command layer, they are no longer interacting with your application—they are interacting with your infrastructure directly.