Prevent Command Injection in Node.js child_process
Using the child_process module in Node.js allows
developers to execute system commands, but it introduces severe security
risks like command injection if user input is handled improperly. This
article outlines the essential best practices for securing your Node.js
applications against command injection, focusing on using safe APIs,
avoiding shell execution, and validating inputs.
1. Avoid Using exec
and execSync
The exec and execSync functions spawn a
shell (such as /bin/sh on Unix or cmd.exe on
Windows) to execute the passed string. If user input is concatenated
directly into this string, attackers can append malicious commands using
shell metacharacters like ;, &, or
|.
Vulnerable Example:
const { exec } = require('child_process');
// If user_input is "image.png; rm -rf /", the system will delete files
exec(`convert ${user_input} output.png`); 2. Use execFile or
spawn Instead
The safest alternative is to use execFile,
execFileSync, spawn, or
spawnSync. These functions do not invoke a system shell by
default. Instead, they execute the file directly and pass arguments as
an array. Since no shell is invoked, shell metacharacters are treated as
literal arguments rather than executable commands.
Secure Example:
const { execFile } = require('child_process');
// Arguments are passed safely as an array
execFile('convert', [user_input, 'output.png'], (error, stdout, stderr) => {
if (error) {
throw error;
}
console.log(stdout);
});3. Keep shell: false
When using spawn or execFile, you can pass
an options object. Ensure you do not set the shell option
to true. Setting { shell: true } forces
Node.js to run the command inside a shell, which reintroduces the exact
command injection vulnerabilities that spawn is designed to
prevent.
Vulnerable Example (Avoid this):
// This makes spawn just as vulnerable as exec
spawn('convert', [user_input, 'output.png'], { shell: true }); 4. Implement Strict Input Validation
Even when using safe APIs like execFile, you should
still validate and sanitize all user inputs. Implement a strict
allowlist of permitted characters, formats, or values.
- Type validation: Ensure the input matches the expected data type (e.g., integer, boolean).
- Pattern matching: Use regular expressions to allow only safe characters (e.g., alphanumeric characters and specific safe punctuation).
- Path sanitization: If the input is a file path, resolve and verify it to prevent directory traversal attacks.
// Example of strict regex validation for alphanumeric filenames
const safeInputPattern = /^[a-zA-Z0-9_\-\.]+$/;
if (!safeInputPattern.test(user_input)) {
throw new Error("Invalid input detected.");
}5. Run with Least Privilege
If a command injection vulnerability is ever successfully exploited,
the damage is limited by the privileges of the Node.js process. Never
run your Node.js application as the root or
Administrator user. Run the process under a dedicated,
low-privilege system user account that only has access to the specific
directories and binaries required for operation.