间接不受控的命令行¶
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
将命令行参数转发到
或其他在 shell 中执行系统命令的库例程可能会由于未转义的特殊字符而意外地改变命令的含义。child_process.exec
当转发来的命令行参数来自未转义参数中的特殊字符的父进程时,由于特殊字符意外地被求值,父进程可能会间接地容易受到命令行注入攻击。
建议¶
如果可能,使用不运行 shell 命令的 API,并将命令参数作为字符串数组而不是单个连接的字符串接受。这样既更安全,也更便携。
如果以单个字符串的形式给出参数,避免简单地按空格分割字符串。参数可能包含带引号的空格,导致它们被分割成多个参数。使用类似
的库来解析字符串,将其变成参数数组。shell-quote
如果这种方法不可行,那么在使用转发来的命令行参数之前,添加代码来验证每个参数是否已正确转义。
示例¶
以下包装脚本示例在子进程中执行另一个 JavaScript 文件,并转发一些命令行参数。这存在问题,因为命令行参数中的特殊字符可能会意外地改变子进程调用的含义。例如,如果某个命令行参数是
,那么子进程将在调用"dollar$separated$name"
之前替换两个环境变量node
和$separated
。$name
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
如果另一个程序使用
来调用上面的包装脚本,并从远程用户接收输入,那么可能会存在命令行注入漏洞。这可能令人惊讶,因为使用child_process.execFile
进行的命令行调用通常被认为是安全的。但在这种情况下,远程用户输入只是简单地转发到包装脚本中存在问题的child_process.execFile
调用。process.exec
为了防范这种情况,请使用不执行环境变量替换的 API,例如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
如果你想让用户为
指定其他选项,可以使用类似node
的库来解析用户输入,将其变成参数数组,而不会冒命令注入的风险shell-quote
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
参考¶
OWASP: 命令注入.
npm: shell-quote.
通用弱点枚举:CWE-78.
通用弱点枚举:CWE-88.