【问题标题】:should I avoid bash -c, sh -c, and other shells' equivalents in my shell scripts?我应该在我的 shell 脚本中避免使用 bash -c、sh -c 和其他 shell 的等效项吗?
【发布时间】:2015-07-07 11:50:55
【问题描述】:

考虑以下代码:

#!/bin/bash -x
VAR='1 2 3'
bash -c "echo "\$$VAR""
eval "echo "\$$VAR""
bash -c "echo \"\$$VAR\""
eval "echo \"\$$VAR\""

哪些输出:

+ VAR='1 2 3'
+ bash -c 'echo $1' 2 3
3
+ eval 'echo $1' 2 3
++ echo 2 3
2 3
+ bash -c 'echo "$1 2 3"'
 2 3
+ eval 'echo "$1 2 3"'
++ echo ' 2 3'
 2 3

evalbash -c 似乎都以相同的方式解释代码,即 "echo "\$$VAR""'echo $1' 2 3"echo \"\$$VAR\""'echo "$1 2 3"'

我似乎注意到的唯一区别是bash -c 打开了一个子shell,因此结果与eval 不同。例如,在

bash -c 'echo $1' 2 3

2 和 3 是子外壳的位置参数。另一方面,在

eval 'echo $1' 2 3

它们只是echo 的另一个参数。

所以我的问题是,-c 选项(bash -csh -c 或其他 shell 的等效项)是安全使用还是像 eval 那样邪恶?

【问题讨论】:

  • 安全用于什么用途?
  • @Marki555 用于命令执行
  • 我相信它们在传递相同的输入时在功能上实际上是相同的。您需要非常小心地控制/清理可能作为命令运行的任何内容。主要区别在于为bash -c 生成的单独进程与eval 不同,后者的成本可能更高,并且意味着变量不能从命令中“泄漏”回来。
  • 一般来说,很多 bash 在 POSIX sh 上的扩展都是为了能够减少或消除eval 调用——参见赋值端的printf -v${!varname}扩展结束,bash 4.3 名称变量等。在现代 bash 中,eval 仍然有一个合法的用例是非常罕见的。
  • 问题似乎不是关于避免运行bash -c,而是关于运行bash -c从shell脚本。在一般情况下,bash -c 没有任何问题,它传递了一个静态代码字符串,就像在该脚本是静态的、手写的、经过审查的代码时运行 bash somescript 没有任何问题一样;当-c 的参数或脚本是机器生成的,没有特别小心时,任何一个都是危险的。

标签: bash shell sh


【解决方案1】:

是的,您应该避免在 shell 脚本中使用 sh -c 和等效项,就像避免使用 eval 一样。

eval "$foo"

...当人们将其归结为安全风险时,因为它将数据视为代码,从一开始就重新启动解析过程(因此,运行扩展、重定向等)。此外,由于在此过程中考虑了引用上下文,因此正在评估的数据中的内容能够转义引用、终止命令以及以其他方式试图逃避任何安全措施。

sh -c "$foo"

同样——运行扩展、重定向等——仅在全新的 shell 中(不共享非导出变量或其他状态)。

这两者都意味着$(rm -rf /) 等内容很容易被扩展,除非非常小心,而不是确保 - 通常情况下 - 数据只会被视为数据,这是编写安全代码的基础元素。


现在,令人高兴的是,当您使用 bash(或 zsh 或 ksh93)而不是 sh 时,您可以在几乎所有情况下避免使用 eval .

例如:

value=$(eval "echo \$$varname")

...可以替换为:

value=${!varname}

...在 bash 中,

eval "$varname="'$value'

...可以替换为...

printf -v varname %s "$value"

...等等(ksh93 和 zsh 直接等效于后者);涉及关联映射等的更高级的公式最好通过新的 bash 4.3(和 ksh93)namevar 支持来解决。


值得注意的是,bash -c 并没有在上述大多数示例中有效地替换 eval,因为它不在相同的上下文中运行:对 shell 状态所做的更改在以下情况下被丢弃子进程退出;因此,bash -c 不仅不能购买安全性,而且它也不能工作作为 eval 的替代品。

【讨论】:

  • 引用 Etan Reisners 的评论:variables can't "leak" back from the command (in case of bash -c).. 这是否使 bash -c 比 eval 更邪恶,因为两者都可以同等应用?
  • @Jahid,在某种程度上,它只是编写(eval "$foo") 的一种低效方式——它还将状态更改范围限定为子shell,但这样做时没有先删除本地人,也没有使用fork(),但没有exec() 从而减少性能损失。然而,在我能想到的所有eval 的合法使用中,放弃状态更改是不可取的,因为状态更改是使用eval 的全部意义——如果你不需要状态更改,您可以以不同的方式做一些安全风险较小的事情。这就是为什么我谈论的所有新的 shell 特性都是改变 shell 本地状态的方法。
  • @Jahid, ...毕竟,风险来自$(rm -rf .)sh -c 根本没有采取任何措施来防范,而不是var=value,子shell 的退出会丢弃它。
  • @Jahid, ...明显的例外是基于在环境中设置恶意 PATH 或 LD_PRELOAD 的攻击,但攻击者更有可能通过就地编辑点文件来启用这些攻击,这将 - - 再次 - 退出后仍然存在。
【解决方案2】:

据我所知,没有理由在 bash shell 中使用 bash -c。使用它会启动一个新进程,这很昂贵。

您可以使用 eval,它不会启动新进程。如果您想要一个子外壳(例如,为了保留环境),您可以使用括号。

通常,bash -c(或其他带有 -c 的 shell)用于从另一个需要 shell 扩展参数的非 shell 环境(或者可能是由 shell 解释的 DSL)执行命令。例如,在过去,您可能会在 C 程序中将它与 execvp 一起使用。如今,在大多数环境中,通常都有使用 shell 运行命令的方法。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2015-07-22
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2015-05-05
    • 1970-01-01
    相关资源
    最近更新 更多