【问题标题】:How do I escape a string for a shell command in node?如何在节点中转义 shell 命令的字符串?
【发布时间】:2010-12-19 06:54:44
【问题描述】:

nodejs 中,执行外部命令的唯一方法是通过 sys.exec(cmd)。我想调用一个外部命令并通过标准输入给它数据。在nodejs中,似乎还没有一种方法可以打开命令然后将数据推送到它(仅用于执行并接收其标准+错误输出),所以我现在必须这样做的唯一方法是通过单个字符串命令,例如:

var dangerStr = "bad stuff here";
sys.exec("echo '" + dangerStr + "' | somecommand");

此类问题的大多数答案都集中在 nodejs(它使用 Google 的 V8 Javascript 引擎)中对我不起作用的正则表达式或 Python 等其他语言的本机功能。

我想逃避 dangerStr 以便可以安全地编写像上面那样的 exec 字符串。如果有帮助,dangerStr 将包含 JSON 数据。

【问题讨论】:

  • 对于 Bourne 类型的 shell,您可以使用以下算法安全地转义字符串:1) 将所有出现的单引号 (') 替换为四个字符序列单引号、反斜杠、单引号、单引号('\'') 2) 在修改后的字符串的开头和结尾添加一个额外的单引号。前导单引号和尾随单引号的编码效率不高,但它仍然有效——当它可能只是 \' 时,' 变成了 ''\'''。
  • 为了澄清:我花了一点时间来理解@ChrisJohnsen 的建议,但它检查出来了。如果您想在 shell 上使用don't do that,请使用echo 'don'\''t do that' 生成don't do that
  • 在python中,这里实现github.com/python/cpython/blob/…
  • 嗯,这是很久以后的事了,不过你也可以使用child_process模块...

标签: javascript shell escaping node.js v8


【解决方案1】:

这是我用的:

var escapeShell = function(cmd) {
  return '"'+cmd.replace(/(["'$`\\])/g,'\\$1')+'"';
};

【讨论】:

  • @DavidTorres abc$abc 变为 abc\$abc
  • 如果使用撇号',则无需转义$
  • 这似乎是错误的:它将foo bar 转义为"foo\ bar",将被解析为foo\ bar
  • 如果将所有内容都用双引号括起来,则不需要引用空格 (\s),因为这会自动保留所有空格。如果字符串周围没有引号,您只需要这样做,否则空格会被 shell 视为参数分隔符。此外,当您将制表符转换为 \ 时,您也没有正确引用空格,但正确的形式是 \t
  • 这似乎是命令注入和奇怪的边缘案例问题的邀请。
【解决方案2】:

如果您需要简单(但正确)的解决方案,您可以使用:

function escapeShellArg (arg) {
    return `'${arg.replace(/'/g, `'\\''`)}'`;
}

因此,正如 Chris Johnsen 所提到的,您的字符串将简单地用单引号转义。

echo 'John'\''s phone';

由于strong quoting,它在bash 中有效,感觉在fish 中也有效,但在zshsh 中无效。

如果您有bash,您可以在shzsh'bash -c \'' + escape('all-the-rest-escaped') + '\'' 中运行您的脚本。

但实际上... node.js 会为您转义所有需要的字符:

var child = require('child_process')
  .spawn('echo', ['`echo 1`;"echo $SSH_TTY;\'\\0{0..5}']);

child.stdout.on('data', function (data) {
  console.log('stdout: ' + data);
});

child.stderr.on('data', function (data) {
  console.log('stderr: ' + data);
});

这段代码将执行:

echo '`echo 1`;"echo $SSH_TTY;'\''\\0{0..5}'

会输出:

stdout: `echo 1`;"echo $SSH_TTY;\'\\0{0..5}

或一些错误。

看看http://nodejs.org/api/child_process.html#child_process_child_process_spawn_command_args_options

顺便说一句,运行一堆命令的简单解决方案是:

require('child_process')
  .spawn('sh', ['-c', [
    'cd all/your/commands',
    'ls here',
    'echo "and even" > more'
  ].join('; ')]);

祝你有美好的一天!

【讨论】:

    【解决方案3】:

    should never rely on escaping unknown input going to a shell parameter - 几乎总会有一些你没有想到的极端情况允许用户在你的服务器上执行任意代码。

    Node 支持调用命令并分别传递每个参数,无需转义。这是最安全的方法:

    const { spawn } = require('child_process');
    // Note that the arguments are in an array, not using string interpolation
    const ls = spawn('ls', ['-lh', '/usr']);
    
    ls.stdout.on('data', (data) => {
      console.log(`stdout: ${data}`);
    });
    
    ls.stderr.on('data', (data) => {
      console.log(`stderr: ${data}`);
    });
    
    ls.on('close', (code) => {
      console.log(`child process exited with code ${code}`);
    });
    

    文档是here

    【讨论】:

      【解决方案4】:

      我赞同Will的意见,尽可能避免用手逃跑,更喜欢spawn。

      但是,在无法避免转义的情况下,例如如果您需要使用 exec 或者您是 executing a command through ssh。然后你可以使用base64将安全字符传递给bash,并依靠bash来逃避未知。

      const dangerStr = 'bad stuff here'
      // base64 has safe characters [A-Za-z=0-9+/]
      const dangerBase64 = btoa(dangerStr)
      
      sys.exec(`echo "$(echo ${dangerBase64} | base64 -d)" | somecommand`)
      

      解释如下:

      dangerBase64 未知,但它不包含bash 中的不安全字符。因此echo ${dangerBase64} 会输出我们想要的。

      最后,$(echo ${dangerBase64} | base64 -d) 周围的双引号转义了用户在 bash 中传递的实际值,这是安全的,并且与用户想要的值相同。

      【讨论】:

        【解决方案5】:

        如果您还需要处理特殊字符(换行符等),您可以这样做:

        str = JSON.stringify(str)
            .replace(/^"|"$/g,'') //remove JSON-string double quotes
            .replace(/'/g, '\'"\'"\'') //escape single quotes the ugly bash way
        

        这假设您通过单引号使用 Bash 的 strong-quoting)并且接收者可以理解 JSON 的类似 C 的转义。

        【讨论】:

          【解决方案6】:

          如果您正在构建自己的软件,您可以将命令编码为 base64 或十六进制格式,然后从程序中解码参数。

          对于我使用的 Nodejs 应用程序。

          var base64_encode = exports.base64_encode = function(non_base64_string){
              return Buffer.from(non_base64_string).toString('base64');
          }
          
          
          var base64_decode = exports.base64_decode = function(base64_string){
              return Buffer.from(base64_string, 'base64').toString('ascii')
          }
          

          所以当我运行这样的 base64 编码命令时

          webman grep --search "aW5jbHVkZV9vbmNlICRfU0VSVkVSWyJET0NVTUVOVF9ST09UIl0uIi9zZXR0aW5ncy5waHAiOw==" --replacement "JGRvY3VtZW50X3Jvb3QgPSBfX0RJUl9fO3doaWxlKHRydWUpe2lmIChmaWxlX2V4aXN0cygkZG9jdW1lbnRfcm9vdC4iL3NldHRpbmdzLmpzb24iKSl7YnJlYWs7fWVsc2V7JGRvY3VtZW50X3Jvb3Q9ZGlybmFtZSgkZG9jdW1lbnRfcm9vdCk7fX08bmV3bGluZT5pbmNsdWRlX29uY2UgJGRvY3VtZW50X3Jvb3QuIi9zZXR0aW5ncy5waHAiOw=="
          

          我可以使用base64_decode 毫无压力地获取参数searchreplacement 参数

          【讨论】:

            【解决方案7】:

            有一种写入外部命令的方法:process.createChildProcess (documentation) 使用write 方法返回一个对象。 createChildProcess 不太方便,因为它不缓冲 stdout 和 stderr,所以你需要事件处理程序来读取块中的输出。

            var stdout = "", stderr = "";
            var child = process.createChildProcess("someCommand");
            
            child.addListener("output", function (data) {
                if (data !== null) {
                    stdout += data;
                }
            });
            child.addListener("error", function (data) {
                if (data !== null) {
                    stderr += data;
                }
            });
            child.addListener("exit", function (code) {
                if (code === 0) {
                    sys.puts(stdout);
                }
                else {
                    // error
                }
            });
            
            child.write("This goes to someCommand's stdin.");
            

            【讨论】:

            • 有趣.. 谢谢!顺便说一句,新的 URI 是 nodejs.org/api/child_process.html
            • 你没有解释任何关于转义参数的事情。
            • 这并不能回答 OP 所询问的内容,即转义参数。 echo 的使用只是一个例子。 OP 不只是试图将字符串传递给命令。他们试图在命令行中使用任意字符串。
            • 它没有回答标题中的问题,但这通过完全避免问题来解决问题。 OP 接受了答案,所以显然它有所帮助。
            猜你喜欢
            • 2013-08-09
            • 1970-01-01
            • 1970-01-01
            • 2011-02-20
            • 2012-01-31
            • 1970-01-01
            • 2012-04-16
            • 2012-04-19
            • 2013-06-10
            相关资源
            最近更新 更多