在没有引号的情况下运行 echo 是否危险?

Vik*_*tor 13 shell security echo quoting

我看过几个类似的话题,但他们指的是不引用变量,我知道这可能会导致不需要的结果。

我看到了这段代码,想知道是否可以在这行代码执行时注入一些要运行的东西:

echo run after_bundle

Sté*_*las 27

只是在@Kusalananda 的好答案之上的额外说明。

echo run after_bundle
Run Code Online (Sandbox Code Playgroud)

很好,因为这 3 个参数¹中的任何字符都没有传递给echo包含 shell 特殊的字符。

并且(我想在这里提出的额外要点)没有系统语言环境可以将这些字节转换为 shell 的特殊字符。

所有这些字符都在POSIX 所谓的可移植字符集中。这些字符应该在 POSIX 系统²上的所有字符集中出现和编码相同。

因此,无论语言环境如何,该命令行都将被解释为相同的。

现在,如果我们开始使用可移植字符集之外的字符,即使它们不是 shell 所特有的,也要引用它们是个好主意,因为在另一个语言环境中,构成它们的字节可能会被解释为不同的字符,这些字符可能会变成特殊的外壳。请注意,无论您使用echo的是命令还是任何其他命令,问题都不echo在于 shell 如何解析其代码。

例如在 UTF-8 中:

echo voilà | iconv -f UTF-8 -t //TRANSLIT
Run Code Online (Sandbox Code Playgroud)

à被编码为0xc3 0XA0。现在,如果您在 shell 脚本中有这行代码,并且 shell 脚本是由使用字符集不是 UTF-8 的语言环境的用户调用的,那么这两个字节可能会产生非常不同的字符。

例如,在fr_FR.ISO8859-15语言环境中,典型的法语语言环境使用涵盖法语的标准单字节字符集(与大多数西欧语言包括英语相同),0xc3 字节被解释为Ã字符,0xa0被解释为非-打破空间字符。

在一些像 NetBSD³ 这样的系统上,不间断空格被认为是一个空白字符(isblank()它返回 true,它被匹配[[:blank:]]),bash因此shell like将其视为语法中的标记分隔符。

这意味着他们不是echo使用$'voil\xc3\xa0'as 参数运行,而是使用as 参数运行它$'voil\xc3',这意味着它不会voilà正确打印。

使用 BIG5、BIG5-HKSCS、GB18030、GBK 等中文字符集会变得更糟,这些字符集的编码包含与|, `,相同的编码\(最坏的情况)(还有那个可笑的 SJIS,又名 Microsoft Kanji,除了它¥不是\,但仍然\被大多数工具视为,因为它在那里被编码为 0x5c )。

例如,如果在zh_CN.gb18030中文语言环境中,您编写如下脚本:

echo ? reboot
Run Code Online (Sandbox Code Playgroud)

该脚本将? reboot在使用 GB18030 或 GBK 的区域设置中输出,? reboot在使用 BIG5 或 BIG5-HKSCS 的区域设置中,但在使用 ASCII 的 C 区域设置或使用 ISO8859-15 或 UTF-8 的区域设置中,将导致reboot运行,因为 GB18030 编码of?是 0xd4 0x7c 并且 0x7c 是|ASCII 中的编码,所以我们最终运行:

 echo ?| reboot
Run Code Online (Sandbox Code Playgroud)

(那个 ? 代表然而 0xd4 字节在语言环境中呈现)。使用危害较小的uname代替 的示例reboot

$ echo $'echo \u8a5c uname' | iconv -t gb18030 > myscript
$ LC_ALL=zh_CN.gb18030 bash ./myscript | sed -n l
\324| uname$
$ LC_ALL=C bash ./myscript | sed -n l
Linux$
Run Code Online (Sandbox Code Playgroud)

uname被运行)。

所以我的建议是引用所有包含可移植字符集之外的字符的字符串。

但是请注意,由于编码\`在一些这些字符的编码被发现,最好不要使用\"..."$'...'(在其内部`和/或\仍是特殊的),但'...'不是到便携式字符集以外的引号字符。

我不知道任何具有区域设置的系统,其中字符集具有任何字符('当然除了它本身),其编码包含 的编码',因此这些'...'绝对应该是最安全的。

请注意,一些 shell 还支持$'\uXXXX'基于字符的 Unicode 代码点表示字符的符号。在像zsh和这样的shell 中bash,字符被插入到语言环境的字符集中编码(但如果该字符集没有该字符,则可能会导致意外行为)。这使您可以避免在 shell 代码中插入非 ASCII 字符。

所以上面:

echo 'voilà' | iconv -f UTF-8 -t //TRANSLIT
echo '? reboot'
Run Code Online (Sandbox Code Playgroud)

或者:

echo $'voil\u00e0'
echo $'\u8a5c reboot'
Run Code Online (Sandbox Code Playgroud)

(需要注意的是,在没有这些字符的语言环境中运行时,它可能会破坏脚本)。

或者更好,因为\它也是特殊的echo(或至少是某些 echo实现,至少是 Unix 兼容的):

printf '%s\n' 'voilà' | iconv -f UTF-8 -t //TRANSLIT
printf '%s\n' '? reboot'
Run Code Online (Sandbox Code Playgroud)

(请注意,\的第一个参数也是特殊的printf,因此最好避免使用非 ASCII 字符,以防它们可能包含 的编码\)。

请注意,您还可以执行以下操作:

'echo' 'voilà' | 'iconv' '-f' 'UTF-8' '-t' '//TRANSLIT'
Run Code Online (Sandbox Code Playgroud)

(这有点矫枉过正,但如果您不确定可移植字符集中哪些字符,可以让您放心)

还要确保永远不要使用古老`...`的命令替换形式(它引入了另一个级别的反斜杠处理),而是使用$(...)


¹技术上,echo作为参数也传递给echo实用程序(告诉它,它是如何调用),它的argv[0]argc是3,虽然在大多数shell时下echo的内置,这样exec()一的/bin/echo有3个参数列表文件由模拟贝壳。将参数列表视为从第二个 ( argv[1]to argv[argc - 1])开始也很常见,因为这是命令主要作用的参数。

² 一个显着的例外ja_JP.SJIS是 FreeBSD 系统的可笑语言环境,其字符集没有\~字符!

³ 请注意,虽然许多系统(FreeBSD、Solaris,但不是 GNU 系统)将 U+00A0 视为[[:blank:]]UTF-8 语言环境中的一个,但很少有人在其他语言环境(例如使用 ISO8859-15 的语言环境中)这样做,这可能是为了避免此类问题。


Kus*_*nda 19

针对具体情况

echo run after_bundle
Run Code Online (Sandbox Code Playgroud)

不需要引用。不需要引用,因为 to 的参数echo是静态字符串,不包含变量扩展或命令替换等。它们“只是两个词”(正如Stéphane 指出的那样,它们是由可移植字符集额外构建的)。

当您处理 shell 可能会扩展或解释的可变数据时,“危险”就会出现。在这种情况下,必须注意 shell 做正确的事情并且结果是预期的。

以下两个问题包含与此相关的信息:


echo有时用于“保护”本站点答案中可能有害的命令。例如,我可能会展示如何使用

echo rm "${name##*/}.txt"
Run Code Online (Sandbox Code Playgroud)

或者

echo mv "$name" "/new_dir/$newname"
Run Code Online (Sandbox Code Playgroud)

这将在终端上输出命令,而不是实际删除或重命名文件。然后用户可以检查命令,确定它们看起来没问题,删除echo并再次运行。

你的命令echo run after_bundle可能是给用户的指令,也可能是一段“注释掉”的代码,在不知道后果的情况下运行起来太危险了。

echo像这样使用,必须知道修改后的命令做了什么,并且必须保证修改后的命令实际上安全的(如果它包含重定向,则可能不会,并且在管道上使用它不起作用,等等)