Vulnerability library  /  OS command injection

Operating system command injection

OS command injection

OS command injection occurs when untrusted input changes a command executed by the operating system. It can let an attacker run commands with the application's privileges and often leads to full server compromise.

Also known as
Shell injection · Command injection · OS command injection
Typical severity · High to Critical
Last updated
On this page: What it is
  1. 1 · What it is
  2. 2 · Why it matters
  3. 3 · Examples
  4. 4 · How to fix it
  5. 5 · How to test for it
  6. References
  7. Related

What it is#

Applications sometimes invoke operating-system utilities for conversion, diagnostics, archive handling, media processing, or administrative work. Command injection occurs when untrusted data is combined with a command string and interpreted by a shell.

Shell metacharacters can turn one intended argument into additional commands, redirections, pipelines, substitutions, or environment changes. The same risk can appear without an obvious shell when an application invokes a dangerous interpreter, accepts attacker-controlled option flags, or searches an untrusted PATH for an executable.

OS command injection is different from argument injection. With argument injection, the intended executable still runs, but attacker-controlled values are interpreted as unintended options or file locations. Both require strict control.

Why it matters#

  • Execute commands as the web server, worker, service account, or container user.
  • Read application secrets, source code, credentials, and customer data.
  • Modify files, application behavior, scheduled jobs, or deployment artifacts.
  • Reach internal services using the compromised server's network access.
  • Establish persistence or pivot into other systems when the service has excessive privilege.
  • Cause outages by terminating processes, deleting data, or exhausting resources.
Typical severity

Usually high or critical. Severity depends on reachability, application privileges, network access, secret exposure, and whether the vulnerable process is meaningfully isolated. Actual severity depends on context: who is affected, what data or actions are reachable, and existing controls.

Examples#

✕ Vulnerablejavascript
// Vulnerable: interpolation creates a shell program.
import { exec } from 'node:child_process';

app.post('/tools/lookup', (req, res) => {
  exec(`/usr/bin/dig ${req.body.hostname}`, (error, stdout) => {
    if (error) return res.sendStatus(400);
    res.type('text/plain').send(stdout);
  });
});
✓ Fixedjavascript
// Fixed: validate the value and pass it as one argument without a shell.
import { execFile } from 'node:child_process';

const hostnamePattern = /^(?=.{1,253}$)(?:[a-z0-9](?:[a-z0-9-]{0,61}[a-z0-9])?\.)*[a-z0-9](?:[a-z0-9-]{0,61}[a-z0-9])?$/i;

app.post('/tools/lookup', (req, res) => {
  const hostname = String(req.body.hostname ?? '');
  if (!hostnamePattern.test(hostname)) return res.sendStatus(400);

  execFile('/usr/bin/dig', ['+short', '--', hostname], {
    shell: false,
    timeout: 5000,
    maxBuffer: 64 * 1024,
  }, (error, stdout) => {
    if (error) return res.sendStatus(400);
    res.type('text/plain').send(stdout);
  });
});

The -- end-of-options marker is useful only when the target program supports it. Validation and a fixed argument structure remain necessary.

How to fix it#

1. Remove the operating-system command when possible. Use a maintained library or platform API for DNS, archive handling, image conversion, file operations, and network requests.

2. Never construct a shell string from untrusted data. Use process APIs that accept an absolute executable path and an argument array. Set shell: false explicitly where supported.

3. Allowlist values by meaning. Validate hostnames, identifiers, dimensions, formats, and paths against the smallest required grammar. Do not rely on escaping a general-purpose shell language.

4. Prevent option injection. Keep options server controlled, use -- where supported, and reject values beginning with option prefixes when they are not valid business data.

5. Control execution context. Set a minimal environment and fixed working directory. Do not inherit an attacker-controlled PATH, interpreter variable, locale, or library search path.

6. Run with least privilege. Use an unprivileged service account, read-only filesystem, restrictive container profile, bounded CPU and memory, no unnecessary secrets, and limited outbound network access.

7. Enforce execution limits. Set timeouts, output limits, concurrency limits, and process-group cleanup. Do not return raw command errors that expose filesystem or environment details.

8. Centralize reviewed wrappers. Route approved commands through a small allowlisted adapter rather than letting each handler launch processes independently.

How to test for it#

  • Search for process-launch APIs, shell invocations, interpreters, archive tools, converters, and system-management wrappers.
  • Trace every argument and environment value back to its source, including filenames, headers, database fields, and queued job data.
  • In a disposable test environment, submit harmless reserved characters and option-like values and verify they are rejected or passed as literal data.
  • Confirm through process instrumentation that the expected absolute executable runs with a fixed argument vector and shell disabled.
  • Test timeouts, output limits, concurrent requests, and child-process cleanup without running destructive commands.
  • Verify the service account cannot write application code, read unrelated secrets, or reach unnecessary internal networks.
  • Add unit tests for the validation grammar and integration tests that inspect the exact executable and argument array.

References#

Permalinklibrary.infinity8labs.com/os-command-injection/JSON
Found this in Breachvane? Your finding links here for background. Evidence, affected assets and retest status are in your Breachvane ↗ (opens in a new tab) workspace. Need help fixing it? Talk to us.