我正在用 C 语言编写一个 HTTP 服务器守护程序(这是有原因的),并使用 systemd 单元文件对其进行管理。
我正在重写一个 20 年前设计的应用程序,大约在 1995 年。他们使用的系统是 chroot,然后是 setuid,以及标准程序。
现在,在我之前的工作中,通常的政策是您永远不会以 root 身份运行任何进程。您为它创建一个用户/组并从那里运行。当然,系统确实以root身份运行了一些东西,但我们可以不以root身份完成所有业务逻辑处理。
现在对于 HTTP 守护进程,如果我不在应用程序中 chroot,我可以在没有 root 的情况下运行它。那么,应用程序永远不会以 root 身份运行不是更安全吗?
从一开始就以 mydaemon-user 身份运行它不是更安全吗?而不是从 root 启动它,chrooting,然后 setuid 到 mydaemon-user?
似乎其他人错过了你的观点,这不是为什么要使用变根的原因,当然你已经清楚地知道了,也不是你可以做些什么来限制守护进程,当你也清楚地知道在非特权用户帐户;但是为什么要在应用程序内部做这些事情。实际上有一个相当恰当的例子来说明原因。
考虑
httpd
Daniel J. Bernstein 的 publicfile 包中守护程序的设计。它所做的第一件事是将 root 更改为它被告知要与命令参数一起使用的根目录,然后将权限删除到在两个环境变量中传递的非特权用户 ID 和组 ID。Dæmon 管理工具集具有专用工具,用于更改根目录和删除非特权用户和组 ID。Gerrit Pape 的 runit 有
chpst
. 我的 nosh 工具集有chroot
和setuidgid-fromenv
. Laurent Bercot 的 s6 有s6-chroot
和s6-setuidgid
. 韦恩·马歇尔 (Wayne Marshall) 的 Perp 有runtool
和runuid
. 等等。事实上,他们都有 M. Bernstein 自己的 daemontools 工具集setuidgid
作为先行词。人们会认为可以从
httpd
这些专用工具中提取功能并使用它们。然后,正如您所设想的,服务器程序的任何部分都不会以超级用户权限运行。问题是,一个直接后果是必须做更多的工作来设置更改的根,这会暴露出新的问题。
就Bernstein而言
httpd
,根目录树中唯一的文件和目录是要发布到世界各地的文件和目录。树上什么都没有。此外,任何可执行程序映像文件都没有理由存在于该树中。但是将根目录更改为链式加载程序(或 systemd),然后突然将程序映像文件
httpd
,它加载的任何共享库,以及程序加载器或 C 运行时库访问的任何特殊文件/etc
,/run
以及/dev
在程序初始化期间(如果您truss
/strace
是 C 或 C++ 程序,您可能会发现这非常令人惊讶),也必须存在于更改的根目录中。否则httpd
无法链接到并且不会加载/运行。请记住,这是一个 HTTP(S) 内容服务器。它可能会在更改的根目录中提供任何(世界可读的)文件。这现在包括您的共享库、程序加载器以及操作系统的各种加载器/CRTL 配置文件的副本。如果某些(意外)意味着内容服务器有权写入内容,则受感染的服务器可能会获得对
httpd
自身程序映像的写入权限,甚至是您系统的程序加载器。(请记住,您现在有两组平行的/usr
、/lib
、/etc
、/run
和/dev
目录来保证安全。)这些都不是
httpd
更改 root 并自行删除特权的情况。因此,您已经换取了少量特权代码,这些代码相当容易审计,并且在
httpd
程序开始时运行,以超级用户权限运行;因为在更改的根目录中具有极大扩展的文件和目录的攻击面。这就是为什么它不像在服务程序外部做所有事情那么简单。
请注意,这仍然是其自身的最低限度的功能
httpd
。所有执行操作的代码,例如在操作系统的帐户数据库中查找用户 ID 和组 ID 以首先放入这些环境变量中,都是程序外部的httpd
,在简单的独立可审计命令中,例如envuidgid
. (当然,它是一个 UCSPI 工具,因此它不包含任何用于侦听相关 TCP 端口或接受连接的代码,这些代码是诸如tcpserver
、tcp-socket-listen
、tcp-socket-accept
、s6-tcpserver4-socketbinder
、等命令的域s6-tcpserver4d
。)进一步阅读
httpd
. 公共文件. cr.yp.to.httpd
. Daniel J. Bernstein 的软件合二为一。软件。乔纳森·德博因·波拉德。2016 年。gopherd
. Daniel J. Bernstein 的软件合二为一。软件。乔纳森·德博因·波拉德。2017 年。我认为您问题的许多细节同样适用于
avahi-daemon
我最近看过的 。(我可能错过了另一个不同的细节)。在 chroot 中运行 avahi-daemon 有很多优点,以防 avahi-daemon 受到威胁。这些包括:当您不使用 dbus 或类似设备时,第 3 点可能特别好……我认为 avahi-daemon 使用 dbus,因此即使从 chroot 内部也可以确保保持对系统 dbus 的访问。如果您不需要在系统 dbus 上发送消息的能力,那么拒绝该能力可能是一个很好的安全功能。
请注意,如果 avahi-daemon 被重写,它可能会选择依赖 systemd 来保证安全性,并使用例如
ProtectHome
. 我建议对 avahi-daemon 进行更改,以将这些保护添加为额外的层,以及 chroot 无法保证的一些额外保护。您可以在此处查看我提出的完整选项列表:https://github.com/lathiat/avahi/pull/181/commits/67a7b10049c58d6afeebdc64ffd2023c5a93d49a
如果 avahi-daemon不使用 chroot 本身,我可以使用更多限制,其中一些在提交消息中提到。我不确定这适用多少。
请注意,我使用的保护措施不会限制守护进程打开 unix 套接字文件(上面的第 3 点)。
另一种方法是使用 SELinux。但是,您可能会将您的应用程序绑定到该 Linux 发行版的子集。我在这里积极考虑 SELinux 的原因是 SELinux 以细粒度的方式限制了进程对 dbus 的访问。例如,我认为您可能经常期望它
systemd
不会出现在您需要能够向其发送消息的总线名称列表中:-)。“我想知道,如果使用 systemd 沙盒比 chroot/setuid/umask/ 更安全...”
摘要:为什么不两者兼而有之?让我们对上面的内容进行一点解码:-)。
如果您考虑第 3 点,使用 chroot 会提供更多限制。ProtectHome= 及其朋友甚至不尝试像 chroot 那样限制。(例如,没有一个命名的 systemd 选项黑名单
/run
,我们倾向于放置 unix 套接字文件)。chroot 表明限制文件系统访问可能非常强大,但并非Linux 上的所有内容都是文件 :-)。有一些 systemd 选项可以限制其他东西,而不是文件。如果程序受到攻击,这很有用,您可以减少它可用的内核功能,它可能会尝试利用其中的漏洞。例如 avahi-daemon 不需要蓝牙套接字,我猜您的 Web 服务器也不需要:-)。所以不要让它访问 AF_BLUETOOTH 地址系列。
RestrictAddressFamilies=
只需使用该选项将 AF_INET、AF_INET6 和 AF_UNIX 列入白名单。请阅读您使用的每个选项的文档。有些选项与其他选项结合使用会更有效,有些选项并非在所有 CPU 架构上都可用。(不是因为 CPU 不好,而是因为那个 CPU 的 Linux 端口设计得不太好。我认为)。
(这里有一个一般原则。如果您可以编写您想要允许的列表,而不是您想要拒绝的列表,那么它会更安全。就像定义一个 chroot 为您提供了一个您可以访问的文件列表,这更健壮而不是说你想阻止
/home
)。原则上,您可以在 setuid() 之前自己应用所有相同的限制。这只是您可以从 systemd 复制的代码。但是,systemd 单元选项应该更容易编写,并且由于它们是标准格式,因此应该更易于阅读和查看。
因此,我强烈建议您通读
man systemd.exec
目标平台上的沙盒部分。但是,如果您想要尽可能安全的设计,我也不会害怕在您的程序中chroot
尝试(然后放弃root
特权)。这里有一个权衡。使用对您的整体设计施加了一些限制。如果您已经有一个使用 chroot 的设计,并且它似乎可以满足您的需求,那听起来非常棒。chroot
如果您可以依赖 systemd,那么将沙盒留给 systemd 确实更安全(也更简单!)。(当然,该应用程序还可以检测它是否已被 systemd 沙箱启动,如果它仍然是 root,则可以检测沙箱本身。)您描述的服务的等价物是:
但我们不必止步于此。systemd 还可以为你做很多其他的沙盒——这里有一些例子:
有关
man 5 systemd.exec
更多指令和更详细的描述,请参阅。如果你让你的守护进程man 5 systemd.socket
可以激活套接字(如果它是一个简单的服务器,只监听一些端口,不需要连接到其他服务器,这可能很有用。(在我看来,与文件系统相关的选项也可以使这些选项RootDirectory
过时,所以也许您不需要再费心为所有必需的二进制文件和库设置一个新的根目录了。)较新的 systemd 版本(自 v232 起)也支持
DynamicUser=yes
,其中 systemd 将自动为您分配服务用户,仅用于服务运行时。这意味着您不必为该服务注册永久用户,并且只要该服务不写入除其StateDirectory
、LogsDirectory
和之外的任何文件系统位置CacheDirectory
(您也可以在单元文件中声明),它就可以正常工作 -再看man 5 systemd.exec
一次——然后由哪个 systemd 管理,注意正确地将它们分配给动态用户)。