以编程方式写入具有 root 权限的文件的最安全方法是什么?

vsz*_*vsz 35 setuid privileges

一个巨大的应用程序需要在某个特定时间对需要 root 权限的文件执行少量写入。它不是真正的文件,而是作为文件暴露给 Linux 的硬件接口。

为了避免为整个应用程序授予 root 权限,我编写了一个 bash 脚本来执行关键任务。例如,以下脚本将启用硬件接口的端口 17 作为输出:

echo "17" > /sys/class/gpio/export
echo "out" > /sys/class/gpio/gpio17/direction
Run Code Online (Sandbox Code Playgroud)

但是,由于suid我系统上的 bash 脚本被禁用,我想知道实现这一目标的最佳方法是什么。

  1. 使用此处介绍的一些解决方法

  2. sudo从主应用程序调用脚本,并相应地编辑 sudoers 列表,以避免在调用脚本时需要密码。授予 sudo 权限让我有点不舒服echo

  3. 只需编写一个 C 程序,使用fprintf,并将其设置为 suid root。对字符串和文件名进行硬编码,并确保只有 root 可以编辑它。或者从文本文件中读取字符串,同样确保没有人可以编辑该文件。

  4. 我没有想到的其他一些解决方案比上面介绍的更安全或更简单?

cas*_*cas 33

您无需授予sudo访问权限echo。事实上,这是没有意义的,因为例如使用sudo echo foo > bar,重定向是作为原始用户而不是 root 完成的。

使用 调用小脚本sudoNOPASSWD:仅允许需要访问它的用户访问该脚本(和任何其他类似脚本)。

这始终是使用sudo. 将少量需要 root 权限的命令隔离到它们自己的单独脚本中,并允许不受信任或部分受信任的用户仅以 root 身份运行该脚本。

sudo脚本不应该从用户那里获取参数(或输入)(即它调用的任何其他程序都应该有硬编码的选项和参数),或者它应该非常小心地验证它必须的任何参数/输入接受用户。

在验证中保持偏执——而不是寻找“已知的坏”事物来排除,只允许“已知的好”事物并在任何不匹配或错误或任何甚至远程可疑的情况下中止。

验证应尽可能早地在脚本中进行(最好在以 root 身份执行任何其他操作之前)。


当我第一次写这个答案时,我真的应该提到这一点,但如果你的脚本是一个 shell 脚本,它必须正确引用所有变量。以任何方式引用包含用户提供的输入的变量时要特别小心,但不要假设某些变量是安全的,引用它们全部

包括由用户潜在地控制(例如环境变量"$PATH""$HOME""$USER"等。并且,包括绝对"$QUERY_STRING""HTTP_USER_AGENT"等在一个CGI脚本)。事实上,只需引用它们。如果您必须构造带有多个参数的命令行,请使用数组来构建 args 列表并引用 - "${myarray[@]}"

我是否经常说“全部引用”?记住它。做吧。

  • 你忘了提到脚本本身应该由 root *拥有*,权限为 500。 (18认同)
  • 至少删除写权限,看在上帝的份上。那真的是我的观点。剩下的只是硬化。 (13认同)
  • 精心编写的 C 程序将比 shell 脚本具有更小的攻击面。 (10认同)
  • @immibis,可能。但是编写和调试比 shell 脚本需要更长的时间。并且需要有 C 编译器(某些生产服务器上禁止使用 C 编译器,以通过使攻击者更难编译漏洞来降低安全风险)。此外,与由具有类似技能的人编写的 C 程序相比,IMO 由新手到中级系统管理员或程序员编写的 shell 脚本不太可能被利用 - 特别是如果它必须接受和验证用户提供的数据。 (7认同)
  • @JonasWielicki - 我认为我们现在已经完全进入了观点而非事实的领域。您可以为 shell 或 C(或 perl 或 python 或 awk 等)提供有效的参数,这些参数或多或少容易出现可利用的错误。实际上,这主要取决于程序员的技能和对细节的关注(以及疲倦、匆忙、谨慎等)。然而,事实是,较低级别的语言往往需要编写更多的代码来实现在高级语言中可以用更少的代码行完成的工作......而且每个 LoC 都是另一个机会要犯的错误。 (3认同)

jpa*_*jpa 16

检查 gpio 文件的所有者:

ls -l /sys/class/gpio/
Run Code Online (Sandbox Code Playgroud)

很可能,您会发现它们归组所有gpio

-rwxrwx--- 1 root     gpio     4096 Mar  8 10:50 export
...
Run Code Online (Sandbox Code Playgroud)

在这种情况下,您只需将您的用户添加到gpio组中即可授予访问权限,而无需 sudo:

sudo usermod -aG gpio myusername
Run Code Online (Sandbox Code Playgroud)

您需要注销并在此之后重新登录才能使更改生效。

  • @vsz 你试过`chgrp gpio /sys/devices/platform/soc/*/gpio`吗?也许类似的东西可以放在启动文件中。 (4认同)

mat*_*tdm 7

对此的一种解决方案(特别是在 Linux 桌面上使用,但也适用于其他情况)是使用D-Bus激活以 root身份运行的小型服务,并使用polkit进行授权。这就是 polkit 的设计初衷;从其介绍性文件

polkit 提供了一个授权 API,旨在供特权程序(“MECHANISMS”)使用,为非特权程序(“CLIENTS”)提供服务。有关系统架构和大图,请参阅 polkit 手册页。

大的、无特权的程序不会执行您的帮助程序,而是会在总线上发送请求。您的助手可以作为在系统启动时启动的守护程序运行,或者更好地由 systemd 根据需要激活。然后,该助手将使用 polkit 来验证请求是否来自授权位置。(或者,在这种情况下,如果感觉有点矫枉过正,您可以使用其他一些硬编码的身份验证/授权机制。)

我找到了一篇关于通过 D-Bus 进行通信很好的基本文章,虽然我还没有对其进行测试,但这似乎是将 polkit 添加到 mix 的基本示例

在这种方法中,不需要标记 setuid。


Pet*_*des 5

一种方法是制作一个用 C 编写的 setuid-root 程序,它只执行需要的操作,仅此而已。在您的情况下,它根本不需要查看任何用户输入。

#include <unistd.h>
#include <string.h>
#include <stdio.h>  // for perror(3)
// #include ...  more stuff for open(2)

static void write_str_to_file(const char*fn, const char*str) {
    int fd = open(fn, O_WRONLY)
    if (-1 == fd) {
        perror("opening device file");  // make this a CPP macro instead of function so you can use string concat to get the filename into the error msg
        exit(1);
    }
    int err = write(fd, str, strlen(str));
    // ... error check
    err = close(fd);
    // ... error check
}

int main(int argc, char**argv) {
    write_string_to_file("/sys/class/gpio/export", "17");
    write_string_to_file("/sys/class/gpio/gpio17/direction", "out");
    return 0;
}
Run Code Online (Sandbox Code Playgroud)

没有办法通过环境变量或任何东西来破坏它,因为它所做的只是进行几个系统调用。

缺点:您应该检查每个系统调用的返回值。

好处:错误检查真的很容易:如果有任何错误,只需perror退出:以非零状态退出。如果有错误,请使用 进行调查strace。你不需要这个程序本身来提供非常好的错误消息。

  • 我可能想在写任何东西之前打开这两个文件,以防止第二个文件的缺失。我可能会通过 `sudo` 运行这个程序,所以它本身不需要 setuid。顺便说一下,`open()` 是 `&lt;fcntl.h&gt;`。 (2认同)