允许在 shell 脚本上使用 setuid

Mic*_*zek 223 shell security scripting setuid

setuid权限位告诉Linux与业主,而不是执行者的有效用户ID运行程序:

> cat setuid-test.c

#include <stdio.h>
#include <unistd.h>

int main(int argc, char** argv) {
    printf("%d", geteuid());
    return 0;
}

> gcc -o setuid-test setuid-test.c
> ./setuid-test

1000

> sudo chown nobody ./setuid-test; sudo chmod +s ./setuid-test
> ./setuid-test

65534
Run Code Online (Sandbox Code Playgroud)

但是,这仅适用于可执行文件;shell 脚本忽略 setuid 位:

> cat setuid-test2

#!/bin/bash
id -u

> ./setuid-test2

1000

> sudo chown nobody ./setuid-test2; sudo chmod +s ./setuid-test2
> ./setuid-test2

1000
Run Code Online (Sandbox Code Playgroud)

维基百科

由于安全漏洞的可能性增加,许多操作系统在应用于可执行 shell 脚本时会忽略 setuid 属性。

假设我愿意接受这些风险,有没有办法告诉 Linux 将 shell 脚本上的 setuid 位与可执行文件上的相同?

如果没有,是否有针对此问题的通用解决方法?我当前的解决方案是添加一个sudoers条目以允许ALL以我希望它运行的用户身份运行给定的脚本,NOPASSWD以避免密码提示。这样做的主要缺点是sudoers每次我想这样做时都需要一个条目,并且需要调用者sudo some-script而不是仅仅some-script

Gil*_*il' 242

Linux 忽略所有解释的可执行文件(即以#!一行开头的可执行文件)上的 setuid¹ 位。该comp.unix.questions FAQ解释了setuid的shell脚本的安全问题。这些问题有两种:shebang相关的和shell相关的;我在下面详细介绍。

如果您不关心安全性并希望允许 setuid 脚本,则在 Linux 下,您需要修补内核。从 3.x 内核开始,我认为您需要在调用之前install_exec_credsload_script函数中添加对 的调用open_exec,但我还没有测试过。


赛邦

#!通常实现shebang ( )的方式存在固有的竞争条件:

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

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

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

一些 unix 系统(主要是 OpenBSD、NetBSD 和 Mac OS X,所有这些系统都需要启用内核设置)使用附加功能实现安全 setuid shebang:路径是指已经在文件描述符N上打开的文件(所以打开是大致相当于)。许多 Unix 系统(包括 Linux)有但没有 setuid 脚本。/dev/fd/N/dev/fd/Ndup(N)/dev/fd

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

Sven Mascheck 的 shebang 页面有很多关于unices 的 shebang的信息,包括setuid 支持


Setuid 解释器

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

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

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

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

  • Perl是一个明显的例外。它以安全的方式明确支持 setuid 脚本。事实上,即使您的操作系统忽略了脚本上的 setuid 位,您的脚本也可以运行 setuid。这是因为 perl 附带了一个 setuid root helper,它执行必要的检查并以所需的权限在所需的脚本上重新调用解释器。这在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仅包含无害变量。


TL,博士:

  • Setuid shebang 是不安全的,但通常被忽略。
  • 如果您使用特权运行程序(通过 sudo 或 setuid),请编写本机代码或 perl,或者使用清理环境的包装器启动程序(例如带有env_reset选项的sudo )。

¹如果您将“setgid”替换为“setuid”,则此讨论同样适用;它们都被 Linux 内核在脚本中忽略

  • 实际上,从 perl 5.11(5.12 稳定版)开始,`suidperl` 已被删除:perl5110delta: &gt; "suidperl" 已被删除。它曾经提供一种机制来在不正确支持它的系统上模拟 setuid 权限位。perl5120delta: &gt; "suidperl" 不再是 Perl 的一部分。它曾经提供一种机制来在不正确支持它的系统上模拟 setuid 权限位。 (8认同)
  • 还要注意 perl 5.6.1 文档中的这一行(大约十年前)... perl561delta:&gt; 请注意,在任何最新版本的 perl 中,suidperl 既没有默认构建也没有安装。**强烈建议不要使用 suidperl**。如果您认为需要它,请先尝试诸如 sudo 之类的替代方法。请参阅 http://www.courtesan.com/sudo/。 (5认同)
  • 显然,`suidperl` 的东西已经被弃用并标记为删除多年(但仍然存在) (4认同)
  • @Josh:安全 setuid shell 脚本是可能的,但前提是 shell 实现者和脚本编写者都非常小心。我推荐 Perl,而不是本机代码,其中实现者已经注意到 setuid 脚本应该是安全的,脚本编写者只需很少的努力。 (3认同)
  • 我不明白:这似乎是对问题原因的解释,但是这里有对 OP 问题的实际答案吗?有没有办法告诉我的操作系统运行 setuid shell 脚本? (3认同)

Hem*_*ant 66

解决此问题的一种方法是从可以使用 setuid 位的程序调用 shell 脚本。
它类似于 sudo。例如,以下是在 C 程序中完成此操作的方法:

#include <stdio.h>
#include <stdlib.h>
#include <sys/types.h>
#include <unistd.h>

int main()
{
    setuid( 0 );   // you can set it at run time also
    system( "/home/pubuntu/setuid-test2.sh" );
    return 0;
 }
Run Code Online (Sandbox Code Playgroud)

将其另存为 setuid-test2.c。
compile
现在在这个程序二进制文件上执行 setuid:

su - nobody   
[enter password]  
chown nobody:nobody a.out  
chmod 4755 a.out  
Run Code Online (Sandbox Code Playgroud)

现在,您应该能够运行它,并且您将看到您的脚本在没有任何人权限的情况下执行。
但在这里,您也需要对脚本路径进行硬编码,或者将其作为命令行 arg 传递给上面的 exe。

  • **请注意,即使脚本的完整路径是硬编码的,这也是不安全的。** shell 将从环境中继承变量,其中许多变量允许调用者注入任意代码。`PATH` 和 `LD_LIBRARY_PATH` 是明显的向量。一些 shell 甚至在它们开始正确执行脚本之前就执行了 `$ENV` 或 `$BASHENV` 或 `~/.zshenv`,所以你根本无法在脚本中保护它们。使用特权调用 shell 脚本的唯一安全方法是**清理环境**。Sudo 知道如何安全地做到这一点。所以**不要编写自己的包装器,使用sudo**。 (49认同)
  • 我很遗憾他突然因此被否决了——我确实特别说过我也想听到不安全的版本,而且我在想象一个可执行文件,当我说它时,它接受了一个 shell 脚本参数。显然它非常不安全,但我想知道存在哪些可能性 (35认同)
  • 我不建议允许将脚本作为命令行参数传递的建议,因为这实质上使任何可以执行该程序的人都能够以该定义的用户身份运行任何脚本。 (28认同)
  • @Gilles:仅供参考,Linux 在遇到 setuid 位时会取消设置 `LD_LIBRARY_PATH` 等。 (9认同)
  • 与使用 `system` 不同,您可能会发现使用 `exec` 系列之一更简单(也更有效)——最有可能是 `execve`。这样,您无需创建新进程或启动 shell,并且可以转发参数(假设您的特权脚本可以安全地处理参数)。 (3认同)
  • 我很惊讶为什么没有人问如果我们写一个 C 程序并设置它的 setuid 位,这个 C 程序将计算脚本的校验和(例如 sha1)和文件大小,如果这些结果与硬编码在同一个 C 程序中。 (3认同)
  • 如果您实现该 C 解决方案,我会添加一些完整性检查:确保正在运行的脚本归正确的用户+组所有,不可全局写入,并且其路径中的目录不是全局或组可写的(基本上与 suPHP 执行的检查相同) - 这将减少您的工具成为安全问题的可能性,因为其他用户能够以某种方式编辑或替换脚本。 (2认同)
  • 很好的建议!如果我们想以危险的方式使用叉子,我们不需要从我们这里拿走叉子......;) (2认同)

rcr*_*ley 26

因此,我在这艘船上添加了一些脚本的前缀:

#!/bin/sh
[ "root" != "$USER" ] && exec sudo $0 "$@"
Run Code Online (Sandbox Code Playgroud)

请注意,这不使用,setuid而只是使用sudo.

  • 这不使用 setuid 而只是给你一个 `sudo` 提示。(对我来说,setuid 的全部意义在于允许事物以 root 身份运行而无需 sudo。) (6认同)
  • @Luc如果您在 sudoers 文件中使用“NOPASSWD”标签,那么这不会给您“sudo”提示,正如OP所说的那样。我也使用这种方法,它还有 sudoers 提供的额外环境卫生。 (3认同)

Mac*_*tka 13

如果你想避免打电话,sudo some_script你可以这样做:

  #!/usr/bin/env sh
  
  sudo /usr/local/scripts/your_script
Run Code Online (Sandbox Code Playgroud)

SETUID 程序需要非常小心地设计,因为它们以 root 权限运行并且用户对它们有很大的控制权。他们需要对一切进行健全性检查。你不能用脚本来做,因为:

  • Shell 是与用户进行大量交互的大型软件。几乎不可能对所有内容进行完整性检查——尤其是因为大多数代码并不打算在这种模式下运行。
  • 脚本通常是一种快速的“不肮脏”的解决方案,并且通常不会小心翼翼地准备好以允许 setuid。它们具有许多潜在的危险特性。
  • 他们严重依赖其他程序。仅检查外壳是不够的。sed,awk等也需要检查

请注意,它sudo提供了一些完整性检查,但这还不够——检查您自己代码中的每一行。

最后一点:考虑使用功能。它们允许您为以用户身份运行的进程授予通常需要 root 权限的特殊权限。但是,例如,虽然ping需要操纵网络,但不需要访问文件。可以选择性地将功能继承到子流程。

  • 我认为`/ust/bin/env` 应该是`/usr/bin/env`。 (4认同)

Niz*_*med 7

super [ -r reqpath] 命令 [ args ]

Super 允许指定用户像 root 一样执行脚本(或其他命令);或者它可以在执行命令之前基于每个命令设置 uid、gid 和/或补充组。它旨在成为制作脚本 setuid root 的安全替代方案。Super 还允许普通用户提供命令供他人执行;这些使用提供命令的用户的 uid、gid 和组来执行。

Super 查阅“super.tab”文件以查看是否允许用户执行请求的命令。如果授予权限,super 将执行 pgm [ args ],其中 pgm 是与此命令关联的程序。(默认情况下允许root执行,但如果规则排除root,仍然可以拒绝。默认情况下不允许普通用户执行。)

如果 command 是指向 super 程序的符号链接(或硬链接),则键入 % command args 等效于键入 % super command args(该命令不能是 super,否则 super 将无法识别它是通过关联。)

http://www.ucolick.org/~will/RUE/super/README

http://manpages.ubuntu.com/manpages/utopic/en/man1/super.1.html


Chr*_*ris 6

如果由于某种原因sudo不可用,您可以用 C 编写一个瘦包装器脚本:

#include <unistd.h>
int main() {
    setuid(0);
    execle("/bin/bash","bash","/full/path/to/script",(char*) NULL,(char*) NULL);
}
Run Code Online (Sandbox Code Playgroud)

一旦你编译它设置为setuidchmod 4511 wrapper_script

这类似于另一个发布的答案,但在干净的环境中运行脚本并显式使用/bin/bash而不是调用的 shell system(),因此关闭了一些潜在的安全漏洞。

请注意,这完全丢弃了环境。如果你想在不打开漏洞的情况下使用一些环境变量,你真的只需要使用sudo.

显然,您要确保脚本本身只能由 root 用户写入。


小智 5

您可以使用较新版本的 shc 并设置参数(启用 SETUID)来编译脚本-S

shc -S -f your-script.sh
chmod u+s your-script.sh.x
Run Code Online (Sandbox Code Playgroud)

your-script.sh.x将是普通用户可以使用的可执行文件。