HIGH CWE-77 Input Validation

CWE-77 in APIs

CWE ID
CWE-77
Category
Input Validation
Severity
CRITICAL
Short Name
Command Injection

What is CWE-77?

CWE-77, Improper Neutralization of Special Elements used in a Command ('Command Injection'), occurs when an application constructs commands using untrusted input without properly neutralizing special elements that could modify the intended command structure. This weakness allows attackers to execute arbitrary commands on the underlying operating system.

The core issue is that user-controlled data flows into a command interpreter (like a shell) without proper validation or escaping. Attackers can inject malicious syntax—such as semicolons, ampersands, pipes, or backticks—to chain additional commands or alter the original command's behavior.

For example, if an API endpoint accepts a filename parameter and passes it directly to an OS command like cat /path/to/files/, an attacker could supply file.txt; rm -rf / to delete files after reading the intended file.

CWE-77 in API Contexts

APIs are particularly vulnerable to command injection because they often need to interact with system utilities, execute scripts, or manage files. Common API scenarios include:

  • File operations: Using user input to construct ls, cat, or find commands
  • Process management: Starting, stopping, or monitoring services based on user input
  • Database interactions: Executing system commands through database interfaces
  • Container orchestration: Running Docker commands with user-supplied parameters
  • Code execution: Dynamic compilation or evaluation of user-provided code

API endpoints that accept parameters for system commands are especially risky. For instance, a file search API might construct a command like grep -r "pattern" /data where "pattern" comes from user input. An attacker could supply pattern"; rm -rf /data to execute arbitrary commands.

Modern APIs using microservices architectures often invoke system commands for logging, monitoring, or inter-service communication, creating multiple injection points. Even containerized APIs aren't immune—commands executed inside containers can still compromise the host system if proper isolation isn't maintained.

Detection

Detecting command injection requires both static analysis and dynamic testing. Static analysis tools examine source code for patterns like:

const exec = require('child_process').exec;
exec('ls ' + userInput); // Vulnerable
exec(`ls ${userInput}`); // Vulnerable
exec('ls ' + JSON.stringify(userInput)); // Still vulnerable

Dynamic testing involves sending malicious payloads to API endpoints and observing responses. Common test payloads include:

;
&
|
$(command)
`command`
$(sleep 10)
$(whoami)

middleBrick's black-box scanning approach tests APIs without requiring source code access. The scanner automatically sends command injection payloads to all parameters and analyzes responses for indicators like:

  • Unexpected output containing system information
  • Changes in response timing (indicating command execution)
  • Error messages revealing command execution context
  • HTTP status codes that suggest command execution (like 500 errors from failed commands)

The scanner tests 12 security categories in parallel, including input validation and unsafe consumption checks that catch command injection vulnerabilities. For APIs with OpenAPI specifications, middleBrick cross-references parameter definitions with runtime findings to identify mismatches between documented and actual behavior.

Remediation

The fundamental fix for command injection is to avoid constructing commands with user input entirely. Instead, use safe alternatives:

// Vulnerable - command injection possible
const { exec } = require('child_process');
exec('ls ' + req.query.path, (err, stdout) => {
console.log(stdout);
});

// Safe - use file system APIs
const fs = require('fs');
const path = require('path');
const safePath = path.join('/data', req.query.path);
fs.readdir(safePath, (err, files) => {
if (err) throw err;
console.log(files);
});

When system commands are unavoidable, use argument arrays instead of string concatenation:

// Vulnerable - string concatenation
exec('grep -r "' + pattern + '" /data');

// Safe - argument array
const { execFile } = require('child_process');
execFile('grep', ['-r', pattern, '/data']);

Additional hardening techniques include:

  • Input validation: Allow only expected characters (alphanumeric, specific symbols)
  • Whitelisting: Restrict input to known safe values
  • Path normalization: Resolve and validate file paths before use
  • Least privilege: Run API processes with minimal permissions
  • Input encoding: Use appropriate encoding for the context

For containerized APIs, combine these fixes with proper container isolation, read-only filesystems where possible, and network segmentation to limit blast radius if injection occurs.

Frequently Asked Questions

How does command injection differ from SQL injection?
Command injection targets the operating system shell, while SQL injection targets database query parsers. Both involve injecting malicious syntax, but command injection uses shell metacharacters (;, |, $, `) while SQL injection uses SQL syntax (', --, /* */). The remediation approaches also differ: command injection requires avoiding shell commands or using argument arrays, while SQL injection requires parameterized queries or prepared statements.
Can command injection occur in serverless APIs?
Yes, serverless functions can still be vulnerable to command injection. While the execution environment differs, serverless functions often invoke system commands for file operations, external service calls, or process management. The same principles apply: avoid shell command construction with user input, use safe APIs, and validate all inputs. Serverless isolation provides some protection, but vulnerabilities within the function's execution context remain exploitable.