FlawOpen

flawopen.com/Command Injection/exec() 与 execFile()

Node.js 中 exec() 和 execFile() 有什么区别?

参考指南
简要回答

exec() 接收一个字符串并通过 shell 执行,因此管道和通配符可用,但不可信输入中的 shell 元字符也可能改变命令。execFile() 默认不启动 shell,而是直接启动指定程序并传入参数数组。这能避免 shell 解析,但不能消除所有风险:目标程序仍可能把参数解释为选项,或对意外值采取操作。

对比

exec() — 使用 shell
exec(`ls ${dir} | grep .txt`,
  (err, out) => {...})
// 管道可用,但 shell 元字符也会生效。
execFile() — 默认不使用 shell
execFile('ls', [dir],
  (err, out) => {...})
// 不启动 shell;仍要验证 ls 如何解释 dir 参数。
// break out of its own argument

确实需要 shell 功能时

如果命令确实需要管道、通配符展开或重定向,execFile() 无法直接表达这些功能。建议在 Node.js 中自行组合管道,分别启动每个阶段并连接 streams。如果必须使用 exec(),不要把不可信输入放入命令字符串。无 shell 的参数数组也不能防止参数注入:应根据目标程序验证每个值,在该程序支持时使用 --,并只允许预期的命令和选项。shell: true 会在默认不启动 shell 的 API 中启用 shell;此模式下切勿传入未经清理的输入。

常见问题

Windows 批处理文件会改变安全边界

在 Windows 上,不能直接通过 execFile() 启动 .bat 和 .cmd 文件。Node 文档说明,可以通过 exec()、cmd.exe 或启用 shell 的 spawn() 调用它们;这些方式会重新引入命令解释器解析。优先使用原生可执行文件或直接调用 API。如果必须调用批处理文件,请固定脚本路径和参数,或按照该脚本的确切契约验证参数。Node 还将启用 shell: true 时传入参数数组标记为弃用(自 v22.15.0 和 v23.11.0 起)。

spawn() 和 execFile() 一样吗?

spawn() 默认也不启动 shell,并接受参数数组。主要区别在输出处理:spawn() 以 streams 方式输出,而 execFile() 会缓冲输出。两者都不能阻止目标程序把参数解释为选项,因此要按目标程序验证参数。设置 shell: true 会重新启用 shell 解析。

参考资料

另请参阅 操作系统命令注入(CWE-78):原理及在各种语言中的防范 — flawopen.com Java Runtime.exec() 与 Command Injection:区别是什么? Node.js child_process.exec() 命令注入风险