5

Linux 是否有一些类似于setuid的 C 接口,它允许程序使用例如用户名/密码切换到不同的用户?setuid的问题在于它只能由超级用户使用。

我正在运行一个简单的 Web 服务,该服务需要以登录用户身份执行作业。所以主进程以root身份运行,用户登录后分叉并调用setuid切换到合适的uid。但是,我对以 root 身份运行的主 proc 不太满意。我宁愿让它作为另一个用户运行,并有一些机制可以切换到另一个类似的用户su(但不启动新进程)。

4

2 回答 2

10

首先,setuid()绝对可以由非超级用户使用。从技术上讲,您在 Linux 中所需要的只是CAP_SETUID(和/或CAP_SETGID)切换到任何用户的能力。其次,setuid()可以在真实(执行进程的用户)、有效(setuid/setgid 二进制文件的所有者)和保存setgid()的身份之间更改进程身份。

但是,这些都与您的情况无关。

有一个相对简单但非常健壮的解决方案:有一个 setuid 根助手,在创建任何线程之前由您的服务守护进程分叉和执行,并使用 Unix 域套接字对在助手和服务之间进行通信,服务通过两者当要执行用户二进制文件时,它的凭据和管道端点文件描述符到帮助程序。助手将安全地检查所有内容,如果一切正常,它将派生并执行所需的用户助手,指定的管道端点连接到标准输入、标准输出和标准错误。

服务尽早启动助手的过程如下:

  1. 创建一个 Unix 域套接字对,用于服务和助手之间的特权通信。

  2. 叉子。

  3. 在子进程中,关闭所有多余的文件描述符,只保留套接字对的一端。将标准输入、输出和错误重定向到/dev/null.

  4. 在父级中,关闭套接字对的子端。

  5. 在子进程中,执行特权助手二进制文件。

  6. 父母发送一条简单的消息,可能根本没有任何数据,但带有包含其凭据的辅助消息。

  7. 帮助程序等待来自服务的初始消息。当它收到它时,它会检查凭据。如果凭据没有通过集合,它会立即退出。

辅助消息中的凭证定义了发起进程' UIDGIDPID。虽然进程需要填写这些,但内核会验证它们是否正确。助手当然会验证这一点UIDGID符合预期(对应于服务应该运行的帐户),但诀窍是获取/proc/PID/exe符号链接指向的文件的统计信息。那是发送凭据的进程的真正可执行文件。您应该验证它与已安装的系统服务守护程序(由 root:root 拥有,在系统二进制目录中)相同。

到目前为止,有一个非常简单的攻击可能会破坏安全性。恶意用户可能会创建自己的程序,该程序会正确分叉并执行辅助二进制文件,使用其真实凭据发送初始消息——但在辅助程序有机会检查凭据实际引用的内容之前,会用正确的系统二进制文件替换自己!

进一步的三个步骤轻松地击败了该攻击:

  1. 帮助程序生成一个(密码安全的)伪随机数,比如 1024 位,并将其发送回父级。

  2. 父母将号码发回,但再次将其凭据添加到辅助消息中。

  3. 帮助程序验证UIDGIDPID没有更改,并且/proc/PID/exe仍然指向正确的服务守护程序二进制文件。(我只是重复完整的检查。)

在第 8 步,助手已经确定套接字的另一端正在执行它应该执行的二进制文件。向它发送一个它必须发回的随机cookie,意味着另一端不能事先用消息“填充”套接字。当然,这假设攻击者无法事先猜出伪随机数。如果您要小心,可以从 中读取合适的 cookie /dev/random,但请记住它是有限资源(如果内核没有足够的随机性可用,可能会阻塞)。我个人只是从 中读取 1024 位(128 字节)/dev/urandom,然后使用它。

此时,助手已确定套接字对的另一端是您的服务守护程序,并且助手可以信任控制消息,只要它可以信任服务守护程序。(我假设这是服务守护进程生成用户进程的唯一机制;否则,您需要在每条进一步的消息中重新传递凭据,并每次在帮助程序中重新检查它们。)

每当服务守护进程希望执行用户二进制文件时,它

  1. 创建必要的管道(一个用于将标准输入提供给用户二进制文件,一个用于从用户二进制文件获取标准输出)

  2. 向助手发送一条消息,其中包含

    • 运行二进制文件的身份;用户(和组)名称,或 UID 和 GID
    • 二进制文件的路径
    • 提供给二进制文件的命令行参数
    • 包含数据管道的用户二进制端点的文件描述符的辅助消息

每当助手收到这样的消息时,它就会分叉。在子进程中,它用辅助消息中的文件描述符替换标准输入和输出,用和/或更改身份setresgid()setresuid()initgroups()工作目录更改到适当的位置,并执行用户二进制文件。父辅助进程关闭辅助消息中的文件描述符,并等待下一条消息。

如果帮助程序在套接字不再有输入时退出,那么它会在服务退出时自动退出。

如果有足够的兴趣,我可以提供一些示例代码。有很多细节需要做对,所以代码写起来有点乏味。但是,如果编写正确,它比例如 Apache SuEXEC 更安全。

于 2012-10-24T10:13:03.530 回答
4

不,仅使用用户名和密码无法更改 UID。(内核不以任何方式识别“密码”的概念——它只存在于用户空间中。)要从一个非 root UID 切换到另一个,您必须成为 root 作为中间步骤,通常通过exec()-uting一个 setuid 二进制文件。

在您的情况下,另一种选择可能是让主服务器以非特权用户身份运行,并让它与以 root 身份运行的后端进程通信。

于 2012-10-23T23:09:08.450 回答