it-swarm.cn

在Shell脚本上允许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

但是,这仅适用于可执行文件。 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

维基百科 says

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

假设我愿意接受这些风险,是否有任何方法可以告诉Linux在Shell脚本上将setuid与在可执行文件上一样对待setuid?

如果不是,是否有解决此问题的常用解决方法?我当前的解决方案是添加sudoers项,以允许ALL以我希望其运行的用户身份运行给定脚本,并使用NOPASSWD避免输入密码提示。这样做的主要缺点是,每次我想执行此操作时,都需要sudoers项,而调用方也需要Sudo some-script而不是some-script

195
Michael Mrozek

Linux会忽略所有已解释的可执行文件(即以_#!_行开头的可执行文件)中的setuid¹位。 comp.unix.questions FAQ 解释了setuid Shell脚本的安全性问题。这些问题有两种:与Shebang有关和与Shell有关。我在下面详细介绍。

如果您不关心安全性并希望允许setuid脚本,那么在Linux下,您需要修补内核。从3.x内核开始,我认为您需要在 _install_exec_creds_ 函数中添加对 _load_script_ 的调用,在致电_open_exec_之前,但我尚未测试。


赛义德·谢邦

Shebang(_#!_)的典型实现方式具有一个内部竞争条件:

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

如果此实现允许使用setuid脚本,则攻击者可以通过创建到现有setuid脚本的符号链接,执行该符号链接,然后安排在内核执行步骤1之后且解释器解决之前更改链接来调用任意脚本。打开第一个论点。由于这个原因,大多数女士在检测到Shebang时都会忽略setuid位

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

一些Unix系统(主要是OpenBSD,NetBSD和Mac OS X,所有这些都需要启用内核设置)使用附加的工具实现安全setuid Shebang功能:路径_/dev/fd/N_表示已在文件描述符上打开的文件[〜#〜] n [〜#〜](因此,打开_/dev/fd/N_等效于dup(N))。许多Unix系统(包括Linux)都具有_/dev/fd_,但没有setuid脚本。

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

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


Setuid口译员

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

  • 任何动态链接的本机二进制可执行文件都是由dynamic loader(例如_/lib/ld.so_)解释的,它会加载程序。在许多操作系统上,您可以通过环境配置动态库的搜索路径(_LD_LIBRARY_PATH_是环境变量的通用名称),甚至可以将其他库加载到所有已执行的二进制文件中(_LD_PRELOAD_)。程序的调用者可以通过在_libc.so_中放置特制的_$LD_LIBRARY_PATH_(在其他策略中)来在该程序的上下文中执行任意代码。所有理智的系统都会忽略setuid可执行文件中的_LD_*_变量。

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

  • 大多数运行时环境(无论是本机的,字节码的还是解释的)都具有相似的功能。在setuid可执行文件中,很少有人采取特殊的预防措施,尽管运行本机代码的可执行文件通常比动态链接做的更好(采取预防措施)。

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

请注意,使用本机二进制包装器本身无法采取任何措施来防止这些问题。实际上,它可能导致出现worse的情况,因为它可能会阻止您的运行时环境检测到它是使用特权调用的,并且绕过了其运行时可配置性。

如果包装程序对环境进行了消毒,则本机二进制包装程序可以使Shell脚本安全。该脚本必须注意不要做出太多假设(例如,关于当前目录),但这是可以做到的。您可以为此使用Sudo,前提是它已设置为清理环境。将变量列入黑名单容易出错,因此请始终将其列入白名单。使用Sudo时,请确保_env_reset_选项已打开,setenv已关闭,并且_env_file_和_env_keep_仅包含无害变量。


TL,DR:

  • Setuid Shebang不安全,但通常被忽略。
  • 如果您以特权方式运行程序(通过Sudo或setuid),请编写本机代码或Perl,或者使用可消毒环境的包装程序启动该程序(例如使用_env_reset_选项的Sudo)。

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

213

解决此问题的一种方法是从可以使用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;
 }

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

su - nobody   
[enter password]  
chown nobody:nobody a.out  
chmod 4755 a.out  

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

57
Hemant

因此,我在这条船中添加了一些脚本作为前缀:

#!/bin/sh
[ "root" != "$USER" ] && exec Sudo $0 "[email protected]"

请注意,这不使用setuid,而只是使用Sudo执行当前文件。

24
rcrowley

如果您想避免调用Sudo some_script您可以执行以下操作:

  #!/ust/bin/env sh

  Sudo /usr/local/scripts/your_script

SETUID程序在设计时要特别小心,因为它们具有root特权运行,并且用户对其具有很大的控制权。他们需要检查一切。您无法使用脚本执行此操作,因为:

  • 外壳程序是与用户进行大量交互的大型软件。几乎不可能检查所有内容,尤其是因为大多数代码都不打算在这种模式下运行。
  • 脚本是一种非常快捷的解决方案,通常在编写脚本时不允许他们使用setuid。它们具有许多潜在的危险功能。
  • 他们严重依赖其他程序。仅检查外壳程序是不够的。 sedawk等也需要检查

请注意,Sudo提供了一些健全性检查,但这还不够-检查您自己的代码中的每一行。

最后一点:考虑使用功能。它们使您可以授予以用户身份运行的进程特殊特权,这些特权通常需要root特权。但是,例如,虽然ping需要操纵网络,但它不需要访问文件。但是,我不确定它们是否被继承。

12
Maciej Piechotka

超级[-r reqpath]命令[args]

超级允许指定的用户执行脚本(或其他命令),就像他们是root用户一样;或者它可以在执行命令之前根据每个命令设置uid,gid和/或补充组。它旨在作为使脚本setuid为root的安全替代方法。 Super还允许普通用户提供命令以供其他人执行;它们与提供命令的用户的uid,gid和组一起执行。

Super查询``super.tab''文件以查看是否允许用户执行所请求的命令。如果授予许可,super将执行pgm [args],其中pgm是与此命令相关联的程序。 (默认情况下,允许执行根,但是如果规则不包括根,则仍然可以拒绝根。默认情况下,不允许普通用户执行。)

如果命令是超级程序的符号链接(或硬链接),则键入%command args等同于键入%super command args(该命令不能为super,否则super将无法识别它是通过a调用的。链接。)

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

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

5
Nizam Mohamed

您可以为Sudo +脚本名称创建别名。当然,这还需要做更多的工作,因为您还必须设置一个别名,但这使您不必键入Sudo。

但是,如果您不介意可怕的安全风险,请使用setuid Shell作为Shell脚本的解释器。不知道这是否对您有用,但我想可能会。

不过,我还是建议您不要这样做。我只是出于教育目的而提及它;-)

4
wzzrd

如果由于某种原因Sudo不可用,则可以在C中编写一个精简包装脚本:

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

编译后,用_chmod 4511 wrapper_script_将其设置为setuid

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

请注意,这会完全丢弃环境。如果您想使用一些环境变量而不打开漏洞,则实际上只需要使用Sudo

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

2
Chris