Paranoid Penguin: 使用 iptables 进行本地安全防护
我们大多数人认为 iptables 严格来说是一种防火墙工具,用于抵御远程攻击者。但是您知道它也可以用于约束本地用户吗?实验性的匹配扩展 owner 添加了新的 iptables 选项,可用于帮助防止本地用户通过其他本地用户的网络进程发送数据包。
例如,假设 root 的一个 cron 作业使用 Stunnel 向远程 rsync 进程发送文件。在该隧道打开时,任何本地用户也可能使用它来访问远程 rsync 服务器。 iptables 可以帮助您防止此类蹭用行为;本月的专栏将展示如何做到这一点。
隧道实用程序是我们可用的最重要的新型安全工具类别之一。它们允许我们将不安全的服务(如 Telnet、IMAP 和 POP3)封装在加密的虚拟“隧道”中,透明且有效。我曾在这些页面上详细介绍过 Secure Shell 及其强大的端口转发功能;Stunnel 和 SSLWrap 是类似的免费工具,可以在 Linux 下用于此目的。
但是,当您在多用户系统上设置这样的隧道时会发生什么?有什么可以阻止未经授权的本地用户通过隧道发送他们自己的流量?直到最近,几乎什么都没有。由于大多数隧道实用程序的工作方式是为隧道的近端创建一个新的本地监听器(例如,localhost:992),并且由于通常任何本地用户都可以连接到本地监听端口,因此通常由隧道另一端的服务器应用程序来验证用户身份。
例如,假设我使用 Stunnel 从我的本地系统“crueller”到远程系统“strudel”创建一个安全套接字层 (SSL) 隧道,我将在其上运行 Telnet。(不必介意这种事务使用 SSH 会更简单;也许由于某些原因我不想在本地安装 SSH。)在远程主机上,该主机已经在 TCP 端口 23 上运行 Telnet 守护程序(通过 inetd),我以守护程序模式运行 Stunnel,命令如下
stunnel -d 992 -r localhost:23 -p \ /etc/stunnel/strudel.pem
在本地主机上,我将以客户端模式运行 Stunnel,也在本地端口 TCP 992 上监听,但将连接转发到 strudel 上的 TCP 端口 992
stunnel -c -d 992 -r strudel:992如果您以前从未使用过 Stunnel,并且这两个命令对您来说毫无意义,请不要担心。重要的是要理解,为了使用此示例隧道从 crueller 安全地 Telnet 到 strudel,我将在 crueller 上使用此命令
telnet localhost 992此时,strudel 将提示我输入用户名和密码,并且与普通 Telnet 不同,我的登录凭据将由 Stunnel 加密,而不是以明文形式在网络上传输。(远程 Stunnel 进程将解密数据包并将它们交给 strudel 的本地 Telnet 进程。这种情况发生在 整个 Telnet 事务中,而不仅仅是身份验证部分;Stunnel 在整个事务期间充当双方的中间人,只要它正在隧道传输的是 TCP,中间人既不知道也不关心它正在隧道传输什么。)
到目前为止一切顺利;我获得了加密,这是没有 Stunnel 就没有的,并且通过 Telnet 本身获得了一定程度的身份验证。问题是 crueller 上的 任何 用户都可以 Telnet 到本地 Stunnel 监听器 TCP 992,并尝试登录到 strudel。也许我担心有人猜测我的 strudel 密码,也许我不担心;但是如何从一开始就阻止他们将 任何 数据包发送到隧道中?使用 iptables 及其新的 owner 匹配扩展,就可以做到。
iptables 的 owner 匹配扩展为 iptables 命令添加了四个匹配标准
—uid-owner UID:匹配由用户 ID 为 UID 的进程生成的数据包。
—gid-owner GID:匹配由组 ID 为 GID 的进程生成的数据包。
—pid-owner PID:匹配由进程 ID 为 PID 的进程生成的数据包。
—sid-owner SID:匹配由会话 ID 为 SID 的进程生成的数据包。
在这四个中,前两个对于我们在此处的目的最有用。
所有者匹配扩展不一定包含在您的发行版的默认内核中;它被认为是实验性功能(由 Linux 内核团队而非 iptables 团队),因此您可能需要自己编译它。但是,它的源代码 是 标准 2.4 内核代码库的一部分,因此使用您发行版的任何最新版本 (2.4.x) 内核源代码包都可以轻松完成。
重新编译内核时,您需要显式设置几件事。首先,在代码成熟度级别选项下,选择“Prompt for development and/or incomplete code/drivers”。
接下来,除了您通常在网络选项中选择的其他网络协议和功能外,请确保选择“网络数据包过滤”。这将启用子组 IP:Netfilter 配置,如图 1 所示。您可以将这些选项编译到内核中(通过使用星号选择它们)或作为模块(使用 M),但大多数人将它们编译为模块,因为并非所有模块都一次使用。
当然,您可以根据需要选择任意数量的 Netfilter 模块。它们不占用太多磁盘空间,并且如果编译为模块,则除非必要,否则无需加载。但是,我们现在最关心的是 owner 匹配支持。
编译和安装 Linux 内核及其模块的其余过程在其他地方有详细文档记录(尤其是在内核源代码的 README 文件中)。一旦您使用内核编译、安装并重新启动,您就可以使用闪亮的新 owner 模块,该模块将被命名为 ipt_owner。
要加载此模块,请使用 modprobe 命令
modprobe ipt_owner
在实践中,您可能希望从 /etc/inet.d 中的启动脚本加载 iptables 规则。如果是这样,请确保将上述 modprobe 行添加到此脚本的开头(即,任何使用 owner 匹配的 iptables 命令之前)。
注意:Bastille-Linux 的自动化防火墙配置功能和 SuSE Linux 的 SuSEfirewall 脚本都不支持 owner 匹配,除非进行重大黑客攻击。这应该不足为奇;它们和其他简单的包过滤器规则生成器主要用于低影响的互联网保护,而不是用于高级控制本地用户访问。对于后者,您需要编写自己的 iptables 规则。
让我们回到我们的示例 Stunnel 客户端 crueller。假设 crueller 的内核已使用 ipt_owner 模块编译。您已使用 modprobe 加载了此模块,并且暂时 iptables 未配置,即,尚未过滤任何内容。
进一步假设您希望将本文开头考虑的 Telnet-over-Stunnel 套接字的使用限制为仅 root 用户。(您可能还记得我们在 crueller 上的 TCP 端口 992 上设置了一个 Stunnel 监听器,它加密并将数据包转发到 strudel 上的同一 TCP 端口。)
如果 crueller 不是防火墙,我们或许可以使用 OUTPUT 链的默认接受策略。在防火墙上,所有链都应具有默认丢弃或默认拒绝策略,但单宿主(单网络接口)堡垒主机有时可能对出站流量采取更宽松的立场。如果 crueller 上是这种情况,那么我们只需要一个过滤规则即可实现所需的限制
iptables -A OUTPUT -p tcp --dport 992 -d localhost \ -m owner ! --uid-owner root -j REJECT
让我们逐字段剖析该命令行
-A OUTPUT:告诉 iptables 我们想在链 OUTPUT 的末尾添加一个规则。由于 owner 匹配仅适用于本地发出的数据包,并且由于出站流量在 OUTPUT 链中处理,因此这是您可以使用的唯一链 owner 匹配。
-p tcp:告诉 iptables 仅匹配 TCP 数据包并加载 iptables 的 TCP 选项。
—dport 992:此 TCP 特定选项告诉 iptables 仅匹配目标端口为 992 的 TCP 数据包。
-d localhost:告诉 iptables 匹配目标为 localhost 的数据包(即,环回接口 127.0.0.1)。
-m owner:告诉 iptables 加载 owner 匹配扩展。
! --uid-owner root:告诉 iptables 仅匹配非 root 用户拥有的进程创建的数据包。
-j REJECT:告诉 iptables 拒绝满足此行中所有匹配表达式的数据包。
总而言之,此规则告诉内核(通过 iptables)丢弃发送到本地 TCP 端口 992 的数据包,除非它们是由 root 的进程之一发送的。
现在假设 crueller 具有更谨慎的默认 OUTPUT 策略 DROP 而不是 ACCEPT。在大多数 iptables 安装中,默认丢弃策略是首选;最小特权原则是信息安全中最重要的概念之一(即,“未明确允许的必须拒绝”)。
但是,现在我们需要更长的 OUTPUT 链。再次从空链开始,首先我们需要告诉 iptables 传递属于它已接受的会话的数据包
iptables -I OUTPUT 1 -m state --state \ ESTABLISHED,NEW -j ACCEPT
-state 匹配扩展为 iptables 提供了关键的状态跟踪能力,允许 iptables 评估与实际会话和数据流相关的数据包。除了这种智能本身的可取性之外,它还大大减少了您需要指定的规则数量,以便容纳单个事务。如果没有状态跟踪,您将需要两条规则而不是一条规则来允许,例如,出站 Telnet 事务;在 OUTPUT 和 INPUT 链中各一条。这就是为什么上述规则几乎总是应该在任何默认策略为 DROP 的链的顶部使用。
接下来,我们需要允许 Stunnel 本身连接到 strudel
stunnel -A OUTPUT -p tcp -dport 992 -d strudel \ -j ACCEPT
此命令将一个新规则附加到 OUTPUT 链的底部,该规则允许与 strudel 上的 TCP 端口 992 的出站连接。
最后,我们输入一个类似于默认接受示例中的命令,但是这个命令的目标是 ACCEPT 而不是 REJECT,并且在 --uid-owner 选项之前没有否定感叹号
iptables -A OUTPUT -p tcp --dport 992 -d localhost \ -m owner --uid-owner root -j ACCEPT
让我们看另一个例子。rsync 是一个强大的文件传输实用程序,可以执行差异文件传输。它可以将远程文件与本地副本进行比较,并仅下载不同的部分。rsync 可以与 SSH 或您猜到的 Stunnel 结合使用。
假设您在 crueller 上有一个 cron 作业,该作业使用 rsync 将 strudel 上的文件 stuff.txt 与本地副本进行比较,并下载任何差异。进一步假设 stuff.txt 包含一些敏感内容,因此您使用 Stunnel 来加密这些传输。但是只有属于“wheel”组的本地管理员需要控制脚本或使用隧道。
在 strudel 上,rsync 以守护程序模式运行,已被配置为共享一个名为 attic 的模块(虚拟卷)。假设 /etc/rsyncd.conf 配置正确(其具体细节超出了本文的范围),则以守护程序模式运行 rsync 的命令很简单
rsync --daemon
此外,strudel 还在 TCP 端口 273 上有一个 Stunnel 监听器,该监听器解密流量并将其转发到 rsync 进程(该进程本身正在 TCP 端口 873 上监听)。在这种情况下,在 strudel 上运行 Stunnel 的命令将是
stunnel -d 273 -r localhost:873 -p /etc/stunnel/ strudel.pem在 crueller 上,将像这样调用相应的客户端模式 Stunnel 监听器
stunnel -c -d 273 -r strudel:273好的,我们现在设置了一个隧道,通过该隧道,发送到 crueller 上 TCP 端口 273 的数据包将被加密并发送到 strudel 上的 TCP 端口 273,在那里它们将被解密并转发到 strudel 的本地 rsync 进程 TCP 873。
在没有 iptables 规则的情况下,如果 crueller 上的普通用户 plebian 尝试使用隧道,他或她将成功
rsync --port=273 -v localhost::attic/stuff.txt . stuff.txt wrote 508 bytes read 575 bytes 2166.00 bytes/sec total size is 48188 speedup is 44.49
除非,也就是说,我们在 crueller 上添加一个 iptables 规则,将 rsync 隧道的本地使用限制为 wheel 组的成员
iptables -A OUTPUT -p tcp -d localhost --dport 272 \ -m owner ! --gid-owner wheel -j REJECT现在,plebian 试图窃取新的 stuff.txt 文件的尝试将失败
rsync --port=273 -v localhost::attic/stuff.txt . rsync: failed to connect to localhost: Connection refused rsync error: error in socket IO (code 10) at clientserver.c(97)但是,如果 wheel 组成员 admin7 尝试连接,这将成功
rsync --port=272 -v localhost::chumly/stuff.txt . stuff.txt wrote 508 bytes read 575 bytes 2166.00 bytes/sec total size is 48188 speedup is 44.49希望您注意到这假定了默认允许策略。如果 OUTPUT 改为使用默认丢弃策略,我们将在 OUTPUT 链中需要一个规则,允许与 strudel 上的 TCP 273 的出站连接。OUTPUT 链还需要以允许已建立/相关会话规则开头。由于这两个规则都将与上一个示例中的规则非常相似,因此我不会在此处费心展示它们。
如您所见,--uid-owner 和 --gid-owner 的用途非常简单。我尚未提及的一件事是,这两个选项都接受名称(如我在示例中所示)或数字 ID。
我回避的另一个问题是 TCP Wrappers 样式的访问控制。在任何使用 TCP Wrappers 的系统上(或其 stunnel 二进制文件在编译时支持 libwrapper),您必须将适当的条目添加到 /etc/hosts.allow 中,Stunnel 才能正常工作,无论您是在该主机上以客户端模式还是守护程序模式运行 Stunnel。这是一件好事;与其说是又一件可能阻止 Stunnel 工作的事情,不如将其视为您安全洋葱的另一层。
最后,我将让您自己尝试使用 --pid-owner 和 --sid-owner。但是,我会给您一个提示。许多守护程序在启动时将其父 PID 写入可预测的位置,即 /var/run/sshd.pid。通过将此类 PID 文件读入 iptables 启动脚本中的变量,您可以匹配来自特定进程的数据包。祝你好运!
Mick Bauer (mick@visi.com) 是 Upstream Solutions, Inc. 的网络安全顾问,总部位于明尼苏达州明尼阿波利斯市。他是即将出版的 O'Reilly 图书《Building Secure Servers With Linux》的作者,“Network Engineering Polka”的作曲家以及一位自豪的家长(育有子女)。