CodeQL 文档

间接不受控的命令行

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

点击查看 CodeQL 仓库中的查询

将命令行参数转发到child_process.exec 或其他在 shell 中执行系统命令的库例程可能会由于未转义的特殊字符而意外地改变命令的含义。

当转发来的命令行参数来自未转义参数中的特殊字符的父进程时,由于特殊字符意外地被求值,父进程可能会间接地容易受到命令行注入攻击。

建议

如果可能,使用不运行 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

参考

  • ©GitHub, Inc.
  • 条款
  • 隐私