如何在考虑到用户权限的情况下执行文件/目录操作?
我有一个将在系统帐户下运行的服务器应用程序,因为在任何给定时间,它将代表系统上的任何用户处理请求。这些请求包含用于操作文件系统的指令。
问题是:程序在执行操作时需要记住特定用户的权限。例如,如果 joe
的权限为 755
,则该 joe
不应能够修改 /home/larry
。
目前我的策略是
- 获取文件的所有者/组
- 将其与尝试执行操作的用户的用户 ID/组 ID 进行比较
- 如果匹配(或者如果没有匹配),则使用权限字段中的适当部分文件允许或拒绝该操作
这样做明智吗?有没有更简单的方法来做到这一点?
起初,我考虑在用户帐户下运行应用程序的多个实例 - 但这不是一个选项,因为这样只有一个实例可以侦听给定的 TCP 端口。
I have a server application that will be running under a system account because at any given time, it will be processing requests on behalf of any user on the system. These requests consist of instructions for manipulating the filesystem.
Here's the catch: the program needs to keep that particular user's privileges in mind when performing the actions. For example, joe
should not be able to modify /home/larry
if its permissions are 755
.
Currently my strategy is this
- Get the owner / group of the file
- Compare it to the user ID / group ID of the user trying to perform the action
- If either match (or if none match), use the appropriate part of the permission field in the file to either allow or deny the action
Is this wise? Is there an easier way to do this?
At first, I was thinking of having multiple instances of the app running under the user's accounts - but this is not an option because then only one of the instances can listen on a given TCP port.
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论
评论(3)
看一下 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)除非你也专门实现了这一点)。
因此,您有三个选择:
setgid()
/setuid()
给您想要的用户。如果需要通信,请在 fork 之前使用pipe(2)
或socketpair(2)
。seteuid()
/setegid()
(不太安全:更有可能意外损害您的服务器)。如果您需要与守护进程进行通信,那么尽管通过套接字或管道进行通信可能会比较困难,但第一个选项确实是正确的安全方法。例如,请参阅 ssh 如何进行权限分离。您可能还会考虑是否可以更改您的体系结构,以便进程可以只共享一些内存或磁盘空间,而不是进行任何通信。
您提到您考虑为每个用户运行一个单独的进程,但需要一个侦听 TCP 端口。你仍然可以这样做。只需让一个主守护进程监听 TCP 端口并将请求分派给每个用户守护进程并根据需要进行通信(例如,通过 Unix 域套接字)。这实际上与拥有一个分叉主守护进程几乎相同;我认为后者会更容易实施。
进一步阅读:
credentials(7)
联机帮助页。另请注意,Linux 有文件系统 uid/gids;除了发送信号等其他内容之外,这几乎与有效的 uid/gids 相同。如果您的用户没有 shell 访问权限并且无法运行任意代码,那么您无需担心差异。Take a look at samba for an example of this can be done. The samba daemon runs as root but forks and assumes the credentials of a normal user as soon as possible.
Unix systems have two separate sets of credentials: the real user/group ids and the effective user/group ids. The real set identifies who you actually are, and the effective set defines what you can access. You can change the effective uid/gid as you please if you are root—including to an ordinary user and back again—as your real user/group ids remain root during the transition. So an alternative way to do this in a single process is to use
seteuid/gid
to apply the permissions of different users back and forth as needed. If your server daemon runs as root or hasCAP_SETUID
then this will be permitted.However, notice that if you have the ability to switch the effective uid/gid at whim and your application is subverted, then that subversion could for example switch the effective uid/gid back to 0 and you could have a serious security vulnerability. This is why it is prudent to drop all privileges permanently as soon as possible, including your real user uid/gid.
For this reason it is normal and safer to have a single listening socket running as root, then fork off and change both the real and effective user ids by calling
setuid
. Then it cannot change back. Your forked process would have the socket that wasaccept()
ed as it is a fork. Each process just closes the file descriptors they don't need; the sockets stay alive as they are referenced by the file descriptors in the opposite processes.You could also try and enforce the permissions by examining them individually yourself, but I hope it is obvious that this is potentially error-prone, has lots of edge cases and more likely to go wrong (eg. it won't work with POSIX ACLs unless you specifically implement that too).
So, you have three options:
setgid()
/setuid()
to the user you want. If communication is required, usepipe(2)
orsocketpair(2)
before you fork.seteuid()
/setegid()
around as needed (less secure: more likely to compromise your server by accident).If you need to communicate with the daemon, then although it might be harder to do it down a socket or a pipe, the first option really is the proper secure way to go about it. See how ssh does privilege separation, for example. You might also consider if can change your architecture so instead of any communication the process can just share some memory or disk space instead.
You mention that you considered having a separate process run for each user, but need a single listening TCP port. You can still do this. Just have a master daemon listen on the TCP port and dispatch requests to each user daemon and communicate as required (eg. via Unix domain sockets). This would actually be almost the same as having a forking master daemon; I think the latter would turn out to be easier to implement.
Further reading: the
credentials(7)
manpage. Also note that Linux has file system uid/gids; this is almost the same as effective uid/gids except for other stuff like sending signals. If your users don't have shell access and cannot run arbitrary code then you don't need to worry about the difference.我会让我的服务器 fork() 并立即
setuid(uid)
放弃 root 权限。那么任何文件操作都将代表您所成为的用户。由于您是服务器的子级,因此您仍然保留请求(并且我假设响应)将继续的accept()ed子套接字。这(显然)需要守护进程的 root 权限。在这种情况下,在进程之间传递文件描述符似乎不必要地复杂,因为子进程已经具有“请求”描述符。
I would have my server fork() and immediately
setuid(uid)
to give up root privileges. Then any file manipulation would be on behalf of the user you've become. Since you're a child of the server you'd still hold the accept()ed child socket that the request (and I assume response) would go on. This (obviously) requires root privilege on the daemon.Passing file descriptors between processes seems unnecessarily complicated in this case, as the child already has the "request" descriptor.
让一台服务器在预先设置的服务器端口上运行,并为登录系统的用户生成子进程。子进程应该放弃特权并模拟登录的用户。现在子进程不能再造成伤害了。
Let one server run on the previlegued server port, and spawn child processes for users that log into the system. The child processes should drop privilegues and inpersonate the user that logged in. Now the childs cannot do harm anymore.