flawopen.com/Command Injection/exec() 与 execFile()
Node.js 中 exec() 和 execFile() 有什么区别?
exec() 接收一个字符串并通过 shell 执行,因此管道和通配符可用,但不可信输入中的 shell 元字符也可能改变命令。execFile() 默认不启动 shell,而是直接启动指定程序并传入参数数组。这能避免 shell 解析,但不能消除所有风险:目标程序仍可能把参数解释为选项,或对意外值采取操作。
对比
exec(`ls ${dir} | grep .txt`,
(err, out) => {...})
// 管道可用,但 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() 默认也不启动 shell,并接受参数数组。主要区别在输出处理:spawn() 以 streams 方式输出,而 execFile() 会缓冲输出。两者都不能阻止目标程序把参数解释为选项,因此要按目标程序验证参数。设置 shell: true 会重新启用 shell 解析。