允许用户执行 shell 脚本而不查看其内容?

允许用户执行 shell 脚本而不查看其内容?

我想要一个使用 gmail 帐户发送电子邮件的 hg hook。显然,我不希望除了我或 root 之外的任何人能够读取电子邮件发送脚本,因为它有密码,所以我尝试了以下方法:

-rwsr-xr-x  1 james james   58 Feb 18 12:05 incoming.email.sh
-rwx--x--x  1 james james  262 Feb 18 12:04 send-incoming-email.sh

incoming.email.sh作为钩子执行的文件在哪里:

#! /bin/bash
/path/to/send-incoming-email.sh

但是,当我尝试以另一个用户身份运行时,出现错误:

/bin/bash: /path/to/send-incoming-email.sh: Permission denied

当我以自己的身份运行时该send-incoming-email.sh文件运行正常。

我尝试做的事情是否可行,或者 setuid 是否不会传播到从 shell 脚本执行的命令?

系统是Ubuntu 10.04.2 LTS。

答案1

如果你需要你的解决方案按原样工作,一个简单的方法是使用一个简短的 C 程序而不是 shell 脚本:

int main(){
setuid(geteuid());
system("/path/to/send-incoming-email.sh");
}

并具有该 setuid,从而避免竞争条件,同时允许您以 root 身份执行脚本。

到目前为止,这不是最好的解决方案,但它可以解决所描述的问题。

答案2

Linux 将忽略setuidshell 脚本的位以避免可能出现的竞争条件。


在 Unix/Linux 系统上发送电子邮件的“正确”方式是配置 MTA,例如后缀Exim4或者发送邮件并让它处理 SMTP 身份验证问题。还有“仅中继”MTA -邮件传输协议邮件传输协议邮件传输协议. 所有这些都可以通过身份验证进行 SMTP 中继(“智能主机”),例如通过 Gmail 服务器。在多用户机器上会变得更加棘手,但仍然可行。

(配置 MTA 后,发送电子邮件是通过将数据传递给来完成的/usr/sbin/sendmail rcpt@address。)

答案3

几乎所有系统都会忽略脚本中的setuidsetgid位。(这不是错误或疏忽,而是一项重要的安全功能;稍后会详细介绍。)


常见的解决方法是使用小型setuid和/或setgid二进制包装器。@jeremy-sturdivant 的答案中的基本版本是一个好的开始,但它不允许您传递任何参数。为此,您需要在修改它以指向实际脚本后将其传递argvexecve

#include <sys/types.h>
#include <stdio.h>   /* perror */
#include <unistd.h>  /* execve, geteuid, setuid */
int main(int argc, char **argv, char **envp) {
    setuid(geteuid());
    *argv = "/real/path/to/script";
    execve(*argv, argv, envp);
    perror(*argv);
    return 127;
}

如果您想要setgidsetuid只需将该行更改setuid...为:

    setgid(getegid());

这仍然相当基础,并且缺少setuid/周围的错误检查setgid。它还依赖于安装程序(您)检查是否存在其他泄漏,例如可写的祖先目录。


但是为什么在脚本中被忽略了呢setuidsetgid

首先,了解调用脚本时发生的情况很有帮助。

与任何程序一样,脚本由execve内核调用调用,它接受 3 个参数:(filename一个字符串)、argv(一个字符串数组)和envp(另一个字符串数组)。按照惯例argv[0]与“相同” filename,但这只是一个近似值;它可能会省略或更改路径,并且/或者它可能-在其前面加上一个以表明这应该是新登录会话的开始。(envp包含环境变量;对于本次讨论来说并不重要。)

和参数通常会不加改变argvenvp传递给新程序中的argv和参数。envpmain

但是如果程序文件以 开头#!,则内核将读取该文件的第一行以获取解释器文件名,最多一个解释器选项.然后它将修改提供的argv参数:

  • 原来的argv[0]将被丢弃并替换为filename,然后
  • filename参数设置为解释器文件名
  • 在前面插入一个或两个元素argv[]解释器文件名,以及解释器选项(如果给出)。

然后execve用这些新的论点重新开始。

这意味着当解释器启动时,脚本文件名main作为 的普通元素传递到其中argv,解释器只需打开该文件名并开始读取它。

setuid这意味着在系统调用期间检查位execve和脚本解释器打开脚本进行读取之间存在短暂的间隔。在此期间,攻击者可以filename用自己的脚本替换,然后使用新的 UID 或 GID 运行。


等一下,您说的是“几乎所有”系统。其他系统呢?

一些系统在execve系统调用期间打开脚本并使用 /dev/fd/ 调用解释器3setuid作为脚本的“名称”。这意味着或操作不会带来任何安全风险setgid,但它的缺点$0是在脚本中用处不大。

相关内容