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


35

大型应用程序需要在特定时间对需要根权限的文件执行少量写入操作。它实际上不是文件,而是硬件接口,它作为文件公开给Linux。

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

echo "17" > /sys/class/gpio/export
echo "out" > /sys/class/gpio/gpio17/direction

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

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

  2. sudo从主应用程序使用调用脚本,并相应地编辑sudoers列表,以避免在调用脚本时要求输入密码。给sudo特权我有点不自在echo

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

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


10
您为什么不只使用root特权启动程序,打开文件并放弃特权?这就是每个网络服务器或类似服务器如何对套接字进行操作的方式。实际上,您不必以root用户身份运行,也不需要这样做。
戴蒙

Answers:


33

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

使用调用小脚本sudo,从而允许需要NOPASSWD:访问该脚本的用户仅访问该脚本(和任何其他类似的脚本)。

这始终是最好/最安全的使用方式sudo。将少量需要root特权的命令隔离到自己的单独脚本中,并允许不受信任或部分受信任的用户仅以root身份运行该脚本。

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

在验证中要偏执狂-而不是寻找排除“已知不良”的东西,只允许“已知不良”的东西,并在任何不匹配或错误或什至是可疑的东西时中止。

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


我在初次编写此答案时确实应该提到这一点,但是如果您的脚本是Shell脚本,则必须正确引用所有变量。请特别小心用任何方式引用包含用户提供的输入的变量,但不要假设某些变量是安全的QUOTE THEM ALL

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

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


18
你忘了提及的是,脚本本身应该拥有的根,具有权限500
通配符

13
至少出于天生的缘故,删除写权限。那真的是我的意思。其余的只是在变硬。
通配符

10
精心编写的C程序的攻击面比Shell脚本小。
user253751 '16

7
@immibis,可能。但是与Shell脚本相比,编写和调试时间要长得多。并且需要使用C编译器(某些生产服务器上禁止使用C编译器,以使攻击者更难编译漏洞利用程序,从而降低安全风险)。另外,与具有类似技能的人编写的C程序相比,IMO由新手向中级sysadmin或程序员编写的shell脚本被利用的可能性较小-特别是在必须接受和验证用户提供的数据的情况下。
cas

3
@JonasWielicki-我认为我们现在真正进入了观点而非事实领域。您可以为shell或C(或perl或python或awk等)提供有效的参数,这些参数或多或少容易产生可利用的错误。何时才真正取决于程序员的技能和对细节的关注(以及疲劳,匆忙,谨慎等)。但是,事实是,较低级别的语言往往需要编写更多的代码才能实现在高级语言中用更少的代码行就能完成的工作。要犯的错误。
cas

16

检查gpio文件的所有者:

ls -l /sys/class/gpio/

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

-rwxrwx--- 1 root     gpio     4096 Mar  8 10:50 export
...

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

sudo usermod -aG gpio myusername

您需要注销然后重新登录,以使更改生效。


没用 确实,其中所有内容的组所有者/sys/class/gpio/都是gpio,但是即使将自己添加到该组中,只要尝试在该组中编写任何内容,我仍然会被“拒绝权限”。
vsz 2016年

1
问题在于,其中的文件/sys/class/gpio/实际上只是/sys/devices/platform/soc/<some temporary name>/gpio所有者和组均为根的位置的符号链接。
vsz 2016年

4
@vsz你尝试了chgrp gpio /sys/devices/platform/soc/*/gpio吗?也许可以将类似的内容放入启动文件中。
jpa

是的,但这不是那么简单。由于它们总是以不同的方式生成,因此我不得不使用类似chgrp gpio `readlink -f /sys/class/gpio/gpio18`/*
vsz 2016年

7

一种解决方案(特别是在Linux桌面上使用,但也适用于其他情况)是使用D-Bus激活以root用户身份运行的小型服务,并通过polkit进行授权。从根本上讲,这就是polkit的目的。从其介绍性文档中

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

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

我找到了一篇有关通过D-Bus进行通信很好的基础文章,尽管我没有对其进行测试,但这似乎是将polkit添加到混合中的一个基本示例

在这种方法中,不需要将任何内容标记为setuid。


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;
}

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

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

好处:错误检查真的很容易:如果根本没有任何错误,请perror放心救助:以非零状态退出。如果有错误,请通过进行调查strace。您不需要此程序本身即可给出非常好的错误消息。


2
我可能会在写任何东西之前先打开两个文件,以防止第二个文件的丢失。而且我可能通过运行该程序,sudo因此它本身不需要设置为setuid。顺便说一句,它<fcntl.h>用于open()
乔纳森·莱夫勒

3

Bless tee代替sudo的echo是解决需要限制根权限的一种常见方法。重定向到/ dev / null是为了阻止任何输出泄漏-tee可以满足您的要求。

echo "17" | sudo tee /sys/class/gpio/export > /dev/null
echo "out" | sudo tee /sys/class/gpio/gpio17/direction > /dev/null

5
如果您允许tee与一起运行sudo,则允许任何文件被覆盖或附加到其中(例如/etc/passwd-仅附加一个新的uid = 0帐户)。您也可以允许所有命令一起运行sudo(BTW /etc/sudoers属于“任意文件”集,因此可以用覆盖sudo tee
cas

2
显然,您可以限制tee可以写入的文件,如本答案中单独问题所述。请确保您也阅读了注释,因为有些人在使用原始语法时遇到了问题,并提出了解决该问题的建议。
亚历克斯

1
@alex,是的,您可以使用sudo中的任何命令来执行此操作-限制允许的args。如果要允许使用tee或对许多文件进行任何操作,则配置可能会变得很长且很复杂sudo
cas

0

您可以制作执行所需任务的脚本。然后,创建一个只能通过提供OpenSSH使用的密钥登录的新用户帐户。

注意:如果任何人都有密钥文件,则任何人都可以运行该脚本,因此请确保您希望阻止执行该任务的任何人都无法读取OpenSSH密钥文件。

在OpenSSH配置中(在authorized_keys文件中),在指定密钥之前,请指定命令(后跟一个空格),如以下“示例”文本中所述:

command="myscript" keydata optionalComment

此配置选项可以将OpenSSH密钥限制为仅运行特定命令。现在,您已经sudo授予了权限,但是OpenSSH配置实际上是用于限制/限制该用户能够执行的操作的解决方案,因此该用户不会运行其他命令。这也没有“ sudo”配置文件那么复杂,因此,如果您使用OpenBSD或OpenBSD的新“ doas”(“ do as”)开始流行(随您使用的任何操作系统的未来版本) ,您无需在sudo配置中面临太多复杂性的挑战。

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.