为什么 SUID 对 shell 脚本禁用,但对二进制文件却没有?

信息安全 linux 特权升级
2021-08-25 20:19:04

虽然我理解 SUID 的想法是让非特权用户以特权用户身份运行程序,但我发现 SUID 通常在没有一些变通方法的情况下无法在 shell 脚本上运行。我的问题是,我不太了解 shell 脚本和二进制程序之间的二分法。似乎无论你可以用 shell 脚本做什么,你也可以用 C 来做,并将它编译成二进制文件。如果 SUID 对于 shell 脚本不安全,那么它对于二进制文件也不安全。那么为什么禁止使用 SUID 的 shell 脚本而不是二进制文件呢?

2个回答

#!shebang ( ) 的典型实现方式固有一个竞争条件:

  1. 内核打开可执行文件,发现它以#!.
  2. 内核关闭可执行文件并打开解释器。
  3. 内核将脚本的路径插入参数列表(as argv[1]),并执行解释器。

如果此实现允许使用 setuid 脚本,攻击者可以通过创建指向现有 setuid 脚本的符号链接、执行它并安排在内核执行步骤 1 和解释器开始之前更改链接来调用任意脚本打开它的第一个论点。出于这个原因,所有现代 unice 在检测到 shebang 时都会忽略 setuid 位。

确保此实现安全的一种方法是让内核锁定脚本文件,直到解释器打开它(注意,这不仅要防止取消链接或覆盖文件,还要防止重命名路径中的任何目录)。但是 unix 系统倾向于回避强制锁,而符号链接会使正确的锁功能变得特别困难和具有侵入性。我认为没有人这样做。

一些 unix 系统使用附加功能实现安全 setuid shebang:路径指的是已经在文件描述符N上打开的文件(因此打开大致相当于)。/dev/fd/N/dev/fd/Ndup(N)

  1. 内核打开可执行文件,发现它以#!. 假设可执行文件的文件描述符是 3。
  2. 内核打开解释器。
  3. 内核插入/dev/fd/3参数列表(as argv[1]),并执行解释器。

包括 Linux 在内的所有现代 unix 变体都实现/dev/fd了,但大多数都不允许使用 setuid 脚本。如果您启用非默认内核设置,OpenBSD、NetBSD 和 Mac OS X 将支持它。在 Linux 上,人们编写了补丁来允许它,但这些补丁从未被合并。Sven Mascheck 的 shebang 页面有很多关于跨 unices 的 shebang 的信息,包括setuid 支持


此外,以提升的权限运行的程序具有固有的风险,这些风险通常在高级编程语言中更难控制,除非解释器是专门为它设计的。原因是编程语言运行时的初始化代码可能会在程序自己的代码有机会清理这些数据之前,根据从低权限调用者继承的数据,以提升的权限执行操作。C 运行时对程序员的作用很小,因此 C 程序有更好的机会在任何不好的事情发生之前控制和清理数据。

假设您已经设法让您的程序以 root 身份运行,要么是因为您的操作系统支持 setuid shebang,要么是因为您使用了本机二进制包装器(例如sudo)。你打开安全漏洞了吗?或许。这里的问题在于解释程序与编译程序。问题是如果以特权执行,您的运行时系统是否安全。

  • 任何动态链接的本机二进制可执行文件都以某种方式由动态加载程序(例如/lib/ld.so)解释,该加载程序加载程序所需的动态库。LD_LIBRARY_PATH在许多 unice 上,您可以通过环境(是环境变量的通用名称)配置动态库的搜索路径,甚至可以将其他库加载到所有已执行的二进制文件中( LD_PRELOAD)。libc.so程序的调用者可以通过放置一个特制的in $LD_LIBRARY_PATH(以及其他策略)在该程序的上下文中执行任意代码。所有健全的系统都会忽略LD_*setuid 可执行文件中的变量。

  • 在 sh、csh 和衍生工具等 shell 中,环境变量自动成为 shell 参数。通过 、 等参数PATHIFS脚本的调用者有很多机会在 shell 脚本的上下文中执行任意代码。如果某些 shell 检测到脚本已被使用特权调用,则它们会将这些变量设置为合理的默认值,但我不知道是否有任何我会信任的特定实现。

  • 大多数运行时环境(无论是本机、字节码还是解释型)都具有类似的功能。很少有人在 setuid 可执行文件中采取特殊的预防措施,尽管运行本机代码的那些通常不做任何比动态链接更有趣的事情(它确实采取了预防措施)。

  • Perl 是一个明显的例外。它以安全的方式明确支持 setuid 脚本。事实上,即使您的操作系统忽略了脚本上的 setuid 位,您的脚本也可以运行 setuid。这是因为 perl 附带了一个 setuid root 帮助程序,它执行必要的检查并以所需权限重新调用所需脚本上的解释器。这在perlsec手册中有解释。过去需要 setuid perl 脚本#!/usr/bin/suidperl -wT而不是#!/usr/bin/perl -wT,但在大多数现代系统上,#!/usr/bin/perl -wT就足够了。

请注意,使用本机二进制包装器本身并不能防止这些问题。事实上,它会使情况变得更糟,因为它可能会阻止您的运行时环境检测到它被特权调用并绕过其运行时可配置性。

如果包装器清理环境,则本机二进制包装器可以使 shell 脚本安全。脚本必须注意不要做出太多假设(例如,关于当前目录),但事实就是如此。您可以为此使用 sudo,前提是它已设置为清理环境。将变量列入黑名单很容易出错,因此请始终将其列入白名单。使用 sudo,确保该env_reset选项已打开,即已setenv关闭,env_file并且env_keep仅包含无害的变量。

所有这些考虑因素同样适用于任何特权提升:setuid、setgid、setcap。

回收自https://unix.stackexchange.com/questions/364/allow-setuid-on-shell-scripts/2910#2910

主要是因为

许多内核都受到竞争条件的影响,这可以让您在新的 exec()ed 进程进入 setuid 时间和命令解释器启动时间之间将 shellscript 交换为您选择的另一个可执行文件。如果你足够持久,理论上你可以让内核运行你想要的任何程序。

以及在该链接上发现的其他原因(但内核竞争条件是最有害的)。当然,脚本的加载方式与二进制程序不同,这就是问题所在。

阅读Dobb 博士 2001 年的这篇文章,您可能会觉得很有趣——它经历了 6 个步骤来编写更安全的 SUID shell 脚本,最终到达第 7 步:

第七课——不要使用 SUID shell 脚本。

即使我们完成了所有工作,也几乎不可能创建安全的 SUID shell 脚本。(在大多数系统上这是不可能的。)由于这些问题,一些系统(例如,Linux)不会在 shell 脚本上支持 SUID。

这段历史讨论了哪些变体对比赛条件进行了修复;该列表比您想象的要大...但是由于其他问题,或者因为与记住您是在安全还是不安全的变体上运行相比,阻止它们更容易,因此仍然不鼓励使用 setuid 脚本。

在过去,这是一个足够大的问题,阅读Perl如何积极地进行补偿是很有启发性的。