Paranoid Penguin - 使用 Zorp 进行应用代理,第二部分
在我的上一篇文章中,我赞扬了应用层代理防火墙,并介绍了 Balazs Scheidler 的 Zorp 防火墙套件,该套件有商业版和免费版。本文继续上一篇文章的内容,讨论简单的内部-DMZ-外部场景的基本 Zorp 配置。我们将只配置几个服务,但这应该足以帮助未来的 Zorp 用户开始构建他们自己的智能防火墙系统。
回顾一下,应用层代理会中介而不是仅仅传递流经它们的流量。例如,当一个网络上的用户在代理防火墙的另一侧发起一个 HTTP 会话时,防火墙会拦截并断开连接,充当服务器(从客户端的角度来看)和客户端(从目标服务器的角度来看)。
Zorp 使用透明代理,这意味着 Zorp 防火墙后面的用户无需意识到防火墙的存在;他们可以针对外部地址和主机名,而无需配置他们的软件与代理通信。这是一个重要的缓解措施,可以应对代理本质上比其他类型的防火墙更复杂这一丑陋事实。使用 Zorp,所有复杂性都在后端,从而使最终用户更加满意。
但这并不意味着 Zorp 对其管理员来说也很痛苦。我将其复杂性评为高于 iptables,但低于 sendmail.cf。那么,事不宜迟,让我们配置一个 Zorp 防火墙吧。
本文假设,根据我的上一篇文章,您已成功修补了您的 Linux 2.4 内核和 iptables 二进制文件,以支持 TPROXY 模块(参见 www.balabit.com/products/oss/tproxy)。它还假设您已经编译和/或安装了 libzorpll、zorp 和 zorp-modules 的软件包;源代码和 deb 软件包可在 www.balabit.com/products/zorp_gpl 获取。我的示例进一步假设您正在运行 Zorp GPL 2.0 版本,尽管这些示例应同样适用于 Zorp Pro 2.0。Zorp Pro 有一些 Zorp GPL 未包含的代理模块,但两者共有的模块行为相同。
Zorp 支持每个防火墙多于三个接口,但现在最常见的防火墙架构是图 1 中所示的三宿主主机架构。这是我在此处介绍的架构。
同样,正如您在图 1 中所看到的,我们只有三个数据流:从 Internet 到 DMZ Web 服务器的 HTTP;从内部网络到 Internet 的 HTTP;以及从内部网络到 DMZ 的 HTTP 和 SSH。缺少诸如 IMAP、NNTP、FTP 和其他即使是简单设置也常用的服务。但是,如果您了解如何配置 Zorp 以适应这些服务,您应该能够弄清楚其他服务。但是,我确实讨论了 DNS 和 SMTP,即使我从图 1 中省略了它们。
我们需要做的第一件事并不直接涉及 Zorp,而是涉及 TPROXY 内核模块。在透明代理中,TPROXY 需要一个虚拟网络接口来绑定,以便在它将数据流分成两部分时使用。这需要是一个 IP 地址既不可通过 Internet 路由,也不与连接到防火墙的任何网络关联的接口。
Linux 2.4 内核默认编译时支持虚拟网络接口。您应该有一个,除非您有意编译了没有虚拟驱动程序支持的内核。如果是这样,请编译一个带有虚拟支持的新内核。因此,对于 TPROXY 的用途,您只需要显式配置 dummy0,使其具有不可路由且未使用的地址。在 Debian 中,您应该将以下行添加到 /etc/networking/interfaces
auto dummy0 iface dummy0 inet static address 1.2.3.4 netmask 255.255.255.255
其他发行版以不同的方式处理网络配置——Red Hat 和 SuSE 在 /etc/sysconfig/network 中使用 ifcfg- 文件——但希望您能理解。注意 32 位网络掩码:我重复一遍,此地址不得属于真实网络。
您可能想知道,这篇文章不是关于 Zorp 而不是 iptables 吗?是的,但是 Zorp 与 iptables 结合使用,而不是代替它。事实上,TPROXY 专门是一个 Netfilter 补丁。要使用 TPROXY,我们需要使用 iptables 命令对其进行配置,就像我们对 Netfilter 的其余部分所做的那样。(Netfilter 是 Linux 2.4 防火墙代码的正确名称——iptables 是其前端命令。)
此外,建议您在防火墙上以自包含代理的形式运行某些服务,即 DNS 和 SMTP。如果您这样做,您需要使用 iptables 配置您的防火墙以直接接受这些连接。例如,BIND v9 支持水平分割 DNS,其中外部客户端与内部客户端从不同的区域文件提供服务。同样,Postfix 很容易配置为代表内部主机充当relay,但在处理外部主机时严格作为本地传递程序。在防火墙上运行此类类似代理的服务是有意义的,只要您非常仔细地配置它们。
如果您不熟悉 Netfilter/iptables,那么以下内容可能没什么意义,而且空间不允许我详细解释所有内容。毕竟,Zorp 是一个高级工具。简而言之,我们使用 iptables 做的是让所有数据包通过一些针对欺骗 IP 地址的简单检查。然后,我们将拦截需要透明代理的数据包,并在自定义链中处理它们,而不是使用正常的 FORWARD 链。从技术上讲,没有任何东西被转发。最后,我们传递一些目标是防火墙本身的数据包。
Zorp Pro 包含一组统称为 iptables-utils 的脚本,这些脚本简化了 Zorp 的 iptables 管理。Zorp GPL 2.0 的免费版本 iptables-utils 可在 www.balabit.com/downloads/zorp/zorp-os/pool/i/iptables-utils 获取。我强烈推荐 iptables-utils,因为它使在实际提交新的 iptables 配置之前更容易对其进行测试。
由于它使用了一种我没有空间在此解释的语法,因此以下示例是一个传统的 iptables 启动脚本。以下是此类脚本最重要的部分。首先应该是 TPROXY 模块添加到 Netfilter 的特殊 tproxy 表的规则(清单 1)。这是我们为每个网络定义自定义代理链的地方:PRblue 用于从我们的内部网络发起的代理连接;PRpurple 用于从我们的 DMZ 发起的代理连接(在本场景中没有);以及 PRred 用于来自 Internet 的代理连接。
清单 1. TPROXY 规则
iptables -t tproxy -P PREROUTING ACCEPT iptables -t tproxy -A PREROUTING -i eth1 -j PRblue iptables -t tproxy -A PREROUTING -i eth2 -j PRpurple iptables -t tproxy -A PREROUTING -i eth0 -j PRred iptables -t tproxy -P OUTPUT ACCEPT iptables -t tproxy -N PRblue iptables -t tproxy -A PRblue -p tcp --dport 80 \ -j TPROXY --on-port 50080 iptables -t tproxy -A PRblue -p tcp --dport 22 \ ! -d firewall.example.net -j TPROXY --on-port 50022 iptables -t tproxy -N PRpurple iptables -t tproxy -N PRred iptables -t tproxy -A PRred -p tcp --dport 80 \ -j TPROXY --on-port 50080
清单 1 中有几点值得指出。首先,请注意 tproxy 表包含其自己的 PREROUTING 和 OUTPUT 输出链。在 Zorp 中,我们使用 tproxy/PREROUTING 链根据每个数据包进入的接口将数据包路由到适当的自定义代理链 (PRblue)。与任何自定义 iptables 链一样,如果数据包通过其中一个链而未匹配规则,则会将其发送回紧跟在将数据包发送到自定义链的规则之后的行。这就是为什么自定义链没有默认目标的原因。
在 PRblue 链中,我们有两个规则,每个规则对应一种允许从内部网络发起的事务类型。所有出站 HTTP 材料都被代理,即,交给侦听端口 50080 的代理进程。但是在 SSH 规则中,我们告诉 Netfilter 代理所有出站 SSH 流量,除非它目标是防火墙本身。虽然图 1 没有显示这样的数据流(Blue→SSH→firewall),但我们需要它来管理防火墙。此流还需要常规 filter 表的 INPUT 链中的规则。在本示例场景中,我们的 DMZ Web 服务器不允许发起任何连接,因此我们创建了一个 PRpurple 链,但实际上没有填充它。
现在我们继续讨论常规 filter 表,这是我们大多数人习惯处理的 Netfilter 表——当您省略带有 iptables 的 -t 选项时,它是默认表。清单 2 显示了我们示例防火墙的 filter 表的 INPUT 规则。
清单 2. Filter 表 INPUT 链
iptables -P INPUT DROP iptables -A INPUT -j noise iptables -A INPUT -j spoof iptables -A INPUT -m tproxy -j ACCEPT iptables -A INPUT -m state \ --state ESTABLISHED,RELATED -j ACCEPT iptables -A INPUT -i lo -j ACCEPT iptables -A INPUT -i eth1 -j LOblue iptables -A INPUT -i eth0 -j LOred iptables -A INPUT -i eth2 -j LOpurple iptables -A INPUT -j LOG --log-prefix "INPUT DROP: " iptables -A INPUT -j DROP
前几行根据一些自定义链检查数据包是否为欺骗 IP 地址;如果它们通过这些检查,它们将继续向下 INPUT 链。TPROXY 模块本身生成的数据包被接受,属于已建立的允许事务的数据包和环回数据包(分别为第 4-6 行)也是如此。接下来,与 tproxy 表的 PREROUTING 链一样,我们根据入口接口将数据包路由到自定义链。这一次,自定义链用于具有本地目标的数据包,而不是代理的数据包,因此我将它们命名为 LOblue 等等。接下来是我们的 filter 表的自定义链(清单 3)。
清单 3. Filter 表中的自定义链
iptables -N LOblue iptables -A LOblue -p tcp --dport 22 --syn -j ACCEPT iptables -A LOblue -p udp --dport 53 -j ACCEPT iptables -A LOblue -p tcp --dport 25 --syn -j ACCEPT iptables -A LOblue -j LOG --log-prefix "LOblue DROP: " iptables -A LOblue -j DROP iptables -N LOpurple iptables -A LOpurple -p udp --dport 53 -j ACCEPT iptables -A LOpurple -j LOG \ --log-prefix "LOpurple DROP: " iptables -A LOpurple -j DROP iptables -N LOred iptables -A LOred -p udp -s upstream.dns.server \ -sport 53 -j ACCEPT iptables -A LOred -p tcp --dport 25 --syn -j ACCEPT iptables -A LOred -j LOG --log-prefix "LOred DROP: " iptables -A LOred -j DROP iptables -N noise iptables -A noise -p udp --dport 137:139 -j DROP iptables -A noise -j RETURN iptables -N spoof iptables -A spoof -i lo -j RETURN iptables -A spoof ! -i lo -s 127.0.0.0/8 -j spoofdrop iptables -A spoof -i eth1 ! -s 10.0.1.0/24 \ -j spoofdrop iptables -A spoof ! -i eth1 -s 10.0.1.0/24 \ -j spoofdrop iptables -A spoof -i eth2 ! -s 192.168.1.0/24 \ -j spoofdrop iptables -A spoof ! -i eth2 -s 192.168.1.0/24 \ -j spoofdrop iptables -A spoof -j RETURN iptables -N spoofdrop iptables -A spoofdrop -j LOG \ --log-prefix "Spoofed packet: " iptables -A spoofdrop -j DROP
这三个自定义链中最重要的是:LOblue、LOpurple 和 LOred 告诉 Netfilter 如何处理目标是防火墙本身的数据包,基于数据包到达的接口。在 LOblue 中,我们接受 DNS 查询、SSH 连接和 SMTP 连接。在 LOpurple 中,我们只接受 DNS 查询。在 LOred 中,我们接受来自我们的 ISP 的 DNS 服务器 (upstream.dns.server) 的 DNS 答复和 SMTP 连接。最后三个自定义链是最简单的:噪声过滤器 NETBIOS 数据包,那些臭名昭著的 Linux 防火墙日志杂波;欺骗过滤器用于具有明显欺骗的(即不可能的)源 IP 地址的数据包;以及 spoofdrop 记录并丢弃被欺骗链捕获的数据包。
清单 4 显示了我们示例 iptables 脚本的其余部分,一个基本上为空的 FORWARD 链,具有默认 DROP 策略,以及一个空的 OUTPUT 链,具有默认 ACCEPT 链。同样,这是一个代理防火墙,因此它不会转发任何内容。您可能对防火墙发起的数据包的默认 ACCEPT 策略感到不安,但这在 Zorp 防火墙上既是必要的又是安全的。
清单 4. Filter 表的 FORWARD 和 OUTPUT 链
iptables -P FORWARD DROP iptables -A FORWARD -j LOG \ --log-prefix "FORWARD DROP: " iptables -A FORWARD -j DROP iptables -P OUTPUT ACCEPT
最后,我们来到了实际的 Zorp 配置文件。这些文件存储在 /etc/zorp 中,我们首先要处理的文件是 instances.conf,它定义和控制 Zorp 的实例。通常,经验法则是每个网络区域定义一个实例,因此在我们的示例场景中,您猜对了,我们的红色、紫色和蓝色区域各有 一个实例。清单 5 显示了这样的 instances.conf 文件会是什么样子。
清单 5. instances.conf
blue -v3 -p /etc/zorp/policy.py \ --autobind-ip 1.2.3.4 purple -v3 -p /etc/zorp/policy.py \ --autobind-ip 1.2.3.4 red -v3 -p /etc/zorp/policy.py \ --autobind-ip 1.2.3.4
每行中的第一个字段是实例的名称。这是用户可定义的,但我们需要在 Zorp 配置文件 policy.py 中逐字引用它。说到 policy.py,如果您愿意,您可以为每个实例使用单独的配置文件,或者您可以在单个文件中配置多个区域。无论如何,instances.conf 中的 -p 选项告诉 Zorp 每个实例使用哪个文件。
-v 参数设置日志消息详细程度:3 是中等设置,5 对于调试很有用。此参数仅控制 Zorp 生成的日志消息,对 Netfilter/iptables 日志记录没有任何影响。最后,每行都以 --autobind-ip 设置结尾,该设置确定 Zorp 在代理连接时应将 TPROXY 绑定到哪个虚拟 IP。此 IP 地址可以在所有实例之间共享,并且应该共享。显然,此地址应该是您之前设置的地址(请参阅上面的“配置虚拟接口”)。
您的 iptables 脚本确定数据包如何路由到代理,而 /etc/zorp/instances.conf 确定 Zorp 如何启动。但是要告诉 Zorp 的代理如何行为,您需要设置 /etc/zorp/policy.py,或者您在 instances.conf 中引用的任何配置文件——policy.py 是传统的,但不是强制性的。此策略文件包含两个部分。第一部分是全局部分,其中根据网络地址和允许的服务定义区域。第二部分是服务实例定义部分,其中根据每个实例中源自的服务以及这些服务映射到应用代理的方式来定义 instances.conf 中列出的每个实例。
清单 6 显示了我们示例 policy.py 中的完整全局部分。它以一些 import 部分开始,其中包含基本的 Python 函数。接下来是我们的区域定义。如果您设置 instances.conf 为每个区域运行一个 Zorp 实例,那么您这里的区域名称可以与您的实例名称相似甚至相同。在清单 6 中,我选择了不同的名称,以便说明从技术上讲,区域名称与实例名称不同。
清单 6. policy.py,第一部分(全局设置)
from Zorp.Core import * from Zorp.Plug import * from Zorp.Http import * InetZone("bluezone", "10.0.1.0/24", outbound_services=["blue_http", "blue_ssh"], InetZone("purplezone", "192.168.1.0/24", inbound_services=["blue_http", "blue_ssh", "red_http"]) InetZone("redzone", "0.0.0.0/0", outbound_services=["red_http"], inbound_services=["*"]) InetZone("localzone", "127.0.0.0/8", inbound_services=["*"]) # end global section
在每个区域定义中,您可以看到与图 1 中的网络地址相对应的网络地址,以及允许的服务的规范。这些服务名称是用户可定义的,并在后续的服务实例定义中充实。关于这些语句,需要理解的重要一点是, inbound 和 outbound 相对于区域/网络而言,而不是相对于防火墙而言。
图 2 显示了从内部到 Internet 的 HTTP 数据流作为代理连接的样子。在此图中,我们看到此数据流既作为出站连接存在于内部(蓝色)区域之外,又作为入站连接存在于 Internet(红色)区域之内。这在清单 6 中相应的 bluezone 和 redzone 定义中得到了证实。在给定的数据流遍历的两个区域定义中(图 2 和清单 6 中的 blue_http 的情况),使用相同的服务名称也很重要。
关于清单 6 的最后一点是 * 通配符表示所有已定义的服务。这比它看起来要窄;* 仅包括 policy.py 的服务实例定义中定义的那些服务,而不是所有可能的服务。请记住,Zorp 仅处理 Netfilter 和 TPROXY 发送给它的那些数据包。如果给定区域不允许任何出站或入站服务,则可以省略 inbound_services 或 outbound_services 参数,或者将其设置为 [](空括号)。
清单 7 显示了我们的 policy.py 文件的服务实例定义。每个定义的首行必须引用 instances.conf 中指定的实例名称,并且定义中的以下行必须缩进,因为这些规则由 Python 处理,Python 对缩进非常精确。定义不能为空:如果给定实例中没有源自的服务,则可以使用令牌 pass,如清单 7 中的 purple() 实例定义所示。
清单 7. policy.py,第二部分(实例定义)
def blue(): Service("blue_http", HttpProxy, router=TransparentRouter()) Service("blue_ssh", PlugProxy, router=TransparentRouter()) Listener(SockAddrInet('10.0.1.254', 50080), "blue_http") Listener(SockAddrInet('10.0.1.254', 50022), "blue_ssh") def purple(): pass def red(): Service("red_http", HttpProxy, router=DirectedRouter(SockAddrInet('192.168.1.242', 80), forge_addr=TRUE)) Listener(SockAddrInet('169.254.1.254', 50080), "red_http")
否则,定义应包含一个或多个 Service 行,指定一个或多个区域定义中引用的服务名称,以及一个 Zorp 代理模块,可以是全局 import 语句中包含的内置代理,也可以是在自定义类中定义的代理。Service 行中的最后一个字段是一个路由器,它指定代理的数据包应发送到哪里。您可以在清单 7 中看到,对于 red_http 服务,我们使用了 forge_addr=TRUE 选项来完整地将 Web 客户端的源 IP 从 Internet 传递到我们的 Web 服务器。如果没有此选项,所有到达 DMZ 的 Web 流量都将显示为源自防火墙本身。
虽然在清单 7 中我们仅使用 HttpProxy 和 PlugProxy(一个逐字复制应用程序数据的通用服务 UDP 和 TCP 代理),但 Zorp GPL 还具有用于 FTP、whois、SSL、telnet 和 finger 的代理。正如我之前提到的,您还可以创建自定义类来更改或增强这些代理。例如,很容易创建一个执行 URL 过滤的 HTTP 代理,或者一个堆叠在 HTTP 代理上的 SSL 代理,以便可以智能地代理 HTTPS 流量。不幸的是,这些都是我无法在此处涵盖的高级主题;幸运的是,Zorp 的所有 Python 代理模块都带有大量注释。
清单 7 中引用的 TransparentRouter 只是将数据包代理到客户端指定的目的地 IP 和端口。但是在红色实例的 red_http 服务中,我们看到可以改为指定 DirectedRouter,它需要强制性的目的地 IP 和端口。
服务实例定义中的每一行 Service 行都必须具有对应的 Listener 行。此行告诉 Zorp 服务应绑定到哪个本地(防火墙)IP 地址和端口。清单 7 的 Listener 语句中指定的端口是高端口似乎违反直觉:50080 而不是 80,50022 而不是 22。但请记住,每个代理都通过 Netfilter 从内核接收其数据包,而不是直接从客户端接收。因此,这些高端口必须与您的 tproxy 表 Netfilter 规则(清单 1)中指定的端口相对应。
我提到过,与 HttpProxy 不同,HttpProxy 是一个完全了解应用程序的代理,它强制执行所有相关的 Internet RFC 以实现正确的 HTTP 行为,PlugProxy 是一个通用服务代理 (GSP)。即使不具备应用程序智能,使用 PlugProxy 仍然比单独进行数据包过滤提供更好的保护,因为代理行为本身,即使没有应用程序智能,也可以将您的系统与 Netfilter 可能无法自行捕获的低级别攻击隔离开来。
资源
Balabit(Zorp 的创建者)的英语主页:www.balabit.com。
ZorpOS 的根下载目录包含一些使使用 Zorp GPL 更加容易的工具,包括 iptables-utils、启用 TPROXY 的 Linux 内核和 iptables 命令。事实上,这些是 Zorp Pro 附带的 Debian 发行版的免费部分,这就是为什么 ZorpOS 中的所有内容都采用 Debian 软件包的形式。如果您不是 Debian 用户,您想要的一切都在 pool 的子目录中;每个软件包的子目录顶部都是包含源代码的 tar.gz 文件。如果您是 Debian 用户,您可以将 URL 用作 apt-get 源:www.balabit.com/downloads/zorp/zorp-os。
Zorp 用户邮件列表是获取 Zorp 使用帮助(无论是 Pro 还是 GPL)的非常快速且简便的方法。此 URL 是订阅或浏览其档案的站点。请注意,Balabit 是一家匈牙利公司,其工程师(以及一些最有帮助的 Zorp 用户)在 CET (GMT+1) 时区工作:https://lists.balabit.hu/mailman/listinfo/zorp。
Mick Bauer,CISSP,是Linux Journal的安全编辑,也是明尼苏达州明尼阿波利斯的 IS 安全顾问。他是 Building Secure Servers With Linux(O'Reilly & Associates,2002 年)的作者。