【问题标题】:Race conditions in shell command listsshell 命令列表中的竞争条件
【发布时间】:2012-03-21 21:36:42
【问题描述】:

shell(或 bash)中的命令列表在竞争条件下的安全性如何?

if [ -h "$dir" ]; then
  echo 'Directory exists and is a symlink'
  exit 1
fi
cd "$dir"

上面的代码显然容易出现竞争条件:攻击者可以在检查之后创建符号链接,但仍然是在将目录更改为之前。

这同样适用于|| 命令列表吗?换句话说:下面的命令是否不受竞争条件的影响,或者上面的规则是否仍然适用?

[ -h "$dir" ] || cd "$dir"

带有错误信息:

[ -h "$dir" ]
  && { echo 'Directory exists and is a symlink'; exit 1; }
  || cd "$dir"

【问题讨论】:

  • 您是否担心攻击者可能用符号链接替换现有目录,或者他/她可能创建符号链接,或两者兼而有之?
  • 来自有关 -h 的测试手册页:保留此运算符是为了与该程序的先前版本兼容。不要依赖它的存在;请改用 -L。
  • @WilliamPursell:有趣……我的 sh(1) 手册页正好相反:»-L file 如果文件存在并且是符号链接,则为真。保留此运算符是为了与此程序的先前版本兼容。不要依赖它的存在;改用 -h。« man bash(1) 没有提到两者之间的任何区别。
  • @AdamLiss:两者都有。他可能会做任何事情来欺骗我进入错误的目录。创建一个单一的如果应该就足够了,但是当他在检查之后但在更改到目录之前用符号链接替换现有目录时,我可能会在其他地方结束。
  • @knittl pubs.opengroup.org/onlinepubs/009695399 开放组规范似乎没有表明 -h 和 -L 之间有任何区别。

标签: security bash shell symlink race-condition


【解决方案1】:

只有两种安全且相对便携的方式来更改目录而无需遵循符号链接。在 shell 脚本中两者都不容易。

为了便于讨论,假设我们正在尝试安全地 chdir 进入“foo”。第一种方法是将当前目录保存在带有open(".", O_RDONLY)lstat() 的“foo”目录的打开文件描述符中,记录得到的st_devst_ino 值,调用 chdir("foo") 然后统计()“。”。比较得到的 st_devst_ino 值。如果它们相同,则您赢得了比赛。如果没有,请发出错误消息,fchdir() 使用您保存的 fd 返回,然后中止或重试。

第二种不太便携的方法是使用fd = open("foo", O_RDONLY|O_NOFOLLOW),然后使用fchdir(fd)。您也可以在此处使用openat 而不是open。可移植性问题是并非所有系统都有O_NOFOLLOW,并且一些较旧的内核不会正确解释该标志(相反,它们会忽略它,这是一个安全问题)。

有关更多信息,请查看 GNU find 的源代码,其中我使用与上述方法非常相似的方法来避免此类问题。

至于用shell脚本解决这个问题:

如果您的系统有stat(1) 命令或类似 Perl 的命令,您可以使用它们来执行统计操作;您可以将结果记录在 shell 变量中。这意味着您可以或多或少地在 shell 脚本中实现第一种方法,除了需要使用fchdir 来恢复。如果在你的 shell 脚本失败后立即中止是可以的,你当然可以调整第一种方法以在 shell 中使用。但最终在 shell 中编写安全代码是非常非常困难的。

【讨论】:

【解决方案2】:

这是一个很好的教育问题,但在实践中你解决了错误的问题。 linux 安全模型无需担心脚本并发问题,而是依赖于文件系统权限。

如果您担心攻击者可能会更改正在执行的程序下的工作环境,那么您应该保护环境:确保只有您对将要运行的脚本和二进制文件具有写入权限,并创建文件和目录仅在只有您可以写入的位置。

对于二进制可执行文件,请考虑使用setuid 位,它允许普通用户以提升的权限执行它们,但请注意,以这种方式运行的任何进程都必须仔细调试并检查安全问题。但是,大多数 linux 发行版都不允许脚本以这种方式运行。

值得一提的是,获得您(或 root)密码的攻击者可以轻松击败此安全措施和其他安全措施,但在这种情况下,您会遇到更大的问题。 :-)

祝你好运!

【讨论】:

  • 感谢您的回答。有问题的脚本是/将作为 setuid 运行,并作为第一个参数传递一个目录,它将 cd 进入该目录,然后执行许多 chmod+chown 任务。 (而且我不希望有人对他不允许执行的文件执行该任务)
  • 你最好使用不同的编程语言,可能。
  • 如果 chown 命令有什么特别之处,也许所有的更改都是在有限数量的 Unix 用户之间进行的,并且是由有限的(可能是不相交的)用户发布的,那么你可以避免有一个 setuid shell 脚本,而是在脚本中将 chown 更改为 sudo chown
  • @JamesYoungman:我看不出 setuid 和通过 sudo 运行脚本之间有什么区别。最后,它以root身份执行。
  • @knittl,哎呀! You should never make a shell script setuid。那是a major security hole:有no reasonable way to make it secure。您需要使用更适合此的语言:可能是 C。
【解决方案3】:

我看不出为什么使用|| 不会受到相同竞争条件的影响。确实,为了使其不易受到攻击,它需要使用任何一个来实现

  • 某种原子的 stat-and-chdir 文件系统操作(我很确定它不存在),或者

  • cd 之后进行某种双重检查,以确保目录仍然指向它以前的位置。

我不是 bash 大师,但我很确定这两种情况都不是。

【讨论】:

  • 双重检查听起来是个好主意,也许是唯一安全的方法。
  • 我怀疑这也是要走的路。但是请注意,仅检查原始目录路径是否仍解析到同一个位置是不够的:有人可能已将其更改 两次。相反,您需要 stat 您实际更改的目录,并检查它是否与您最初 stated 的目录相同。
  • 感谢您确保我以正确的方式进行操作。我已经在比较 inode:stat -c%i dir && cd dir && stat -c%i '.' && compare both inodes。我什至不知道如何在不引入另一个竞争条件的情况下检查路径是否解析为同一目录;)
  • @D.W.:我只做一次检查。
【解决方案4】:

一般来说,您无法避免 shell 脚本中的竞争条件。您需要使用 C,并使用 TOCTTOU 安全接口,如 openatfchdir 等。避免 TOCTTOU(种族)漏洞并非易事,需要仔细研究。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2022-01-23
    • 2014-06-10
    • 1970-01-01
    • 2019-07-04
    • 2018-09-19
    • 2014-01-10
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多