Node中如何为shell命令转义字符串?

56

nodejs 中,唯一执行外部命令的方式是通过 sys.exec(cmd)。我想调用一个外部命令并通过 stdin 给它提供数据。在 nodejs 中似乎没有一种打开命令并将数据推送到它(仅通过 exec 并接收其标准和错误输出),因此目前看来我的唯一选择是使用单个字符串命令,例如:

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

大多数类似这样的问题的答案都集中在正则表达式上,但在使用Google的V8 Javascript引擎的nodejs中不起作用,或者使用其他语言的本地功能,例如Python。

我想转义dangerStr,以便安全地组合像上面那样的exec字符串。 如果有帮助,dangerStr将包含JSON数据。


12
对于 Bourne 类型的 shell,您可以使用以下算法来安全地转义字符串:1)将所有单引号(')替换为四个字符序列单引号、反斜杠、单引号、单引号(''');2)在修改后的字符串开头和结尾添加一个额外的单引号。前导和尾随的单引号并不完全高效地编码,但仍然有效——' 会变成 ''''',即使只是 \ '。 - Chris Johnsen
澄清一下:我花了一点时间才理解@ChrisJohnsen的建议,但它是正确的。如果你想在shell上输出“don't do that”,可以使用echo 'don'\''t do that'命令。 - mikemaccana
在Python中,它是在这里实现的 https://github.com/python/cpython/blob/ad83fde75463dad2df878ff264f52436eb48bc6b/Lib/subprocess.py#L533 - user3064538
好的,虽然时间已经过去了很久,但您也可以使用 child_process 模块... - code
8个回答

40

这是我使用的:

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

1
@DavidTorres abc$abc 变成了 abc\$abc - Konstantin Tarkus
1
如果使用撇号',则无需转义$ - Alex Yaroshevich
4
这似乎是错误的:它将 foo bar 转义为 "foo\ bar",但实际上会被解析为 foo\ bar - Anders Kaseorg
1
如果您将所有内容都用双引号括起来,那么引用空格(\s)是不必要的,因为它会自动保留所有空格。只有在字符串周围没有引号时,才需要这样做,否则空格会被 shell 视为参数分隔符。此外,您没有正确引用空格,因为您将制表符转换为\ ,但正确的形式应该是\t - Mecki
6
这似乎是一种命令注入和奇怪边缘情况问题的邀请。 - Cully
显示剩余3条评论

37

绝不应该依赖于转义未知输入以进入shell参数 - 几乎总会存在一些边缘情况,让用户可以在您的服务器上执行任意代码。

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}`);
});

文档在这里


1
这应该是最受欢迎的答案!任何手动转义都是一个等待被滥用的安全问题。 - eKKiM

24
如果您需要简单(但正确)的解决方案,可以使用以下方法:
function escapeShellArg (arg) {
    return `'${arg.replace(/'/g, `'\\''`)}'`;
}

所以正如Chris Johnsen所提到的那样,您的字符串将简单地用单引号进行转义。
echo 'John'\''s phone';

它在bash中起作用是因为采用了强引用,感觉在fish中也能正常工作,但在zshsh中无法正常工作。
如果你有bash,你可以使用'bash -c \'' + escape('all-the-rest-escaped') + '\''shzsh中运行脚本。
但实际上... 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('; ')]);

祝你有美好的一天!


7
我支持Will的观点,尽可能避免手动转义,而应该使用spawn。
但是,在某些情况下,转义是不可避免的,例如需要使用exec或通过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 的实际值转义,这是安全的,并且具有用户所需的相同值。

1
如果您还需要处理特殊字符(如换行符等),可以按照以下方式操作:
str = JSON.stringify(str)
    .replace(/^"|"$/g,'') //remove JSON-string double quotes
    .replace(/'/g, '\'"\'"\'') //escape single quotes the ugly bash way

这假定您使用Bash的强引用(通过单引号)并且接收者可以理解JSON的类C转义。

0

如果您正在构建自己的软件,可以将命令编码为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参数


0

一个快速的示例使用 await,并具有适当的自动转义(无需手工正则表达式)...

import util from 'util';
import {execFile as execFileWithCallback} from 'child_process';
const execFile = util.promisify(execFileWithCallback)

async function go() {
    const dangerStr1 = `"!£'\"`
    const dangerStr2 = `>> more\n ; | ^Craziness`
    const result = await execFile("echo", [dangerStr1, dangerStr2])
    console.log(result.stdout)
}
go()

/*

Will pump out...

"!£'" >> more
 ; | ^Craziness

*/

请注意,您无法使用此技术进行管道处理。您必须将 result.stdout 放入另一个 execFile() 调用中。

-12

有一种方法可以写入外部命令:process.createChildProcess文档)返回一个带有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是http://nodejs.org/api/child_process.html - grilix
55
关于转义参数的内容,你没有做任何解释。 - Will
3
这并没有回答 OP 所问的内容,即如何转义参数。使用 echo 只是一个例子。OP 不仅仅是要将字符串传递给命令,他们想在命令行上使用任意字符串。 - nonrectangular
4
虽然这个答案没有回答标题中的问题,但是通过完全避免这个问题来解决了这个问题。楼主接受了这个答案,显然有所帮助。 - Matthew Crumley

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接