在shell脚本上允许setuid


184

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

维基百科

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

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

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

Answers:


202

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

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


塞杜伊·谢邦

#!通常,shebang()的实现方式固有一种竞争条件:

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

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

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

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

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

Sven Mascheck的shebang页面包含有关 unices的shebang的大量信息,包括 setuid支持


Setuid口译员

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

  • 任何动态链接的本机二进制可执行文件都由动态加载程序(例如/lib/ld.so)解释,该动态加载程序将加载程序所需的动态库。在许多操作系统上,您可以通过环境配置动态库的搜索路径(这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根辅助程序,该辅助程序执行必要的检查并以所需的特权在所需的脚本上重新调用解释器。在perlsec手册中对此进行了说明。过去,用setuid perl脚本#!/usr/bin/suidperl -wT代替#!/usr/bin/perl -wT,但在大多数现代系统上#!/usr/bin/perl -wT就足够了。

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

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


TL,DR:

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

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


2
@Josh:可以使用安全的setuid Shell脚本,但前提是Shell实施者和脚本编写者都非常小心。我建议使用Perl而不是本机代码,在Perl中,实现者会注意setuid脚本应该是安全的,而脚本编写者则应毫不费力。
吉尔斯(Gilles)2010年

3
显然,suidperl这些东西已被弃用并标记为可以移除多年(但
仍会

7
实际上suidperl已从perl 5.11(稳定于5.12)中删除:perl5110delta:>“ suidperl”已被删除。它用来提供一种机制,在不正确支持setuid权限的系统上模拟setuid权限。perl5120delta:>“ suidperl”不再是Perl的一部分。它用来提供一种机制,在不正确支持setuid权限的系统上模拟setuid权限。
兰迪·史陶纳

5
另请注意,来自perl 5.6.1 docs(大约十年前)的这一行... perl561delta:>请注意,在任何最新版本的perl中,suidperl都不是默认生成或安装的。 强烈建议不要使用suidperl。如果您认为需要,请先尝试使用sudo之类的方法。参见courtesan.com/sudo
兰迪·斯汤纳

3
我不明白:这似乎是造成问题的原因的解释,但是这里对OP的问题有实际答案吗?有没有办法告诉我的操作系统运行setuid shell脚本?
汤姆(Tom)

55

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


25
我建议不要允许将脚本作为命令行参数进行传递,因为从本质上讲,任何可以执行该程序的人都可以以该定义的用户身份运行任何脚本。
dsp 2010年

39
请注意,即使脚本的完整路径是硬编码的,这也不安全。Shell将从环境中继承变量,并且其中许多变量允许调用者注入任意代码。PATH并且LD_LIBRARY_PATH是明显的向量。有些shell执行$ENV$BASHENV~/.zshenv他们开始执行脚本正确甚至之前,所以你不能从这些根本防止脚本中。调用具有特权的Shell脚本的唯一安全方法是清理环境。Sudo知道如何安全地进行操作。所以不要写自己的包装器,使用sudo
吉尔斯(Gilles)2010年

28
我感到很难过,因为他突然为此感到不满意–我确实明确地说过我也想听到不安全的版本,并且我在想象一个可执行程序,当我说它时会使用shell脚本作为参数。显然,这是完全不安全的,但我想知道存在什么可能性
迈克尔·莫罗泽克

8
@Gilles:仅供参考,Linux LD_LIBRARY_PATH在遇到setuid位时会取消设置。
grawity 2012年

3
除了使用之外system,您可能会发现更容易(更有效)地使用exec家族中的一种execve。这样,您无需创建新进程或启动Shell,就可以转发参数(假设特权脚本可以安全地处理参数)。
Toby Speight 2015年

23

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

#!/bin/sh
[ "root" != "$USER" ] && exec sudo $0 "$@"

请注意,这不会使用,setuid而只是使用来执行当前文件sudo


4
这不使用setuid,而只是sudo提示您。(对我来说,setuid的全部要点是允许事物以root身份运行而无需sudo。)
Luc

12

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

  #!/ust/bin/env sh

  sudo /usr/local/scripts/your_script

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

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

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

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


2
我想/ust/bin/env应该是/usr/bin/env
鲍勃”

4

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

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

让我指出,尽管如此,我还是建议不要这样做。我只是出于教育目的提及它;-)


5
它将起作用。正如您所说的那样可怕。SETUID位允许使用所有者权限执行。Setuid外壳程序(除非它旨在与setuid一起使用)将以root用户身份运行。即任何人都可以运行rm -rf /(以及系列“ 请勿在家中”的其他命令)。
Maciej Piechotka 2010年

9
@MaciejPiechotka by 不要在家里做你的意思是在工作中随意做吗?:)
彼得

4

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

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

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

如果command是到超级程序的符号链接(或硬链接),则键入%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


嗨,欢迎来到该网站!我们希望答案在这里更详细。您能否编辑答案并解释该程序是什么,以及它如何帮助解决OP的问题?
terdon

谢谢Nizam,花了几个小时尝试寻找类似于super之类的东西,以允许其他用户作为文件用户执行帐户。
乍得

2

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

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

编译后,将其设置为setuidchmod 4511 wrapper_script

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

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

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


-3

我首先发现了这个问题,没有所有答案都令人信服,这是一个更好的问题,如果您愿意,还可以完全混淆bash脚本!

它应该是不言自明的。

#include <string>
#include <unistd.h>

template <typename T, typename U>
T &replace (
          T &str, 
    const U &from, 
    const U &to)
{
    size_t pos;
    size_t offset = 0;
    const size_t increment = to.size();

    while ((pos = str.find(from, offset)) != T::npos)
    {
        str.replace(pos, from.size(), to);
        offset = pos + increment;
    }

    return str;
}

int main(int argc, char* argv[])
{
    // Set UUID to root
    setuid(0);

    std::string script = 
R"(#!/bin/bash
whoami
echo $1
)";

    // Escape single quotes.
    replace(script, std::string("'"), std::string("'\"'\"'"));

    std::string command;
    command = command + "bash -c '" + script + "'"; 

    // Append the command line arguments.
    for (int a = 0; a < argc; ++a)
    {
        command = command + " " + argv[a];
    }

    return system(command.c_str());
}

然后你跑

g++ embedded.cpp -o embedded
sudo chown root embedded
sudo chmod u+s embedded

为什么要下票???
西奥多·R·史密斯

2
可能是因为它是一个过于复杂的解决方案,可以进行字符串操作并嵌入了外壳代码。您可以制作一个简单的启动器,也可以使用sudo。这是所有选择中最糟糕的。
siride
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.