【问题标题】:How to perform file / directory manipulation with user privileges in mind?如何在考虑用户权限的情况下执行文件/目录操作?
【发布时间】:2011-01-31 04:57:21
【问题描述】:

我有一个将在系统帐户下运行的服务器应用程序,因为在任何给定时间,它都会代表系统上的任何用户处理请求。这些请求包含操作文件系统的指令。

这里有一个问题:程序在执行操作时需要牢记该特定用户的权限。例如,joe 的权限为 755,则不应修改 /home/larry

目前我的策略是这样的

  • 获取文件的所有者/组
  • 将其与尝试执行操作的用户的用户 ID/组 ID 进行比较
  • 如果匹配(或不匹配),则使用文件中权限字段的适当部分来允许或拒绝操作

这是明智的吗?有没有更简单的方法来做到这一点?

起初,我考虑让应用程序的多个实例在用户帐户下运行 - 但这不是一个选项,因为这样只有一个实例可以侦听给定的 TCP 端口。

【问题讨论】:

    标签: unix privileges file-manipulation


    【解决方案1】:

    查看samba 以了解可以做到这一点的示例。 samba 守护进程以 root 身份运行,但会尽快分叉并使用普通用户的凭据。

    Unix 系统有两组独立的凭据:真实用户/组 ID 和有效用户/组 ID。真正的集合确定了您的真实身份,而有效的集合定义了您可以访问的内容。如果您是 root 用户,您可以随意更改有效的 uid/gid — 包括普通用户并再次返回 — 因为您的真实用户/组 id 在转换期间保持 root 身份。因此,在单个进程中执行此操作的另一种方法是使用seteuid/gid 根据需要来回应用不同用户的权限。如果您的服务器守护程序以 root 身份运行或具有CAP_SETUID,那么这将被允许。

    但是,请注意,如果您能够随心所欲地切换有效的 uid/gid 并且您的应用程序被破坏,那么例如,该破坏可以将有效的 uid/gid 切换回 0,您可能会遇到严重的安全漏洞.这就是为什么尽快永久删除所有权限是明智的,包括您的真实用户 uid/gid。

    因此,以 root 身份运行单个侦听套接字是正常且安全的,然后分叉并通过调用 setuid 更改真实和有效的用户 ID。然后就不能变回来了。您的分叉进程将拥有accept()ed 的套接字,因为它是一个分叉。每个进程只是关闭它们不需要的文件描述符;套接字保持活动状态,因为它们被相反进程中的文件描述符引用。

    您也可以尝试通过自己单独检查权限来强制执行权限,但我希望很明显这很容易出错,有很多边缘情况并且更容易出错(例如,它不起作用使用 POSIX ACL,除非您也专门实现它)。

    所以,你有三个选择:

    1. fork 和setgid()/setuid() 给你想要的用户。如果需要通信,请在分叉前使用pipe(2)socketpair(2)
    2. 不要根据需要分叉和seteuid()/setegid()(不太安全:更有可能意外破坏您的服务器)。
    3. 不要乱用系统凭据;手动执行权限(不太安全:更容易出错)。

    如果您需要与守护进程通信,那么尽管通过套接字或管道进行通信可能更困难,但第一个选项确实是正确的安全方式。例如,请参阅how ssh does privilege separation。您还可以考虑是否可以更改您的架构,以便进程可以共享一些内存或磁盘空间,而不是任何通信。

    您提到您考虑为每个用户运行一个单独的进程,但需要一个侦听 TCP 端口。你仍然可以这样做。只需让一个主守护进程监听 TCP 端口并将请求分派给每个用户守护进程并根据需要进行通信(例如,通过 Unix 域套接字)。这实际上与拥有一个分叉主守护进程几乎相同;我认为后者会更容易实现。

    进一步阅读:credentials(7) 联机帮助页。另请注意,Linux 具有文件系统 uid/gids;除了发送信号等其他内容外,这与有效的 uid/gids 几乎相同。如果您的用户没有 shell 访问权限并且无法运行任意代码,那么您无需担心差异。

    【讨论】:

    • 感谢您写得很好的答案!是否可以创建一个主进程来侦听端口,然后通过另一个套接字连接将数据转发到另一个进程?这就是你在倒数第二段中所说的吗?
    • 是的,没错,你可以这样做。但是,根据您所说的一切,我很确定您实现分叉主监听守护程序并让孩子删除权限会更容易和更简单。分叉的进程将自动访问请求套接字,就像主守护进程一样;不需要沟通那部分。
    • 好的。我正在寻找一种同样适用于 Qt 的解决方案,尽管这不是必需的。所以我在决定正确的方法时会考虑到这一点。
    • 据我所知,使用fork() 执行此操作在 Qt 上可以正常工作,但当然它不再是跨平台的,因为 Windows 没有这样的概念。
    • 谢谢!我接受了您的回答并获得了赏金 - 您的回答非常有帮助。
    【解决方案2】:

    我会让我的服务器 fork() 并立即 setuid(uid) 放弃 root 权限。然后任何文件操作都将代表您已成为的用户。由于您是服务器的子节点,因此您仍将持有接受()处理的子套接字,请求(我假设响应)将继续。这(显然)需要守护进程的 root 权限。

    在这种情况下,在进程之间传递文件描述符似乎不必要地复杂,因为子进程已经有了“请求”描述符。

    【讨论】:

    • 嗯……这当然有可能。
    【解决方案3】:

    让一台服务器在previlegued服务器端口上运行,并为登录系统的用户生成子进程。子进程应该放弃特权并模拟登录的用户。现在子进程不能再造成伤害了。

    【讨论】:

    • 是的,但是连接如何从服务器转移到子进程?
    • 我相信这可以通过让根进程设置套接字,然后将文件描述符传递给子进程来实现。我不知道这是如何完成的,但至少 PostgreSQL 以类似的方式工作,对于每个连接,都会产生一个子进程来处理它。而且我不敢相信所有的流量都必须通过主进程,所以会有一些 fd 从那里经过。
    • 嗯...我会调查的。这确实使事情变得更加复杂,但因为子进程必须与父进程通信。
    猜你喜欢
    • 2021-01-05
    • 1970-01-01
    • 1970-01-01
    • 2019-09-08
    • 2014-11-22
    • 1970-01-01
    • 2017-08-01
    • 1970-01-01
    • 2013-10-04
    相关资源
    最近更新 更多