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
- Application accepts user input
- Input is concatenated into OS command
- Shell interprets special characters
- Attacker injects additional commands
- Server executes attacker-controlled instructions
Visual: Command Injection Execution Flow
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
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.

One thought on “CWE-77: Improper Neutralization of Special Elements used in OS Command (Command Injection)”