Indirect uncontrolled command line¶
ID: js/indirect-command-line-injection
Kind: path-problem
Security severity: 6.3
Severity: warning
Precision: medium
Tags:
- correctness
- security
- external/cwe/cwe-078
- external/cwe/cwe-088
Query suites:
- javascript-security-extended.qls
- javascript-security-and-quality.qls
Click to see the query in the CodeQL repository
Forwarding command-line arguments to child_process.exec
or some other library routine that executes a system command within a shell can change the meaning of the command unexpectedly due to unescaped special characters.
When the forwarded command-line arguments come from a parent process that has not escaped the special characters in the arguments, then the parent process may indirectly be vulnerable to command-line injection since the special characters are evaluated unexpectedly.
Recommendation¶
If possible, use APIs that don’t run shell commands and accept command arguments as an array of strings rather than a single concatenated string. This is both safer and more portable.
If given arguments as a single string, avoid simply splitting the string on whitespace. Arguments may contain quoted whitespace, causing them to split into multiple arguments. Use a library like shell-quote
to parse the string into an array of arguments instead.
If this approach is not viable, then add code to verify that each forwarded command-line argument is properly escaped before using it.
Example¶
The following wrapper script example executes another JavaScript file in a child process and forwards some command-line arguments. This is problematic because the special characters in the command-line arguments may change the meaning of the child process invocation unexpectedly. For instance, if one of the command-line arguments is "dollar$separated$name"
, then the child process will substitute the two environment variables $separated
and $name
before invoking node
.
var cp = require("child_process");
const args = process.argv.slice(2);
const script = path.join(__dirname, 'bin', 'main.js');
cp.execSync(`node ${script} ${args.join(' ')}`); // BAD
If another program uses child_process.execFile
to invoke the above wrapper script with input from a remote user, then there may be a command-line injection vulnerability. This may be surprising, since a command-line invocation with child_process.execFile
is generally considered safe. But in this case, the remote user input is simply forwarded to the problematic process.exec
call in the wrapper script.
To guard against this, use an API that does not perform environment variable substitution, such as child_process.execFile
:
var cp = require("child_process");
const args = process.argv.slice(2);
const script = path.join(__dirname, 'bin', 'main.js');
cp.execFileSync('node', [script].concat(args)); // GOOD
If you want to allow the user to specify other options to node
, you can use a library like shell-quote
to parse the user input into an array of arguments without risking command injection:
var cp = require("child_process"),
shellQuote = require("shell-quote");
const args = process.argv.slice(2);
let nodeOpts = '';
if (args[0] === '--node-opts') {
nodeOpts = args[1];
args.splice(0, 2);
}
const script = path.join(__dirname, 'bin', 'main.js');
cp.execFileSync('node', shellQuote.parse(nodeOpts).concat(script).concat(args)); // GOOD
References¶
OWASP: Command Injection.
npm: shell-quote.
Common Weakness Enumeration: CWE-78.
Common Weakness Enumeration: CWE-88.