第 33 章 防火墙

如果发现翻译错误,请直接 发起PR修改

33.1. 简介

防火墙使得过滤通过系统流动的进出流量成为可能。防火墙可以使用一个或多个“规则”集来检查进出网络连接的网络数据包,并允许流量通过或阻止它。防火墙的规则可以检查数据包的一个或多个特征,如协议类型、源或目标主机地址以及源或目标端口。

防火墙可以增强主机或网络的安全性。它们可以用于执行以下一项或多项功能:

  • 保护和隔离内部网络中的应用程序、服务和机器,以防止来自公共互联网的不必要的流量。

  • 限制或禁止内部网络主机访问公共互联网的服务。

  • 支持网络地址转换(NAT),允许内部网络使用私有 IP 地址,并使用单个 IP 地址或共享的自动分配的公共地址池与公共互联网建立单一连接。

FreeBSD 基本系统内置了三个防火墙:PF、IPFW 和 IPFILTER(也称为 IPF)。FreeBSD 还提供了两个流量整形器,用于控制带宽使用:altq(4)dummynet(4)。ALTQ 通常与 PF 紧密关联,而 dummynet 与 IPFW 关联。每个防火墙使用规则来控制数据包对 FreeBSD 系统的访问,尽管它们的实现方式不同,并且每个防火墙都有不同的规则语法。

FreeBSD 提供多个防火墙,以满足各种用户的不同需求和偏好。每个用户应该评估哪个防火墙最适合他们的需求。

阅读完本章后,您将了解:

  • 如何定义数据包过滤规则。

  • FreeBSD 内置防火墙的区别。

  • 如何使用和配置 PF 防火墙。

  • 如何使用和配置 IPFW 防火墙。

  • 如何使用和配置 IPFILTER 防火墙。

在阅读本章之前,你应该:

  • 了解基本的 FreeBSD 和互联网概念。

由于所有防火墙都是基于检查选定数据包控制字段的值,因此防火墙规则集的创建者必须了解 TCP/IP 的工作原理,数据包控制字段中不同值的含义,以及这些值在正常会话中的使用方式。对于一个很好的介绍,请参考 Daryl 的 TCP/IP 入门指南

33.2. 防火墙概念

一个规则集包含一组规则,根据数据包中包含的值来通过或阻止数据包。主机之间的双向数据包交换构成一个会话对话。防火墙规则集处理来自公共互联网的数据包以及系统作为对其响应而产生的数据包。每个 TCP/IP 服务都由其协议和监听端口预定义。目标为特定服务的数据包源自源地址使用非特权端口,并且目标地址上的特定服务端口。所有上述参数都可以用作选择条件来创建将通过或阻止服务的规则。

请查看此链接,了解 Trojans 使用的端口号

FTP 有两种模式:主动模式和被动模式。它们的区别在于数据通道的获取方式。被动模式更安全,因为数据通道是由原始的 FTP 会话请求者获取的。关于 FTP 和不同模式的详细解释,请参见 http://www.slacksite.com/other/ftp.html

防火墙规则集可以是“排他性(exclusive)”或“包容性(inclusive)”。排他性防火墙允许所有流量通过,除了与规则集匹配的流量。而包容性防火墙则相反,它只允许与规则匹配的流量通过,阻止其他所有流量。

一个包容性防火墙可以更好地控制出站流量,因此对于向公共互联网提供服务的系统来说是更好的选择。它还可以控制来自公共互联网的流量类型,以便访问私有网络。所有不符合规则的流量都会被阻止并记录。包容性防火墙通常比排他性防火墙更安全,因为它们显著降低了允许不需要的流量的风险。

除非另有说明,本章中的所有配置和示例规则集都创建包容性防火墙规则集。

可以使用“有状态防火墙(stateful firewall)”进一步加强安全性。这种类型的防火墙会跟踪打开的连接,并且只允许与现有连接匹配或打开新的允许连接的流量通过。

有状态过滤将流量视为会话中的双向数据包交换。当在匹配规则上指定状态时,防火墙会动态生成每个会话期间预期交换的数据包的内部规则。它具有足够的匹配能力来确定数据包是否适用于会话。任何不符合会话模板的数据包都会被自动拒绝。

当会话完成时,它将从动态状态表中移除。

有状态过滤允许我们专注于阻止/通过新的会话。如果新的会话被通过,所有后续的数据包将自动被允许通过,任何冒充的数据包将自动被拒绝。如果新的会话被阻止,它的所有后续数据包都将被禁止。有状态过滤提供了高级匹配能力,能够抵御攻击者使用的各种攻击方法。

NAT 代表网络地址转换。NAT 功能使得防火墙后面的私有局域网能够共享一个由 ISP 分配的 IP 地址,即使该地址是动态分配的。NAT 允许局域网中的每台计算机都能够访问互联网,而无需为多个互联网账户或 IP 地址向 ISP 付费。

NAT 会自动将局域网上每个系统的私有 LAN IP 地址转换为单个公共 IP 地址,以便在通过防火墙发送到公共互联网的数据包中使用。它还会对返回的数据包执行相反的转换。

根据 RFC 1918,以下 IP 地址范围被保留用于私有网络,这些地址将不会直接路由到公共互联网,因此可用于与 NAT 一起使用:

  • 10.0.0.0/8

  • 172.16.0.0/12

  • 192.168.0.0/16

在处理防火墙规则时,请非常小心。某些配置可能会将管理员锁在服务器外。为了安全起见,考虑从本地控制台执行初始防火墙配置,而不是通过 ssh 远程执行。

33.3. PF

自从 FreeBSD 5.3 版本以来,OpenBSD 的 PF 防火墙的移植版本已经作为基本系统的一部分包含在其中。PF 是一个完整的、功能齐全的防火墙,可选择性地支持 ALTQ(Alternate Queuing ),提供了服务质量(QoS)功能。

OpenBSD 项目在 PF FAQ 中维护了 PF 的权威参考。Peter Hansteen 在 http://home.nuug.no/\~ peter/pf/[http://home.nuug.no/~peter/pf/] 上维护了一个详尽的 PF 教程。

阅读 PF FAQ 时,请记住多年来,FreeBSD 的 PF 版本与上游的 OpenBSD 版本有很大的差异。在 FreeBSD 上,不是所有的功能都与 OpenBSD 上的工作方式相同,反之亦然。

FreeBSD packet filter mailing list 是一个很好的地方,可以询问有关配置和运行 PF 防火墙的问题。在提问之前,请先查看邮件列表的存档,因为可能已经有人回答过了。

本手册的这一部分重点介绍了与 FreeBSD 相关的 PF。它演示了如何启用 PF 和 ALTQ。还提供了在 FreeBSD 系统上创建规则集的几个示例。

33.3.1. 启用 PF

要使用 PF,首先必须加载其内核模块。本节描述了可以添加到 /etc/rc.conf 文件中以启用 PF 的条目。

首先,在 /etc/rc.conf 中添加 pf_enable=yes

# sysrc pf_enable=yes

当启动 PF 时,可以传递在 pfctl(8) 中描述的其他选项。在 /etc/rc.conf 中添加或更改此条目,并在两个引号("")之间指定任何所需的标志。

pf_flags=""                     # additional flags for pfctl startup

如果 PF 无法找到其规则集配置文件,则不会启动。默认情况下,FreeBSD 不提供规则集,并且没有 /etc/pf.conf 文件。示例规则集可以在 /usr/share/examples/pf/ 中找到。如果已经将自定义规则集保存在其他位置,请在 /etc/rc.conf 文件中添加一行,指定文件的完整路径:

pf_rules="/path/to/pf.conf"

PF 提供了日志记录支持,可以通过 pflog(4) 来查看。要启用日志记录支持,请将 pflog_enable=yes 添加到 /etc/rc.conf 文件中。

# sysrc pflog_enable=yes

还可以添加以下行来更改日志文件的默认位置,或者指定在启动 pflog(4) 时传递的任何其他标志:

pflog_logfile="/var/log/pflog"  # where pflogd should store the logfile
pflog_flags=""                  # additional flags for pflogd startup

最后,如果防火墙后面有一个局域网,并且需要转发局域网上的计算机的数据包,或者需要进行网络地址转换(NAT),请启用以下选项:

gateway_enable="YES"            # Enable as LAN gateway

保存所需的编辑后,可以通过输入以下命令启动带有日志支持的 PF:

# service pf start
# service pflog start

默认情况下,PF 从 /etc/pf.conf 读取其配置规则,并根据该文件中指定的规则或定义修改、丢弃或通过数据包。FreeBSD 安装包含位于 /usr/share/examples/pf/ 的几个示例文件。请参考 PF FAQ 以获取完整的 PF 规则集覆盖范围。

要控制 PF,请使用 pfctl 命令。有用的 pfctl 选项 总结了一些对该命令有用的选项。有关所有可用选项的描述,请参阅 pfctl(8)

表 1. 有用的 pfctl 选项
命令 目的

pfctl -e

启用 PF 。

pfctl -d

禁用 PF 。

pfctl -F all -f /etc/pf.conf

清除所有的 NAT、过滤、状态和表规则,并重新加载 /etc/pf.conf 文件。

pfctl -s [ rules | nat | states ]

关于过滤规则、 NAT 规则或状态表的报告。

pfctl -vnf /etc/pf.conf

检查 /etc/pf.conf 是否有错误,但不要加载规则集。

security/sudo 是用于运行需要提升权限的命令,例如 pfctl。它可以从 Ports Collection 安装。

要监视通过 PF 防火墙的流量,请考虑安装 sysutils/pftop 包或 port。安装完成后,可以运行 pftop 以查看类似于 top(1) 的格式的流量的实时快照。

33.3.2. PF 规则集

本节演示如何创建自定义规则集。它从最简单的规则集开始,并通过多个示例来构建其概念,以展示 PF 的许多功能在实际应用中的用法。

最简单的规则集适用于不运行任何服务且需要访问一个网络(可能是互联网)的单台机器。要创建这个最小的规则集,请编辑 /etc/pf.conf 文件,使其如下所示:

block in all
pass out all keep state

第一条规则默认拒绝所有传入流量。第二条规则允许由该系统创建的连接通过,并保留这些连接的状态信息。这些状态信息允许这些连接的返回流量通过,并且只应在可信任的机器上使用。可以使用以下命令加载规则集:

# pfctl -e ; pfctl -f /etc/pf.conf

除了保持状态,PF 还提供了可以在创建规则时定义和使用的 列表。宏可以包含列表,并且需要在使用之前进行定义。例如,在规则集的最顶部插入以下行:

tcp_services = "{ ssh, smtp, domain, www, pop3, auth, pop3s }"
udp_services = "{ domain }"

PF 不仅可以识别端口号,还可以识别端口名称,只要这些名称在 /etc/services 文件中列出。这个例子创建了两个宏。第一个宏是一个包含七个 TCP 端口名称的列表,第二个宏是一个 UDP 端口名称。一旦定义了宏,就可以在规则中使用它们。在这个例子中,除了由该系统发起的与七个指定的 TCP 服务和一个指定的 UDP 服务相关的连接之外,所有流量都被阻止。

tcp_services = "{ ssh, smtp, domain, www, pop3, auth, pop3s }"
udp_services = "{ domain }"
block all
pass out proto tcp to any port $tcp_services keep state
pass proto udp to any port $udp_services keep state

尽管 UDP 被认为是一种无状态协议,但 PF 能够跟踪一些状态信息。例如,当传递一个 UDP 请求来询问域名服务器的域名时,PF 会监视响应并将其传递回去。

每当对规则集进行编辑时,必须加载新的规则,以便可以使用它们:

# pfctl -f /etc/pf.conf

如果没有语法错误,pfctl 在加载规则时不会输出任何消息。在尝试加载规则之前,也可以对规则进行测试:

# pfctl -nf /etc/pf.conf

包括 -n 会导致规则仅被解释而不被加载。这提供了纠正任何错误的机会。在任何时候,只有最后一个有效的规则集被加载,直到 PF 被禁用或加载了一个新的规则集。

pfctl 规则集的验证或加载命令后添加 -v 参数,将会显示完全解析的规则,这些规则将会被加载。在调试规则时,这非常有用。

33.3.2.1. 一个带有 NAT 的简单网关

本节演示了如何配置运行 PF 的 FreeBSD 系统,使其充当至少一台其他计算机的网关。网关需要至少两个网络接口,每个接口连接到不同的网络。在本示例中,xl0 连接到互联网, xl1 连接到内部网络。

首先,启用网关以使机器将其接收到的网络流量从一个接口转发到另一个接口。这个 sysctl 设置将转发 IPv4 数据包:

# sysctl net.inet.ip.forwarding=1

要转发 IPv6 流量,请使用:

# sysctl net.inet6.ip6.forwarding=1

要在系统启动时启用这些设置,请使用 sysrc(8) 将它们添加到 /etc/rc.conf 文件中。

# sysrc gateway_enable=yes
# sysrc ipv6_gateway_enable=yes

使用 ifconfig 命令验证两个接口是否都已启动并运行。

接下来,创建 PF 规则以允许网关传递流量。虽然以下规则允许来自内部网络主机的有状态流量通过网关,但 to 关键字不能保证从源到目的地的完全通过:

pass in on xl1 from xl1:network to xl0:network port $ports keep state

该规则只允许流量通过内部接口进入网关。要让数据包继续传递,需要一个匹配的规则:

pass out on xl0 from xl1:network to xl0:network port $ports keep state

虽然这两个规则可以工作,但这样具体的规则很少需要。对于忙碌的网络管理员来说,一个可读的规则集是更安全的规则集。本节的其余部分演示了如何尽可能简化规则以提高可读性。例如,这两个规则可以用一条规则来替代:

pass from xl1:network to any port $ports keep state

可以使用宏来替代 interface:network 表示法,使规则集更易读。例如,可以定义一个 $localnet 宏,表示直接连接到内部接口的网络($xl1:network)。另外,也可以将 $localnet 的定义更改为 IP 地址/子网掩码 表示法来表示网络,例如 192.168.100.1/24 表示私有地址的子网。

如果需要的话,$localnet 甚至可以被定义为一个网络列表。无论具体需求如何,一个合理的 $localnet 定义可以在典型的通过规则中使用,如下所示:

pass from $localnet to any port $ports keep state

以下示例规则集允许内部网络上的机器发起的所有流量。它首先定义了两个宏,用于表示网关的外部和内部 3COM 接口。

对于拨号用户,外部接口将使用 tun0。对于 ADSL 连接,特别是使用以太网上的 PPP(PPPoE)的连接,正确的外部接口是 tun0,而不是物理以太网接口。

ext_if = "xl0"	# macro for external interface - use tun0 for PPPoE
int_if = "xl1"	# macro for internal interface
localnet = $int_if:network
# ext_if IP address could be dynamic, hence ($ext_if)
nat on $ext_if from $localnet to any -> ($ext_if)
block all
pass from { lo0, $localnet } to any keep state

这个规则集引入了 nat 规则,用于处理内部网络中不可路由的地址到分配给外部接口的 IP 地址的网络地址转换。nat 规则的最后部分周围的括号 ($ext_if) 是在外部接口的 IP 地址是动态分配时包含的。它确保即使外部 IP 地址发生变化,网络流量也能够正常运行而不会有严重的中断。

请注意,这个规则集可能允许更多的流量从网络中通过,而实际上并不需要这么多。一个合理的设置可以创建这个宏:

client_out = "{ ftp-data, ftp, ssh, domain, pop3, auth, nntp, http, \
    https, cvspserver, 2628, 5999, 8000, 8080 }"

用于主要通行规则中:

pass inet proto tcp from $localnet to any port $client_out \
    flags S/SA keep state

可能还需要一些其他的通行规则。这个规则允许在外部接口上使用 SSH :

pass in inet proto tcp to $ext_if port ssh

这个宏定义和规则允许内部客户端使用 DNS 和 NTP 服务。

udp_services = "{ domain, ntp }"
pass quick inet proto { tcp, udp } to any port $udp_services keep state

请注意这条规则中的 quick 关键字。由于规则集包含多个规则,因此了解规则集中规则之间的关系非常重要。规则按照从上到下的顺序进行评估,按照它们编写的顺序进行。对于每个由 PF 评估的数据包或连接,规则集中的 最后一个匹配的规则 是应用的规则。然而,当一个数据包匹配包含 quick 关键字的规则时,规则处理停止,数据包根据该规则进行处理。当需要对一般规则进行异常处理时,这非常有用。

33.3.2.2. 创建一个 FTP 代理

由于 FTP 协议的特性,配置工作中的 FTP 规则可能会出现问题。 FTP 在防火墙出现几十年之前就存在,并且在设计上存在不安全性。使用 FTP 的最常见问题包括:

  • 密码以明文形式传输。

  • 该协议要求使用至少两个 TCP 连接(控制和数据),并且这两个连接要使用不同的端口。

  • 当建立会话时,数据使用随机选择的端口进行通信。

在考虑客户端或服务器软件的潜在安全弱点之前,所有这些要点都提出了安全挑战。存在更安全的文件传输替代方案,例如 sftp(1)scp(1),它们都具有身份验证和加密连接上的数据传输功能。

对于需要使用 FTP 的情况,PF 提供了将 FTP 流量重定向到一个名为 ftp-proxy(8) 的小型代理程序,该程序包含在 FreeBSD 的基本系统中。代理的作用是使用一组锚点动态插入和删除规则,以正确处理 FTP 流量。

要启用 FTP 代理,请将以下行添加到 /etc/rc.conf 文件中:

ftpproxy_enable="YES"

然后通过运行以下命令启动代理:

# service ftp-proxy start

对于基本配置,需要将三个元素添加到 /etc/pf.conf 文件中。首先是代理将用于插入生成的 FTP 会话规则的锚点:

nat-anchor "ftp-proxy/*"
rdr-anchor "ftp-proxy/*"

其次,需要一个通过规则来允许 FTP 流量进入代理服务器。

第三,重定向和 NAT 规则需要在过滤规则之前定义。在 nat 规则之后立即插入以下 rdr 规则:

rdr pass on $int_if proto tcp from any to any port ftp -> 127.0.0.1 port 8021

最后,允许重定向的流量通过。

pass out proto tcp from $proxy to any port ftp

$proxy 是代理守护程序绑定的地址的展开形式。

保存 /etc/pf.conf 文件,加载新的规则,并从客户端验证 FTP 连接是否正常工作:

# pfctl -f /etc/pf.conf

这个例子涵盖了一个基本的设置,其中本地网络中的客户端需要联系其他地方的 FTP 服务器。这个基本配置应该适用于大多数 FTP 客户端和服务器的组合。如 ftp-proxy(8) 所示,通过在 ftpproxy_flags= 行中添加选项,可以改变代理的行为。一些客户端或服务器可能有特定的问题,在配置中必须进行补偿,或者可能需要以特定的方式集成代理,例如将 FTP 流量分配给特定的队列。

要以 PF 和 ftp-proxy(8) 保护的方式运行 FTP 服务器,可以配置一个独立的 ftp-proxy 以反向模式运行,使用 -R 选项,在一个独立的端口上,并具有自己的重定向通过规则。

33.3.2.3. 管理 ICMP

许多用于调试或故障排除 TCP/IP 网络的工具依赖于 Internet 控制消息协议(ICMP),该协议专门设计用于调试。

ICMP 协议在主机和网关之间发送和接收 控制消息(ontrol messages),主要是为了向发送方提供有关到目标主机的任何异常或困难条件的反馈。路由器使用 ICMP 来协商数据包大小和其他传输参数,这个过程通常被称为 路径 MTU 发现(path MTU discovery)

从防火墙的角度来看,一些 ICMP 控制消息容易受到已知的攻击向量的攻击。此外,无条件地允许所有诊断流量通过可以使调试更容易,但也会使他人更容易提取有关网络的信息。因此,以下规则可能不是最佳选择:

pass inet proto icmp from any to any

一种解决方案是允许本地网络中的所有 ICMP 流量通过,同时阻止来自网络外部的所有探测。

pass inet proto icmp from $localnet to any keep state
pass inet proto icmp from any to $ext_if keep state

还有其他可用的选项,这些选项展示了 PF 的一些灵活性。例如,可以指定 ping(8)traceroute(8) 使用的消息,而不是允许所有的 ICMP 消息。首先,定义一个用于该类型消息的宏:

icmp_types = "echoreq"

和一个使用宏的规则:

pass inet proto icmp all icmp-type $icmp_types keep state

如果需要其他类型的 ICMP 数据包,请将 icmp_types 扩展为这些数据包类型的列表。输入 more /usr/src/sbin/pfctl/pfctl_parser.c 以查看 PF 支持的 ICMP 消息类型列表。请参考 http://www.iana.org/assignments/icmp-parameters/icmp-parameters.xhtml 以了解每个消息类型的解释。

由于 Unix 的 traceroute 默认使用 UDP,因此需要添加另一条规则来允许 Unix 的 traceroute

# allow out the default range for traceroute(8):
pass out on $ext_if inet proto udp from any to any port 33433 >< 33626 keep state

由于 Microsoft Windows 系统上的 TRACERT.EXE 使用 ICMP 回显请求消息,因此只需要第一个规则来允许来自这些系统的网络跟踪。Unix 的 traceroute 也可以指示使用其他协议,并且如果使用了 -I 选项,它将使用 ICMP 回显请求消息。有关详细信息,请参阅 traceroute(8) 手册页。

33.3.2.3.1. 路径 MTU 发现

Internet 协议被设计为与设备无关,设备无关的一个结果是无法可靠地预测给定连接的最佳数据包大小。数据包大小的主要限制是 最大传输单元(MTU),它设置了接口的数据包大小的上限。输入 ifconfig 命令可以查看系统网络接口的 MTU 值。

TCP/IP 使用一种称为路径 MTU 发现的过程来确定连接的正确数据包大小。该过程发送大小不同的数据包,并设置“不分片(Do not fragment)”标志,期望在达到上限时收到一个 ICMP 返回数据包,其类型为“type 3, code 4”。Type 3 表示“目标不可达(destination unreachable)”,Code 4 表示“需要分片,但设置了不分片标志(fragmentation needed,but the do-not-fragment flag is set)”。为了支持与其他 MTU 的连接,可以将 destination unreachable 类型添加到 icmp_types 宏中,以允许路径 MTU 发现。

icmp_types = "{ echoreq, unreach }"

由于 pass 规则已经使用了该宏,所以不需要修改 pass 规则来支持新的 ICMP 类型。

pass inet proto icmp all icmp-type $icmp_types keep state

PF 允许对所有 ICMP 类型和代码进行过滤。可能的类型和代码的列表在 icmp(4)icmp6(4) 中有文档记录。

33.3.2.4. 使用表格

某些类型的数据在特定时间对过滤和重定向很重要,但它们的定义太长了,无法包含在规则集文件中。 PF 支持使用表格,这些表格是定义的列表,可以在不需要重新加载整个规则集的情况下进行操作,并且可以提供快速查找。表格名称始终用 < > 括起来,就像这样:

table <clients> { 192.168.2.0/24, !192.168.2.5 }

在这个例子中,192.168.2.0/24 网络是表的一部分,除了地址 192.168.2.5,它使用 ! 运算符被排除在外。还可以从文件中加载表,其中每个项目都在单独的行上,就像在这个例子中看到的那样 /etc/clients

192.168.2.0/24
!192.168.2.5

要引用该文件,请按照以下方式定义表格:

table <clients> persist file "/etc/clients"

一旦表被定义,就可以通过规则进行引用:

pass inet proto tcp from <clients> to any port $client_out flags S/SA keep state

可以使用 pfctl 实时操作表的内容。以下示例将另一个网络添加到表中:

# pfctl -t clients -T add 192.168.1.0/16

请注意,通过这种方式进行的任何更改将立即生效,这使它们非常适合测试,但在断电或重启后将不会保留。要使更改永久生效,可以修改规则集中表的定义或编辑表所引用的文件。可以使用 cron(8) 作业来维护表的磁盘副本,该作业定期将表的内容转储到磁盘上,使用类似 pfctl -t clients -T show >/etc/clients 的命令。或者,也可以使用 /etc/clients 更新内存中的表内容:

# pfctl -t clients -T replace -f /etc/clients

33.3.2.5. 使用过载表保护 SSH

那些在外部接口上运行 SSH 的人可能在身份验证日志中看到类似于以下内容的信息:

Sep 26 03:12:34 skapet sshd[25771]: Failed password for root from 200.72.41.31 port 40992 ssh2
Sep 26 03:12:34 skapet sshd[5279]: Failed password for root from 200.72.41.31 port 40992 ssh2
Sep 26 03:12:35 skapet sshd[5279]: Received disconnect from 200.72.41.31: 11: Bye Bye
Sep 26 03:12:44 skapet sshd[29635]: Invalid user admin from 200.72.41.31
Sep 26 03:12:44 skapet sshd[24703]: input_userauth_request: invalid user admin
Sep 26 03:12:44 skapet sshd[24703]: Failed password for invalid user admin from 200.72.41.31 port 41484 ssh2

这表明正在进行一次暴力攻击,某人或某个程序正在尝试发现能够让他们进入系统的用户名和密码。

如果需要合法用户进行外部 SSH 访问,更改 SSH 使用的默认端口可以提供一定的保护。然而,PF 提供了一种更优雅的解决方案。通过 Pass 规则,可以限制连接主机的操作,并将违规者放入被拒绝某些或全部访问的地址表中。甚至可以断开超出限制的机器的所有现有连接。

要进行配置,请在规则集的表格部分创建此表格。

table <bruteforce> persist

然后,在规则集的早期位置添加规则,以阻止暴力访问,同时允许合法访问:

block quick from <bruteforce>
pass inet proto tcp from any to $localnet port $tcp_services \
    flags S/SA keep state \
    (max-src-conn 100, max-src-conn-rate 15/5, \
    overload <bruteforce> flush global)

括号中的部分定义了限制条件,数字应根据当地要求进行更改。可以按以下方式阅读:

max-src-conn 是指允许来自一个主机的同时连接数。

max-src-conn-rate 是每秒钟(5)允许来自任何单个主机的新连接的速率(15)。

overload <bruteforce> 的意思是任何超过这些限制的主机都会将其地址添加到 bruteforce 表中。规则集会阻止来自 bruteforce 表中地址的所有流量。

最后,flush global 表示当一个主机达到限制时,该主机的所有连接 (global) 将被终止 (flush) 。

这些规则将 不会 阻止慢速暴力破解者,如 http://home.nuug.no/~peter/hailmary2013/ 中所描述的。

这个示例规则集主要是为了说明。例如,如果希望有大量的连接,但对于 ssh 的要求更加严格,可以在规则集的早期添加以下类似的规则来补充上面的规则:

pass quick proto { tcp, udp } from any to any port ssh \
    flags S/SA keep state \
    (max-src-conn 15, max-src-conn-rate 5/3, \
    overload <bruteforce> flush global)

不一定需要阻止所有超载器:

值得注意的是,过载机制是一种通用技术,不仅适用于 SSH,并且完全阻止来自违规者的所有流量并不总是最优的选择。

例如,可以使用过载规则来保护邮件服务或网络服务,并且可以在规则中使用过载表来将违规者分配到带有最小带宽分配的队列中,或者重定向到特定的网页。

随着时间的推移,表格将被过载规则填满,其大小将逐渐增长,占用更多的内存。有时,被阻止的 IP 地址是一个动态分配的地址,后来被分配给了一个有合法理由与本地网络中的主机通信的主机。

对于这种情况,pfctl 提供了过期表项的功能。例如,以下命令将删除在 86400 秒内没有被引用的 <bruteforce> 表项:

# pfctl -t bruteforce -T expire 86400

类似的功能由 security/expiretable 提供,该包会删除在指定时间内未被访问的表条目。

安装完成后,可以运行 expiretable 来删除指定时限之前的 <bruteforce> 表条目。以下示例将删除所有 24 小时之前的条目:

/usr/local/sbin/expiretable -v -d -t 24h bruteforce

33.3.2.6. 防止垃圾邮件的保护措施

不要将其与 spamassassin 捆绑的 spamd 守护程序混淆,mail/spamd 可以通过配置 PF 来提供对垃圾邮件的外部防御。这个 spamd 通过一组重定向钩入 PF 配置。

垃圾邮件发送者往往发送大量的消息,垃圾邮件主要来自一些友好的垃圾邮件网络和大量被劫持的机器,这些都会被迅速报告给封锁列表。

当接收到来自黑名单中地址的 SMTP 连接时,spamd 会呈现其横幅,并立即切换到一种模式,以逐字节地回答 SMTP 流量。这种技术旨在尽可能浪费垃圾邮件发送者的时间,被称为“蜿蜒(tarpitting)”。具体实现使用逐字节 SMTP 回复的方法通常被称为“口吃(stuttering)”。

这个示例演示了使用自动更新的屏蔽列表设置 spamd 的基本步骤。有关更多信息,请参考安装在 mail/spamd 中的 man 页面。

配置 spamd 过程
  1. 安装 mail/spamd 包或 port。要使用 spamd 的灰名单功能,必须在 /dev/fd 上挂载 fdescfs(5)。将以下行添加到 /etc/fstab

     fdescfs /dev/fd fdescfs rw 0 0

    然后,挂载文件系统:

    #  mount fdescfs
  2. 接下来,编辑 PF 规则集以包含以下内容:

    table <spamd> persist
    table <spamd-white> persist
    rdr pass on $ext_if inet proto tcp from <spamd> to \
        { $ext_if, $localnet } port smtp -> 127.0.0.1 port 8025
    rdr pass on $ext_if inet proto tcp from !<spamd-white> to \
        { $ext_if, $localnet } port smtp -> 127.0.0.1 port 8025

    两个表 <spamd><spamd-white> 是必需的。来自在 <spamd> 中列出但不在 <spamd-white> 中列出的地址的 SMTP 流量将被重定向到监听在端口 8025 的 spamd 守护进程。

  3. 下一步是在 /usr/local/etc/spamd.conf 中配置 spamd,并添加一些 rc.conf 参数。

    mail/spamd 安装包括一个示例配置文件(/usr/local/etc/spamd.conf.sample)和一个用于 spamd.conf 的 man 页面。请参考这些内容以获取超出本示例所示配置选项的其他配置选项。

    配置文件中第一行不以 # 注释符号开头的行包含了定义 all 列表的块,该列表指定要使用的列表:

    all:\
        :traplist:allowlist:

    此条目添加了所需的阻止列表,用冒号(:)分隔。要使用允许列表从阻止列表中减去地址,请在该阻止列表的名称之后 立即 添加允许列表的名称。例如::blocklist:allowlist:

    接下来是指定块列表的定义:

    traplist:\
        :black:\
        :msg="SPAM. Your address %A has sent spam within the last 24 hours":\
        :method=http:\
        :file=www.openbsd.org/spamd/traplist.gz

    第一行是块列表的名称,第二行指定了列表类型。msg 字段包含在 SMTP 对话期间显示给被列入块列表的发件人的消息。method 字段指定了 spamd-setup 获取列表数据的方式;支持的方法有 httpftp、从挂载文件系统中的 file 文件和通过执行外部程序的 exec。最后,file 字段指定了 spamd 期望接收的文件的名称。

    指定允许列表的定义类似,但省略了 msg 字段,因为不需要消息。

    allowlist:\
        :white:\
        :method=file:\
        :file=/var/mail/allowlist.txt

    谨慎选择数据源:

    使用样本中的所有屏蔽列表 spamd.conf 将会屏蔽大量的互联网区块。管理员需要编辑该文件,以创建一个最佳配置,使用适用的数据源,并在必要时使用自定义列表。

    接下来,将此条目添加到 /etc/rc.conf 文件中。其他标志在由注释指定的 man 页面中有描述。

    spamd_flags="-v" # use "" and see spamd-setup(8) for flags

    完成后,重新加载规则集,通过输入 service obspamd start 启动 spamd,并使用 spamd-setup 完成配置。最后,创建一个 cron(8) 任务,定期调用 spamd-setup 来更新表格。

在邮件服务器前的典型网关上,主机很快就会在几秒钟到几分钟内被困住。

PF 还支持 灰名单(greylisting),它会暂时拒绝来自未知主机的带有 45n 代码的消息。在合理的时间内再次尝试的灰名单主机的消息将被放行。符合 RFC 1123 和 RFC 2821 规定的发件人的流量将立即被放行。

关于灰名单技术的更多信息可以在 greylisting.org 网站上找到。灰名单最令人惊讶的事情之一,除了它的简单性,就是它仍然有效。垃圾邮件发送者和恶意软件编写者一直非常慢地适应绕过这种技术。

配置灰名单的基本步骤如下:

配置灰名单过程
  1. 确保按照上一个步骤的说明,将 fdescfs(5) 挂载起来。

  2. 要以灰名单模式运行 spamd,请将以下行添加到 /etc/rc.conf 文件中:

    spamd_grey="YES"  # use spamd greylisting if YES

    请参考 spamd 手册,了解其他相关参数的描述。

  3. 完成灰名单设置的步骤:

    #  service obspamd restart
    #  service obspamlogd start

在幕后,spamdb 数据库工具和 spamlogd 白名单更新程序为灰名单功能执行重要功能。spamdb 是管理员通过 /var/db/spamdb 数据库的内容来管理阻止、灰名单和允许名单的主要界面。

33.3.2.7. 网络维护

本节介绍了如何使用 block-policyscrubantispoof 来使规则集的行为正常。

block-policy 是一个选项,可以在规则集的 options 部分中设置,该部分位于重定向和过滤规则之前。该选项确定了 PF 在规则阻止主机时发送的反馈信息(如果有的话)。该选项有两个可能的值:drop 会丢弃被阻止的数据包而不提供反馈,return 会返回一个状态码,例如 Connection refused

如果未设置,默认策略为 drop。要更改 block-policy,请指定所需的值:

set block-policy return

在 PF 中,scrub 是一个关键字,它可以启用网络数据包的规范化。该过程会重新组装分段的数据包,并且丢弃具有无效标志组合的 TCP 数据包。启用 scrub 可以提供一定程度的保护,防止基于数据包片段处理不正确的某些攻击。有许多选项可供选择,但最简单的形式适用于大多数配置:

scrub in all

一些服务,如 NFS,需要特定的片段处理选项。有关更多信息,请参阅 https://home.nuug.no/~peter/pf/en/scrub.html

这个例子重新组装片段,清除“不分段(do not fragment)”位,并将最大分段大小设置为 1440 字节:

scrub in all fragment reassemble no-df max-mss 1440

antispoof 机制通过阻止出现在逻辑上不可能的接口和方向上的数据包,主要保护免受伪造或伪装的 IP 地址的活动的影响。

这些规则可以过滤掉来自世界其他地方的伪造流量,以及源自本地网络的任何伪造数据包。

antispoof for $ext_if
antispoof for $int_if

33.3.2.8. 处理非可路由地址

即使有一个正确配置的网关来处理网络地址转换,人们可能仍然需要弥补其他人的错误配置。常见的错误配置是允许非可路由地址的流量进入互联网。由于来自非可路由地址的流量可能参与多种拒绝服务攻击技术,建议通过外部接口明确地阻止非可路由地址的流量进入网络。

在这个例子中,定义了一个包含非可路由地址的宏,然后在阻止规则中使用。对这些地址的流量在网关的外部接口上被静默丢弃。

martians = "{ 127.0.0.0/8, 192.168.0.0/16, 172.16.0.0/12, \
	      10.0.0.0/8, 169.254.0.0/16, 192.0.2.0/24, \
	      0.0.0.0/8, 240.0.0.0/4 }"

block drop in quick on $ext_if from $martians to any
block drop out quick on $ext_if from any to $martians

33.3.3. 启用 ALTQ

在 FreeBSD 上,可以使用 ALTQ 与 PF 一起提供服务质量(QoS)。一旦启用 ALTQ,可以在规则集中定义队列,确定出站数据包的处理优先级。

在启用 ALTQ 之前,请参考 altq(4) 以确定系统上安装的网络卡驱动程序是否支持它。

ALTQ 不可用作可加载的内核模块。如果系统的接口支持 ALTQ,请按照 配置 FreeBSD 内核 中的说明创建自定义内核。以下内核选项可用。第一个选项用于启用 ALTQ。其他选项中至少需要一个来指定队列调度算法:

options         ALTQ
options         ALTQ_CBQ        # Class Based Queuing (CBQ)
options         ALTQ_RED        # Random Early Detection (RED)
options         ALTQ_RIO        # RED In/Out
options         ALTQ_HFSC       # Hierarchical Packet Scheduler (HFSC)
options         ALTQ_PRIQ       # Priority Queuing (PRIQ)

以下调度算法可用:

CBQ

基于类的排队(CBQ)用于将连接的带宽划分为不同的类别或队列,以根据过滤规则对流量进行优先级排序。

RED

随机早期检测(RED)用于通过测量队列的长度并将其与队列的最小和最大阈值进行比较来避免网络拥塞。当队列超过最大阈值时,所有新的数据包将被随机丢弃。

RIO

在随机早期检测输入输出(RIO)模式中,RED 维护多个平均队列长度和多个阈值,每个 QOS 级别对应一个阈值。

HFSC

层次公平服务曲线分组调度器(HFSC)的描述可以在 http://www-2.cs.cmu.edu/hzhang/HFSC/main.html[http://www-2.cs.cmu.edu/hzhang/HFSC/main.html] 找到。

PRIQ

优先队列(PRIQ)始终首先传递位于较高队列中的流量。

有关调度算法和示例规则集的更多信息,请访问 OpenBSD 的网络存档: https://web.archive.org/web/20151109213426/http://www.openbsd.org/faq/pf/queueing.html

33.4. IPFW

IPFW 是为 FreeBSD 编写的一种有状态防火墙,支持 IPv4 和 IPv6。它由几个组件组成:内核防火墙过滤规则处理器及其集成的数据包计数功能、日志记录功能、 NAT、dummynet(4) 流量整形器、转发功能、桥接功能和 ipstealth 功能。

FreeBSD 在 /etc/rc.firewall 中提供了一个示例规则集,该规则集定义了几种常见场景的防火墙类型,以帮助新手用户生成适当的规则集。IPFW 提供了一个强大的语法,高级用户可以使用它来创建符合特定环境安全要求的自定义规则集。

本节介绍了如何启用 IPFW,提供了其规则语法的概述,并演示了几个常见配置场景的规则集。

33.4.1. 启用 IPFW

IPFW 已经作为一个可加载的内核模块包含在基本的 FreeBSD 安装中,这意味着不需要自定义内核来启用 IPFW。

对于那些希望将 IPFW 支持静态编译到自定义内核中的用户,请参阅 IPFW 内核选项

要在启动时配置系统以启用 IPFW ,请将 firewall_enable ="YES" 添加到 /etc/rc.conf 文件中。

# sysrc firewall_enable="YES"

要使用 FreeBSD 提供的默认防火墙类型之一,请添加另一行来指定类型:

# sysrc firewall_type="open"

可用的类型有:

  • open:允许通过所有流量。

  • client: 仅保护本机。

  • simple:保护整个网络。

  • closed:完全禁用 IP 流量,除了回环接口之外。

  • workstation:使用有状态规则仅保护此计算机。

  • UNKNOWN:禁用防火墙规则的加载。

  • filename: 包含防火墙规则集的文件的完整路径。

如果 firewall_type 被设置为 clientsimple ,请修改 /etc/rc.firewall 中的默认规则以适应系统的配置。

请注意,filename 类型用于加载自定义规则集。

加载自定义规则集的另一种方法是将 firewall_script 变量设置为包含 IPFW 命令的可执行脚本的绝对路径。本节中使用的示例假设 firewall_script 设置为 /etc/ipfw.rules

# sysrc firewall_script="/etc/ipfw.rules"

要通过 syslogd(8) 启用日志记录,请包含以下行:

# sysrc firewall_logging="YES"

只有带有 log 选项的防火墙规则才会被记录。默认规则不包含此选项,必须手动添加。因此,建议编辑默认规则集以进行日志记录。此外,如果日志存储在单独的文件中,可能需要进行日志轮转。

没有设置日志限制的 /etc/rc.conf 变量。要限制每个连接尝试中规则被记录的次数,请在 /etc/sysctl.conf 中使用以下行指定数字:

# echo "net.inet.ip.fw.verbose_limit=5" >> /etc/sysctl.conf

要通过名为 ipfw0 的专用接口启用日志记录,请将以下行添加到 /etc/rc.conf 中:

# sysrc firewall_logif="YES"

然后使用 tcpdump 查看被记录的内容:

# tcpdump -t -n -i ipfw0

只有在附加了 tcpdump 时,才会出现由于日志记录而产生的额外开销。

保存所需的编辑后,启动防火墙。要立即启用日志记录限制,还需设置上述 sysctl 值。

# service ipfw start
# sysctl net.inet.ip.fw.verbose_limit=5

33.4.2. IPFW 规则语法

当一个数据包进入 IPFW 防火墙时,它会与规则集中的第一条规则进行比较,并按顺序逐条进行,从上到下移动。当数据包与规则的选择参数匹配时,执行规则的动作,并终止该数据包的规则集搜索。这被称为“先中者胜(first match wins)”。如果数据包不匹配任何规则,则被强制 IPFW 默认规则号 65535 捕获,该规则拒绝所有数据包并将其静默丢弃。然而,如果数据包与包含 countskiptotee 关键字的规则匹配,搜索将继续进行。有关这些关键字如何影响规则处理的详细信息,请参阅 ipfw(8)

创建 IPFW 规则时,关键字必须按照以下顺序编写。某些关键字是必需的,而其他关键字是可选的。大写字母表示变量,小写字母表示必须在其后出现的变量。 # 符号用于标记注释的开始,可以出现在规则的末尾或单独的一行上。空行将被忽略。

CMD RULE_NUMBER set SET_NUMBER ACTION log LOG_AMOUNT PROTO from SRC SRC_PORT to DST DST_PORT OPTIONS

本节提供了这些关键字及其选项的概述。这不是一个详尽无遗的选项列表。有关创建 IPFW 规则时可用的规则语法的完整描述,请参阅 ipfw(8)

CMD

每条规则都必须以 ipfw add 开头。

RULE_NUMBER

每个规则都与一个从 165534 的数字相关联。该数字用于指示规则处理的顺序。多个规则可以具有相同的数字,这种情况下它们按照添加的顺序应用。

SET_NUMBER

每个规则与一个从 031 的集合编号相关联。可以单独禁用或启用集合,从而可以快速添加或删除一组规则。如果未指定 SET_NUMBER ,则规则将被添加到集合 0 中。

动作(ACTION)

规则可以与以下动作之一关联。当数据包与规则的选择条件匹配时,将执行指定的动作。

allow | accept | pass | permit:这些关键字是等效的,允许匹配规则的数据包通过。

check-state:检查数据包是否与动态状态表匹配。如果找到匹配项,则执行与生成此动态规则相关联的操作,否则转到下一条规则。 check-state 规则没有选择条件。如果规则集中没有 check-state 规则,则在第一个 keep-statelimit 规则处检查动态规则表。

count:更新与规则匹配的所有数据包的计数器。搜索将继续进行下一个规则。

deny | drop:这两个词都表示静默丢弃与此规则匹配的数据包。

还有其他可用的操作。有关详细信息,请参阅 ipfw(8)

LOG_AMOUNT

当一个数据包与带有 log 关键字的规则匹配时,将会向 syslogd(8) 记录一条消息,该消息的设施名称为 SECURITY。只有在特定规则的记录的数据包数量不超过指定的 LOG_AMOUNT 时才会发生记录。如果没有指定 LOG_AMOUNT,则限制值将从 net.inet.ip.fw.verbose_limit 的值中获取。值为零将移除记录限制。一旦达到限制,可以通过清除该规则的记录计数器或数据包计数器来重新启用记录,使用 ipfw resetlog 命令。

在满足所有其他数据包匹配条件之后,进行日志记录,并在对数据包执行最终操作之前。管理员决定在哪些规则上启用日志记录。

协议(PROTO)

这个可选值可以用来指定在 /etc/protocols 中找到的任何协议名称或编号。

源地址(SRC)

from 关键字后面必须跟着源地址或表示源地址的关键字。地址可以用 anyme(在此系统上配置的任何接口的地址)、me6(在此系统上配置的任何 IPv6 地址)或 table 后跟包含地址列表的查找表的编号来表示。在指定 IP 地址时,可以选择性地跟着 CIDR 掩码或子网掩码。例如,1.2.3.4/251.2.3.4:255.255.255.128

源端口(SRC_PORT)

可以使用端口号或者从 /etc/services 中的名称来指定可选的源端口。

目标地址(DST)

to 关键字后面必须跟着目标地址或代表目标地址的关键字。在 SRC 部分描述的相同关键字和地址可以用来描述目标。

目标端口(DST_PORT)

可以使用端口号或者从 /etc/services 文件中的名称来指定可选的目标端口。

选项(OPTIONS)

源和目的地后面可以跟随几个关键字。顾名思义,OPTIONS 是可选的。常用的选项包括 inout,用于指定数据包流动的方向,icmptypes 后面跟随 ICMP 消息的类型,以及 keep-state

当匹配到 keep-state 规则时,防火墙将创建一个动态规则,该规则匹配源地址和目标地址以及端口之间的双向流量,使用相同的协议。

动态规则功能容易受到 SYN 洪水攻击的资源耗尽问题的影响,该攻击会打开大量的动态规则。为了使用 IPFW 来对抗这种类型的攻击,可以使用 limit 选项。该选项通过检查打开的动态规则,计算该规则和 IP 地址组合出现的次数,来限制同时会话的数量。如果该计数大于 limit 指定的值,数据包将被丢弃。

有数十种选项可供选择。有关每个可用选项的描述,请参阅 ipfw(8)

33.4.3. 示例规则集

本节演示了如何创建一个名为 /etc/ipfw.rules 的示例有状态防火墙规则脚本。在这个示例中,所有连接规则使用 inout 来明确方向。它们还使用 via interface-name 来指定数据包所经过的接口。

在首次创建或测试防火墙规则集时,考虑临时设置此可调整项:

net.inet.ip.fw.default_to_accept="1"

这将将 ipfw(8) 的默认策略设置为比默认的 deny ip from any to any 更宽松,这样在重新启动后更难被系统锁定。

防火墙脚本首先指示它是一个 Bourne shell 脚本,并清除任何现有的规则。然后它创建 cmd 变量,这样在每个规则的开头就不需要输入 ipfw add 。它还定义了 pif 变量,表示连接到互联网的接口的名称。

#!/bin/sh
# Flush out the list before we begin.
ipfw -q -f flush

# Set rules command prefix
cmd="ipfw -q add"
pif="dc0"     # interface name of NIC attached to Internet

前两条规则允许在可信的内部接口和回环接口上的所有流量通过。

# Change xl0 to LAN NIC interface name
$cmd 00005 allow all from any to any via xl0

# No restrictions on Loopback Interface
$cmd 00010 allow all from any to any via lo0

下一个规则允许数据包通过,如果它与动态规则表中的现有条目匹配:

$cmd 00101 check-state

下面的规则集定义了内部系统可以与互联网上的主机建立哪些有状态连接:

# Allow access to public DNS
# Replace x.x.x.x with the IP address of a public DNS server
# and repeat for each DNS server in /etc/resolv.conf
$cmd 00110 allow tcp from any to x.x.x.x 53 out via $pif setup keep-state
$cmd 00111 allow udp from any to x.x.x.x 53 out via $pif keep-state

# Allow access to ISP's DHCP server for cable/DSL configurations.
# Use the first rule and check log for IP address.
# Then, uncomment the second rule, input the IP address, and delete the first rule
$cmd 00120 allow log udp from any to any 67 out via $pif keep-state
#$cmd 00120 allow udp from any to x.x.x.x 67 out via $pif keep-state

# Allow outbound HTTP and HTTPS connections
$cmd 00200 allow tcp from any to any 80 out via $pif setup keep-state
$cmd 00220 allow tcp from any to any 443 out via $pif setup keep-state

# Allow outbound email connections
$cmd 00230 allow tcp from any to any 25 out via $pif setup keep-state
$cmd 00231 allow tcp from any to any 110 out via $pif setup keep-state

# Allow outbound ping
$cmd 00250 allow icmp from any to any out via $pif keep-state

# Allow outbound NTP
$cmd 00260 allow udp from any to any 123 out via $pif keep-state

# Allow outbound SSH
$cmd 00280 allow tcp from any to any 22 out via $pif setup keep-state

# deny and log all other outbound connections
$cmd 00299 deny log all from any to any out via $pif

下一组规则控制来自互联网主机到内部网络的连接。它首先拒绝通常与攻击相关的数据包,然后明确允许特定类型的连接。所有从互联网发起的授权服务都使用 limit 来防止洪水攻击。

# Deny all inbound traffic from non-routable reserved address spaces
$cmd 00300 deny all from 192.168.0.0/16 to any in via $pif     #RFC 1918 private IP
$cmd 00301 deny all from 172.16.0.0/12 to any in via $pif      #RFC 1918 private IP
$cmd 00302 deny all from 10.0.0.0/8 to any in via $pif         #RFC 1918 private IP
$cmd 00303 deny all from 127.0.0.0/8 to any in via $pif        #loopback
$cmd 00304 deny all from 0.0.0.0/8 to any in via $pif          #loopback
$cmd 00305 deny all from 169.254.0.0/16 to any in via $pif     #DHCP auto-config
$cmd 00306 deny all from 192.0.2.0/24 to any in via $pif       #reserved for docs
$cmd 00307 deny all from 204.152.64.0/23 to any in via $pif    #Sun cluster interconnect
$cmd 00308 deny all from 224.0.0.0/3 to any in via $pif        #Class D & E multicast

# Deny public pings
$cmd 00310 deny icmp from any to any in via $pif

# Deny ident
$cmd 00315 deny tcp from any to any 113 in via $pif

# Deny all Netbios services.
$cmd 00320 deny tcp from any to any 137 in via $pif
$cmd 00321 deny tcp from any to any 138 in via $pif
$cmd 00322 deny tcp from any to any 139 in via $pif
$cmd 00323 deny tcp from any to any 81 in via $pif

# Deny fragments
$cmd 00330 deny all from any to any frag in via $pif

# Deny ACK packets that did not match the dynamic rule table
$cmd 00332 deny tcp from any to any established in via $pif

# Allow traffic from ISP's DHCP server.
# Replace x.x.x.x with the same IP address used in rule 00120.
#$cmd 00360 allow udp from any to x.x.x.x 67 in via $pif keep-state

# Allow HTTP connections to internal web server
$cmd 00400 allow tcp from any to me 80 in via $pif setup limit src-addr 2

# Allow inbound SSH connections
$cmd 00410 allow tcp from any to me 22 in via $pif setup limit src-addr 2

# Reject and log all other incoming connections
$cmd 00499 deny log all from any to any in via $pif

最后一条规则记录所有不符合规则集中任何规则的数据包:

# Everything else is denied and logged
$cmd 00999 deny log all from any to any

33.4.4. 内核级网络地址转换(In-kernel NAT)

FreeBSD 的 IPFW 防火墙有两种 NAT 实现:用户空间实现 natd(8) 和更近期的内核 NAT 实现。两者都与 IPFW 配合工作,提供网络地址转换功能。这可以用于提供 Internet 连接共享解决方案,使多台内部计算机可以使用单个公共 IP 地址连接到互联网。

要实现这一点,连接到互联网的 FreeBSD 机器必须充当网关。该系统必须具有两个网络接口卡(NIC),其中一个连接到互联网,另一个连接到内部局域网(LAN)。连接到 LAN 的每台机器应分配一个私有网络空间中的 IP 地址,如 RFC 1918 所定义。

为了启用 IPFW 内核中的 NAT 功能,需要进行一些额外的配置。要在启动时启用内核中的 NAT 支持,必须在 /etc/rc.conf 文件中设置以下内容:

gateway_enable="YES"
firewall_enable="YES"
firewall_nat_enable="YES"

当设置了 firewall_nat_enable 但未设置 firewall_enable 时,它将不会产生任何效果和作用。这是因为内核中的 NAT 实现仅与 IPFW 兼容。

当规则集包含有状态的规则时,NAT 规则的位置至关重要,并且使用 skipto 操作。skipto 操作需要一个规则编号,以便知道要跳转到哪个规则。下面的示例基于前一节中显示的防火墙规则集进行构建。它添加了一些额外的条目并修改了一些现有规则,以便为内核中的 NAT 配置防火墙。首先添加一些额外的变量,这些变量表示要跳转到的规则编号,keep-state 选项以及将用于减少规则数量的 TCP 端口列表。

#!/bin/sh
ipfw -q -f flush
cmd="ipfw -q add"
skip="skipto 1000"
pif=dc0
ks="keep-state"
good_tcpo="22,25,37,53,80,443,110"

使用内核级别的 NAT 时,由于 libalias(3) 的架构,需要禁用 TCP 分段卸载(TSO),libalias(3) 是一个作为内核模块实现的库,用于提供 IPFW 的内核级别 NAT 功能。可以使用 ifconfig(8) 在每个网络接口上禁用 TSO,也可以使用 sysctl(8) 在整个系统上禁用 TSO。要在整个系统上禁用 TSO,需要在 /etc/sysctl.conf 中设置以下内容:

net.inet.tcp.tso="0"

还将配置一个 NAT 实例。可以配置多个 NAT 实例,每个实例都有自己的配置。在这个例子中,只需要一个 NAT 实例,即 NAT 实例号 1。配置可以包括一些选项,如:if 表示公共接口,same_ports 确保别名端口和本地端口号映射相同,unreg_only 将只处理未注册(私有)地址空间的 NAT 实例,reset 将帮助保持一个正常运行的 NAT 实例,即使 IPFW 机器的公共 IP 地址发生变化。有关可以传递给单个 NAT 实例配置的所有可能选项,请参阅 ipfw(8)。在配置有状态的 NAT 防火墙时,需要允许将已转换的数据包重新注入防火墙进行进一步处理。可以通过在防火墙脚本的开始处禁用 one_pass 行为来实现这一点。

ipfw disable one_pass
ipfw -q nat 1 config if $pif same_ports unreg_only reset

入站 NAT 规则被插入在允许所有受信任和回环接口上的所有流量的两个规则之后,并且在重新组装规则之后,但在 check-state 规则之前。在此示例中,为此 NAT 规则选择的规则编号(例如 100)比前三个规则更高,但比 check-state 规则更低非常重要。此外,由于内核 NAT 的行为,建议在第一个 NAT 规则之前和允许受信任接口上的流量的规则之后放置一个重新组装规则。通常情况下,不应发生 IP 分段,但在处理 IPSEC/ESP/GRE 隧道流量时可能会发生,并且在将完整数据包交给内核 NAT 设备之前需要重新组装片段。

在使用用户空间的 natd(8) 时,不需要使用 reassemble 规则,因为 IPFW 的 divert 动作在将数据包传递到套接字之前已经负责重新组装数据包,这也在 ipfw(8) 中有所说明。

在这个示例中使用的 NAT 实例和规则编号与由 rc.firewall 创建的默认 NAT 实例和规则编号不匹配。 rc.firewall 是一个设置 FreeBSD 中默认防火墙规则的脚本。

$cmd 005 allow all from any to any via xl0  # exclude LAN traffic
$cmd 010 allow all from any to any via lo0  # exclude loopback traffic
$cmd 099 reass all from any to any in       # reassemble inbound packets
$cmd 100 nat 1 ip from any to any in via $pif # NAT any inbound packets
# Allow the packet through if it has an existing entry in the dynamic rules table
$cmd 101 check-state

出站规则已经被修改,将 allow 操作替换为 $skip 变量,表示规则处理将继续在规则 1000 处。七个 tcp 规则已被替换为规则 125,因为 $good_tcpo 变量包含了七个允许的出站端口。

请记住,IPFW 的性能主要取决于规则集中存在的规则数量。

# Authorized outbound packets
$cmd 120 $skip udp from any to x.x.x.x 53 out via $pif $ks
$cmd 121 $skip udp from any to x.x.x.x 67 out via $pif $ks
$cmd 125 $skip tcp from any to any $good_tcpo out via $pif setup $ks
$cmd 130 $skip icmp from any to any out via $pif $ks

入站规则保持不变,除了最后一条规则,它移除了 via$pif,以便捕获入站和出站规则。NAT 规则必须跟随这个最后的出站规则,规则号必须比最后一条规则的号码更高,并且规则号必须被 skipto 动作引用。在这个规则集中,规则号为 1000 的规则处理将所有数据包传递给我们配置的实例进行 NAT 处理。下一条规则允许经过 NAT 处理的任何数据包通过。

$cmd 999 deny log all from any to any
$cmd 1000 nat 1 ip from any to any out via $pif # skipto location for outbound stateful rules
$cmd 1001 allow ip from any to any

在这个例子中,规则 10010112510001001 控制出站和入站数据包的地址转换,以便动态状态表中的条目始终注册私有 LANIP 地址。

考虑一个内部的网络浏览器,它通过 80 端口初始化一个新的出站 HTTP 会话。当第一个出站数据包进入防火墙时,它不匹配规则 100 ,因为它是向外而不是向内的。它通过规则 101 ,因为这是第一个数据包,并且尚未被添加到动态状态表中。该数据包最终匹配规则 125 ,因为它是在允许的端口上进行出站,并且具有来自内部局域网的源 IP 地址。匹配此规则后,会执行两个操作。首先,keep-state 操作将一个条目添加到动态状态表中,并执行指定的操作 skipto rule 1000。接下来,数据包经过 NAT 处理并发送到互联网。该数据包到达目标 Web 服务器,生成并发送一个响应数据包。这个新的数据包进入规则集的顶部。它匹配规则 100,并将其目标 IP 地址映射回原始的内部地址。然后,它经过 check-state 规则的处理,在表中被识别为一个现有的会话,并被释放到局域网。

在入站方面,规则集必须拒绝恶意数据包,并仅允许授权的服务。与入站规则匹配的数据包将被发布到动态状态表,并将数据包释放到局域网。作为响应生成的数据包被 check-state 规则识别为属于现有会话。然后,它将被发送到规则 1000 进行网络地址转换(NAT),然后释放到出站接口。

从用户空间的 natd(8) 过渡到内核级别的 NAT 可能一开始看起来是无缝的,但有一个小问题。当使用 GENERIC 内核时,当在 /etc/rc.conf 中启用 firewall_nat_enable 时,IPFW 将加载 libalias.ko 内核模块。 libalias.ko 内核模块只提供基本的 NAT 功能,而用户空间实现的 natd(8) 在其用户空间库中提供了所有的 NAT 功能,无需任何额外配置。所有功能都指的是以下内核模块,除了标准的 libalias.ko 内核模块之外,还可以在需要时额外加载: alias_ftp.koalias_bbt.koskinny.koirc.koalias_pptp.koalias_smedia.ko,使用[.filename]/etc/rc.conf 中的 kld_list 指令。如果使用自定义内核,可以使用 options LIBALIAS 将用户空间库的全部功能编译到内核中。

33.4.4.1. 端口重定向

NAT 的一般缺点是 LAN 客户端无法从互联网访问。 LAN 上的客户端可以向外部建立连接,但无法接收传入连接。如果尝试在 LAN 客户端机器上运行互联网服务,这将带来问题。一个简单的解决方法是将 NAT 提供机器上的选定互联网端口重定向到 LAN 客户端。

例如,IRC 服务器运行在客户端 A 上,而 Web 服务器运行在客户端 B 上。为了使其正常工作,必须将接收到的 6667 端口(IRC)和 80 端口(HTTP)的连接重定向到相应的机器上。

使用内核级别的 NAT,所有配置都在 NAT 实例配置中完成。要查看内核级别 NAT 实例可以使用的所有选项,请参考 ipfw(8)。 IPFW 的语法遵循 natd 的语法。redirect_port 的语法如下:

redirect_port proto targetIP:targetPORT[-targetPORT]
  [aliasIP:]aliasPORT[-aliasPORT]
  [remoteIP[:remotePORT[-remotePORT]]]

要配置上述示例设置,参数应为:

redirect_port tcp 192.168.0.2:6667 6667
redirect_port tcp 192.168.0.3:80 80

在上述规则集中将这些参数添加到 NAT 实例 1 的配置后,TCP 端口将被端口转发到运行 IRC 和 HTTP 服务的局域网客户机。

ipfw -q nat 1 config if $pif same_ports unreg_only reset \
  redirect_port tcp 192.168.0.2:6667 6667 \
  redirect_port tcp 192.168.0.3:80 80

可以使用 redirect_port 指示端口范围而不是单个端口。例如,tcp 192.168.0.2:2000-3000 2000-3000 将会将所有在端口 2000 到 3000 上接收到的连接重定向到客户端 A 上的端口 2000 到 3000 。

33.4.4.2. 地址重定向

如果有多个 IP 地址可用,地址重定向非常有用。每个局域网客户端可以通过 ipfw(8) 分配自己的外部 IP 地址,然后 ipfw(8) 将重写来自局域网客户端的出站数据包,使用正确的外部 IP 地址,并将所有进入特定 IP 地址的流量重定向回特定的局域网客户端。这也被称为静态 NAT 。例如,如果可用的 IP 地址为 128.1.1.1128.1.1.2128.1.1.3,则 128.1.1.1 可以用作 ipfw(8) 机器的外部 IP 地址,而 128.1.1.2128.1.1.3 则被转发回局域网客户端 AB

redirect_addr 的语法如下,其中 localIP 是局域网客户端的内部 IP 地址,publicIP 是与局域网客户端对应的外部 IP 地址。

redirect_addr localIP publicIP

在这个例子中,参数将被读取为:

redirect_addr 192.168.0.2 128.1.1.2
redirect_addr 192.168.0.3 128.1.1.3

redirect_port 类似,这些参数也被放置在 NAT 实例配置中。通过地址重定向,不需要进行端口重定向,因为所有接收到的特定 IP 地址的数据都会被重定向。

ipfw(8) 机器上的外部 IP 地址必须处于活动状态,并且必须与外部接口进行别名设置。有关详细信息,请参阅 rc.conf(5)

33.4.4.3. 用户空间 NAT

让我们从一个声明开始:用户空间的 NAT 实现:natd(8),比内核中的 NAT 有更多的开销。为了让 natd(8) 翻译数据包,数据包必须从内核复制到用户空间,然后再返回,这带来了额外的开销,而内核中的 NAT 则没有这种开销。

要在启动时启用用户空间 NAT 守护进程 natd(8),以下是在文件 /etc/rc.conf 中的最小配置。其中,natd_interface 设置为连接到互联网的网卡的名称。natd(8)rc(8) 脚本将自动检查是否使用动态 IP 地址,并配置自身以处理该地址。

gateway_enable="YES"
natd_enable="YES"
natd_interface="rl0"

通常情况下,上述内核 NAT 的规则集也可以与 natd(8) 一起使用。唯一的例外是内核 NAT 实例的配置 (ipfw -q nat 1 config …​) ,不需要与重组规则 99 一起使用,因为其功能已包含在 divert 操作中。规则编号 100 和 1000 需要稍作修改,如下所示。

$cmd 100 divert natd ip from any to any in via $pif
$cmd 1000 divert natd ip from any to any out via $pif

要配置端口或地址重定向,使用与内核 NAT 相似的语法。然而,现在,与内核 NAT 不同,我们不再在规则集脚本中指定配置,而是最好在配置文件中配置 natd(8)。为了做到这一点,必须通过 /etc/rc.conf 传递一个额外的标志,指定配置文件的路径。

natd_flags="-f /etc/natd.conf"

指定的文件必须包含一行一个的配置选项列表。有关配置文件和可能的变量的更多信息,请参阅 natd(8)。以下是两个示例条目,每行一个:

redirect_port tcp 192.168.0.2:6667 6667
redirect_addr 192.168.0.3 128.1.1.3

33.4.5. IPFW 命令

ipfw 可以在防火墙运行时手动添加或删除单个规则。使用这种方法的问题是,当系统重新启动时,所有更改都会丢失。建议将所有规则写入一个文件,并在启动时使用该文件加载规则,并在该文件更改时替换当前运行的防火墙规则。

ipfw 是一种将运行中的防火墙规则显示到控制台屏幕的有用方法。 IPFW 计费功能会为每个规则动态创建一个计数器,用于计算与规则匹配的每个数据包。在测试规则的过程中,列出规则及其计数器是确定规则是否按预期工作的一种方式。

列出所有正在运行的规则的顺序:

# ipfw list

列出所有正在运行的规则,并显示规则最后匹配的时间戳:

# ipfw -t list

下一个示例列出了匹配规则的会计信息和数据包计数,以及规则本身。第一列是规则编号,其后是匹配的数据包和字节数,然后是规则本身。

# ipfw -a list

除了静态规则外,列出动态规则:

# ipfw -d list

为了显示已过期的动态规则:

# ipfw -d -e list

将计数器归零:

# ipfw zero

将编号为 NUM 的规则的计数器清零:

# ipfw zero NUM

33.4.5.1. 记录防火墙消息

即使启用了日志功能,IPFW 也不会自动生成任何规则日志。防火墙管理员决定哪些规则将被记录,并在这些规则中添加 log 关键字。通常只有拒绝规则会被记录。习惯上,在规则集中将“ipfw default deny everything”规则复制一份,并在最后一条规则中包含 log 关键字。这样,就可以查看所有未匹配规则集中任何规则的数据包。

日志记录是一把双刃剑。如果不小心处理,过多的日志数据或者 DoS 攻击可能会填满磁盘的日志文件。日志消息不仅会被写入 syslogd,还会显示在根控制台屏幕上,很快就会变得令人讨厌。

IPFIREWALL_VERBOSE_LIMIT=5 内核选项限制了发送给 syslogd(8) 的连续消息数量,这些消息涉及给定规则的数据包匹配。当内核中启用了这个选项时,关于特定规则的连续消息数量被限制在指定的数量上。200 条相同的日志消息并没有什么好处。当这个选项设置为五时,关于特定规则的五条连续消息将被记录到 syslogd 中,其余相同的连续消息将被计数并以类似以下的短语发布到 syslogd 中:

last message repeated 45 times

所有已记录的数据包消息默认写入到 /var/log/security,该路径在 /etc/syslog.conf 中定义。

33.4.5.2. 构建规则脚本

大多数经验丰富的 IPFW 用户会创建一个包含规则的文件,并以与脚本运行兼容的方式编码它们。这样做的主要好处是防火墙规则可以批量刷新,而无需重新启动系统来激活它们。这种方法在测试新规则时非常方便,因为可以根据需要执行该过程多次。作为脚本,可以使用符号替换来将常用值替换为多个规则中的值。

这个示例脚本与 sh(1)csh(1)tcsh(1) shells 使用的语法兼容。符号替换字段以美元符号 ($) 为前缀。符号字段没有 $ 前缀。用于填充符号字段的值必须用双引号 ("") 括起来。

像这样开始规则文件:

############### start of example ipfw rules script #############
#
ipfw -q -f flush       # Delete all rules
# Set defaults
oif="tun0"             # out interface
odns="192.0.2.11"      # ISP's DNS server IP address
cmd="ipfw -q add "     # build rule prefix
ks="keep-state"        # just too lazy to key this each time
$cmd 00500 check-state
$cmd 00502 deny all from any to any frag
$cmd 00501 deny tcp from any to any established
$cmd 00600 allow tcp from any to any 80 out via $oif setup $ks
$cmd 00610 allow tcp from any to $odns 53 out via $oif setup $ks
$cmd 00611 allow udp from any to $odns 53 out via $oif $ks
################### End of example ipfw rules script ############

规则并不重要,因为这个例子的重点是如何填充符号替换字段。

如果上面的示例位于 /etc/ipfw.rules 文件中,则可以使用以下命令重新加载规则:

# sh /etc/ipfw.rules

/etc/ipfw.rules 可以位于任何位置,文件的名称也可以是任意的。

通过手动运行这些命令也可以实现相同的效果:

# ipfw -q -f flush
# ipfw -q add check-state
# ipfw -q add deny all from any to any frag
# ipfw -q add deny tcp from any to any established
# ipfw -q add allow tcp from any to any 80 out via tun0 setup keep-state
# ipfw -q add allow tcp from any to 192.0.2.11 53 out via tun0 setup keep-state
# ipfw -q add 00611 allow udp from any to 192.0.2.11 53 out via tun0 keep-state

33.4.6. IPFW 内核选项

为了将 IPFW 支持静态编译到自定义内核中,请参考 中的说明。以下选项可用于自定义内核配置文件:

options    IPFIREWALL			# enables IPFW
options    IPFIREWALL_VERBOSE		# enables logging for rules with log keyword to syslogd(8)
options    IPFIREWALL_VERBOSE_LIMIT=5	# limits number of logged packets per-entry
options    IPFIREWALL_DEFAULT_TO_ACCEPT # sets default policy to pass what is not explicitly denied
options    IPFIREWALL_NAT		# enables basic in-kernel NAT support
options    LIBALIAS			# enables full in-kernel NAT support
options    IPFIREWALL_NAT64		# enables in-kernel NAT64 support
options    IPFIREWALL_NPTV6		# enables in-kernel IPv6 NPT support
options    IPFIREWALL_PMOD		# enables protocols modification module support
options    IPDIVERT			# enables NAT through natd(8)

IPFW 可以作为内核模块加载:上述选项默认情况下被构建为模块,或者可以在运行时使用可调节参数进行设置。

33.5. IPFILTER (IPF)

IPFILTER,也称为 IPF,是一个跨平台的开源防火墙,已经移植到多个操作系统,包括 FreeBSD、NetBSD、OpenBSD 和 Solaris™。

IPFILTER 是一个位于内核的防火墙和 NAT 机制,可以由用户空间程序进行控制和监控。可以使用 ipf 设置或删除防火墙规则,使用 ipnat 设置或删除 NAT 规则,使用 ipfstat 打印 IPFILTER 内核部分的运行时统计信息,并可以使用 ipmon 将 IPFILTER 的操作记录到系统日志文件中。

IPF 最初使用“最后匹配规则获胜(the last matching rule wins)”的规则处理逻辑,并且只使用无状态规则。此后,IPF 已经进行了增强,包括了 quickkeep state 选项。

IPF FAQ 位于 http://www.phildev.net/ipf/index.html。IPFilter 邮件列表的可搜索存档可在 http://marc.info/?l=ipfilter 上找到。

本手册的这一部分重点介绍了与 FreeBSD 相关的 IPF。它提供了包含 quickkeep state 选项的规则示例。

33.5.1. 启用 IPF

IPF 被包含在基本的 FreeBSD 安装中作为一个可加载的内核模块,这意味着不需要自定义内核来启用 IPF 。

对于喜欢将 IPF 支持静态编译到自定义内核中的用户,请参考 配置 FreeBSD 内核 中的说明。以下内核选项可用:

options IPFILTER
options IPFILTER_LOG
options IPFILTER_LOOKUP
options IPFILTER_DEFAULT_BLOCK

options IPFILTER 启用对 IPFILTER 的支持,options IPFILTER_LOG 启用使用 ipl 伪设备对于每个带有 log 关键字的规则进行 IPF 日志记录,IPFILTER_LOOKUP 启用 IP 池以加快 IP 查找速度,而 options IPFILTER_DEFAULT_BLOCK 更改默认行为,使得任何不符合防火墙 pass 规则的数据包都被阻止。

要在启动时配置系统以启用 IPF,请将以下条目添加到 /etc/rc.conf 文件中。这些条目还将启用日志记录和 default pass all。要将默认策略更改为 block all 而无需编译自定义内核,请记住在规则集的末尾添加一个 block all 规则。

ipfilter_enable="YES"             # Start ipf firewall
ipfilter_rules="/etc/ipf.rules"   # loads rules definition text file
ipv6_ipfilter_rules="/etc/ipf6.rules" # loads rules definition text file for IPv6
ipmon_enable="YES"                # Start IP monitor log
ipmon_flags="-Ds"                 # D = start as daemon
                                  # s = log to syslog
                                  # v = log tcp window, ack, seq
                                  # n = map IP & port to names

如果需要 NAT 功能,还需要添加以下行:

gateway_enable="YES"              # Enable as LAN gateway
ipnat_enable="YES"                # Start ipnat function
ipnat_rules="/etc/ipnat.rules"    # rules definition file for ipnat

然后,现在开始启动 IPF:

#  service ipfilter start

要加载防火墙规则,请使用 ipf 指定规则集文件的名称。可以使用以下命令替换当前运行的防火墙规则:

# ipf -Fa -f /etc/ipf.rules

-Fa 选项会清空所有内部规则表,-f 选项用于指定包含要加载的规则的文件。

这提供了对自定义规则集进行更改并使用最新的规则副本更新正在运行的防火墙的能力,而无需重新启动系统。这种方法对于测试新规则非常方便,因为可以根据需要执行该过程多次。

有关此命令可用的其他标志的详细信息,请参考 ipf(8)

33.5.2. IPF 规则语法

本节描述了用于创建有状态规则的 IPF 规则语法。在创建规则时,请记住,除非规则中出现了 quick 关键字,否则每个规则都会按顺序读取,应用的是 最后匹配的规则。这意味着,即使第一个匹配数据包的规则是 pass,如果后面有一个匹配的规则是 block,数据包将被丢弃。示例规则集可以在 /usr/share/examples/ipfilter 中找到。

创建规则时,使用 # 字符来标记注释的开始,并且可以出现在规则的末尾,以解释该规则的功能,或者独占一行。任何空行都会被忽略。

在规则中使用的关键字必须按照特定的顺序从左到右进行编写。一些关键字是必需的,而其他一些是可选的。一些关键字具有子选项,这些子选项可能是关键字本身,并且还可以包含更多的子选项。关键字的顺序如下所示,其中大写字母表示变量,小写字母表示必须在其后面的变量之前出现的关键字:

ACTION DIRECTION OPTIONS proto PROTO_TYPE from SRC_ADDR SRC_PORT to DST_ADDR DST_PORT TCP_FLAG|ICMP_TYPE keep state STATE

本节描述了每个关键字及其选项。这不是一个详尽无遗的选项列表。有关在创建 IPF 规则和使用每个关键字的示例时可以使用的规则语法的完整描述,请参阅 ipf(5)

动作(ACTION)

action 关键字指示如果数据包与该规则匹配,应该采取什么操作。每个规则都 必须 有一个动作。以下动作被识别:

block:丢弃该数据包。

pass:允许数据包通过。

log:生成一条日志记录。

count:计算数据包和字节数的数量,可以提供规则使用频率的指示。

auth:将数据包排队,以便由另一个程序进一步处理。

call:提供对内置于 IPF 中的函数的访问,允许执行更复杂的操作。

decapsulate:移除任何头部以便处理数据包的内容。

方向(DIRECTION)

接下来,每个规则必须明确指定流量的方向,使用以下关键字之一:

in:规则应用于入站数据包。

out: 规则应用于出站数据包。

all: 规则适用于任何方向。

如果系统有多个接口,可以在指定方向时同时指定接口。一个例子是 in on fxp0

选项(OPTIONS)

选项是可选的。然而,如果指定了多个选项,则必须按照这里显示的顺序使用。

log:在执行指定的操作时,数据包头部的内容将被写入到 ipl(4) 数据包日志伪设备中。

quick: 如果一个数据包符合这个规则,那么规则指定的操作将会执行,并且对于这个数据包,不会再进行任何后续规则的处理。

on:必须跟随着 ifconfig(8) 中显示的接口名称。只有当数据包通过指定的接口以指定的方向传输时,规则才会匹配。

使用 log 关键字时,可以按照以下顺序使用以下限定词:

body:表示在报文头部之后,将记录报文内容的前 128 个字节。

first:如果 log 关键字与 keep state 选项一起使用,建议使用此选项,以便仅记录触发的数据包,而不是与状态连接匹配的每个数据包。

还有其他选项可用于指定错误返回消息。有关更多详细信息,请参阅 ipf(5)

协议类型(PROTO_TYPE)

协议类型是可选的。然而,如果规则需要指定 SRC_PORT 或 DST_PORT ,则协议类型是必需的,因为它定义了协议的类型。在指定协议类型时,使用 proto 关键字,后面跟着来自 /etc/protocols 的协议号或名称。示例协议名称包括 tcpudpicmp。如果指定了 PROTO_TYPE,但未指定 SRC_PORT 或 DST_PORT,则该规则将匹配该协议的所有端口号。

源地址

from 关键字是必需的,并且后面跟着一个关键字,表示数据包的来源。来源可以是主机名、IP 地址后跟 CIDR 掩码、地址池或关键字 all。有关示例,请参阅 ipf(5)

没有办法匹配那些不容易用点分十进制形式或掩码长度表示的 IP 地址范围。可以使用 net-mgmt/ipcalc 软件包或 port 来简化 CIDR 掩码的计算。有关该实用程序的更多信息,请访问其网页:http://jodies.de/ipcalc[http://jodies.de/ipcalc]。

源端口(SRC_PORT)

源端口号是可选的。但是,如果使用了源端口号,规则中必须首先定义 PROTO_TYPE。端口号还必须在 proto 关键字之前。

支持多种不同的比较运算符:` = (等于),!=(不等于),<`(小于),>(大于),(小于等于)和 >=(大于等于)。

要指定端口范围,请将两个端口号放在 <>(小于和大于)、><(大于和小于)或 :(大于或等于和小于或等于)之间。

目标地址

to 关键字是必需的,后面跟着一个关键字,表示数据包的目的地。与 SRC_ADDR 类似,它可以是主机名、IP 地址后跟 CIDR 掩码、地址池或关键字 all

目标端口(DST_PORT)

与 SRC_PORT 类似,目标端口号是可选的。然而,如果使用了目标端口号,规则中必须首先定义 PROTO_TYPE 。端口号还必须在 proto 关键字之前。

TCP_FLAG|ICMP_TYPE

如果指定 tcp 作为 PROTO_TYPE,那么可以使用字母来指定标志,其中每个字母代表用于确定连接状态的可能的 TCP 标志之一。可能的值包括:S(SYN),A(ACK),P(PSH),F(FIN),U(URG),R(RST),C(CWN)和 E(ECN)。

如果指定了 icmp 作为 PROTO_TYPE,可以指定要匹配的 ICMP 类型。有关允许的类型,请参阅 ipf(5)

状态(STATE)

如果一个 pass 规则包含 keep state,IPF 将在其动态状态表中添加一个条目,并允许匹配连接的后续数据包。IPF 可以跟踪 TCP、UDP 和 ICMP 会话的状态。任何 IPF 可以确定是活动会话的一部分的数据包,即使是不同的协议,也将被允许通过。

在 IPF 中,目标要通过连接到公共互联网的接口发送的数据包首先会与动态状态表进行匹配检查。如果数据包与活动会话对话的下一个预期数据包匹配,它将离开防火墙,并且会话对话流的状态将在动态状态表中更新。不属于已经活动会话的数据包将会与出站规则集进行检查。从连接到公共互联网的接口进入的数据包首先会与动态状态表进行匹配检查。如果数据包与活动会话的下一个预期数据包匹配,它将离开防火墙,并且会话对话流的状态将在动态状态表中更新。不属于已经活动会话的数据包将会与入站规则集进行检查。

keep state 之后可以添加几个关键字。如果使用了这些关键字,它们可以设置各种控制有状态过滤的选项,例如设置连接限制或连接时长。请参考 ipf(5) 以获取可用选项及其描述的列表。

33.5.3. 示例规则集

本节演示如何创建一个示例规则集,该规则集仅允许与 pass 规则匹配的服务,并阻止所有其他服务。

FreeBSD 使用回环接口(lo0)和 IP 地址 127.0.0.1 进行内部通信。防火墙规则集必须包含允许这些内部使用的数据包自由传输的规则:

# no restrictions on loopback interface
pass in quick on lo0 all
pass out quick on lo0 all

与互联网连接的公共接口用于授权和控制所有出站和入站连接的访问。如果一个或多个接口连接到私有网络,那些内部接口可能需要规则来允许源自局域网的数据包在内部网络之间或连接到互联网的接口之间流动。规则集应该分为三个主要部分:任何受信任的内部接口、通过公共接口的出站连接和通过公共接口的入站连接。

这两条规则允许所有流量通过一个名为 xl0 的受信任的局域网接口传输。

# no restrictions on inside LAN interface for private network
pass out quick on xl0 all
pass in quick on xl0 all

公共接口的出站和入站部分的规则应该将最常匹配的规则放在不常匹配的规则之前,而该部分的最后一条规则应该阻止并记录该接口和方向的所有数据包。

这组规则定义了名为 dc0 的公共接口的出站部分。这些规则保持状态并标识内部系统被授权用于公共互联网访问的特定服务。所有规则都使用 quick 并指定适当的端口号和(如果适用)目标地址。

# interface facing Internet (outbound)
# Matches session start requests originating from or behind the
# firewall, destined for the Internet.

# Allow outbound access to public DNS servers.
# Replace x.x.x.x with address listed in /etc/resolv.conf.
# Repeat for each DNS server.
pass out quick on dc0 proto tcp from any to x.x.x.x port = 53 flags S keep state
pass out quick on dc0 proto udp from any to x.x.x.x port = 53 keep state

# Allow access to ISP's specified DHCP server for cable or DSL networks.
# Use the first rule, then check log for the IP address of DHCP server.
# Then, uncomment the second rule, replace z.z.z.z with the IP address,
# and comment out the first rule
pass out log quick on dc0 proto udp from any to any port = 67 keep state
#pass out quick on dc0 proto udp from any to z.z.z.z port = 67 keep state

# Allow HTTP and HTTPS
pass out quick on dc0 proto tcp from any to any port = 80 flags S keep state
pass out quick on dc0 proto tcp from any to any port = 443 flags S keep state

# Allow email
pass out quick on dc0 proto tcp from any to any port = 110 flags S keep state
pass out quick on dc0 proto tcp from any to any port = 25 flags S keep state

# Allow NTP
pass out quick on dc0 proto tcp from any to any port = 37 flags S keep state

# Allow FTP
pass out quick on dc0 proto tcp from any to any port = 21 flags S keep state

# Allow SSH
pass out quick on dc0 proto tcp from any to any port = 22 flags S keep state

# Allow ping
pass out quick on dc0 proto icmp from any to any icmp-type 8 keep state

# Block and log everything else
block out log first quick on dc0 all

公共接口入站部分的规则示例首先阻止所有不需要的数据包。这样可以减少最后一个规则记录的数据包数量。

# interface facing Internet (inbound)
# Block all inbound traffic from non-routable or reserved address spaces
block in quick on dc0 from 192.168.0.0/16 to any    #RFC 1918 private IP
block in quick on dc0 from 172.16.0.0/12 to any     #RFC 1918 private IP
block in quick on dc0 from 10.0.0.0/8 to any        #RFC 1918 private IP
block in quick on dc0 from 127.0.0.0/8 to any       #loopback
block in quick on dc0 from 0.0.0.0/8 to any         #loopback
block in quick on dc0 from 169.254.0.0/16 to any    #DHCP auto-config
block in quick on dc0 from 192.0.2.0/24 to any      #reserved for docs
block in quick on dc0 from 204.152.64.0/23 to any   #Sun cluster interconnect
block in quick on dc0 from 224.0.0.0/3 to any       #Class D & E multicast

# Block fragments and too short tcp packets
block in quick on dc0 all with frags
block in quick on dc0 proto tcp all with short

# block source routed packets
block in quick on dc0 all with opt lsrr
block in quick on dc0 all with opt ssrr

# Block OS fingerprint attempts and log first occurrence
block in log first quick on dc0 proto tcp from any to any flags FUP

# Block anything with special options
block in quick on dc0 all with ipopts

# Block public pings and ident
block in quick on dc0 proto icmp all icmp-type 8
block in quick on dc0 proto tcp from any to any port = 113

# Block incoming Netbios services
block in log first quick on dc0 proto tcp/udp from any to any port = 137
block in log first quick on dc0 proto tcp/udp from any to any port = 138
block in log first quick on dc0 proto tcp/udp from any to any port = 139
block in log first quick on dc0 proto tcp/udp from any to any port = 81

每当规则上有带有 log first 选项的日志消息时,运行 ipfstat -hio 来评估该规则被匹配的次数。大量的匹配可能表明系统正在遭受攻击。

入站部分的其余规则定义了允许从互联网发起的连接。最后一条规则拒绝了所有未在本节中明确允许的连接。

# Allow traffic in from ISP's DHCP server. Replace z.z.z.z with
# the same IP address used in the outbound section.
pass in quick on dc0 proto udp from z.z.z.z to any port = 68 keep state

# Allow public connections to specified internal web server
pass in quick on dc0 proto tcp from any to x.x.x.x port = 80 flags S keep state

# Block and log only first occurrence of all remaining traffic.
block in log first quick on dc0 all

33.5.4. 配置 NAT

要启用 NAT,请将以下语句添加到 /etc/rc.conf 文件中,并指定包含 NAT 规则的文件的名称:

gateway_enable="YES"
ipnat_enable="YES"
ipnat_rules="/etc/ipnat.rules"

NAT 规则是灵活的,可以实现许多不同的功能,以满足商业和家庭用户的需求。这里介绍的规则语法已经简化,以展示常见用法。有关完整的规则语法描述,请参考 ipnat(5)

NAT 规则的基本语法如下所示,其中 map 是规则的开始,IF 应替换为外部接口的名称:

map IF LAN_IP_RANGE -> PUBLIC_ADDRESS

LAN_IP_RANGE 是内部客户端使用的 IP 地址范围。通常情况下,它是一个私有地址范围,比如 192.168.1.0/24PUBLIC_ADDRESS 可以是静态外部 IP 地址,也可以是关键字 0/32,表示分配给 IF 的 IP 地址。

在 IPF 中,当一个数据包从局域网带有公共目的地到达防火墙时,它首先通过防火墙规则集的出站规则。然后,数据包被传递到 NAT 规则集,该规则集从上到下进行读取,第一个匹配的规则获胜。IPF 会将每个 NAT 规则与数据包的接口名称和源 IP 地址进行测试。当数据包的接口名称与 NAT 规则匹配时,将检查数据包在私有局域网中的源 IP 地址是否落在 LAN_IP_RANGE 指定的 IP 地址范围内。如果匹配成功,数据包的源 IP 地址将被重写为 PUBLIC_ADDRESS 指定的公共 IP 地址。IPF 会在其内部 NAT 表中发布一个条目,以便当数据包从互联网返回时,可以将其映射回原始的私有 IP 地址,然后将其传递给防火墙规则进行进一步处理。

对于拥有大量内部系统或多个子网的网络来说,将每个私有 IP 地址汇集到一个公共 IP 地址中的过程会成为一个资源问题。有两种方法可以解决这个问题。

第一种方法是分配一段端口作为源端口使用。通过添加 portmap 关键字,可以指示 NAT 仅使用指定范围内的源端口。

map dc0 192.168.1.0/24 -> 0/32 portmap tcp/udp 20000:60000

或者,使用 auto 关键字,告诉 NAT 确定可用于使用的端口:

map dc0 192.168.1.0/24 -> 0/32 portmap tcp/udp auto

第二种方法是使用一个公共地址池。当局域网地址太多无法适应单个公共地址,并且有一块公共 IP 地址可用时,这种方法非常有用。这些公共地址可以作为一个池, NAT 会从中选择一个 IP 地址,将数据包的地址映射为其外部传输时的地址。

公共 IP 地址的范围可以使用网络掩码或 CIDR 表示法来指定。这两个规则是等价的:

map dc0 192.168.1.0/24 -> 204.134.75.0/255.255.255.0
map dc0 192.168.1.0/24 -> 204.134.75.0/24

常见做法是将公共可访问的 Web 服务器或邮件服务器隔离到内部网络段。这些服务器的流量仍然需要经过 NAT ,但需要进行端口重定向以将入站流量定向到正确的服务器。例如,要将使用内部地址 10.0.10.25 的 Web 服务器映射到其公共 IP 地址 20.20.20.5,请使用以下规则:

rdr dc0 20.20.20.5/32 port 80 -> 10.0.10.25 port 80

如果它是唯一的 Web 服务器,这个规则也会起作用,因为它会将所有外部 HTTP 请求重定向到 10.0.10.25

rdr dc0 0.0.0.0/0 port 80 -> 10.0.10.25 port 80

IPF 内置了一个可以与 NAT 一起使用的 FTP 代理。它监视所有出站流量,以检测主动或被动的 FTP 连接请求,并动态创建临时过滤规则,其中包含 FTP 数据通道使用的端口号。这样就不需要打开大范围的高阶端口来进行 FTP 连接。

在这个例子中,第一条规则调用代理来处理内部局域网的出站 FTP 流量。第二条规则将 FTP 流量从防火墙传递到互联网,第三条规则处理内部局域网的所有非 FTP 流量。

map dc0 10.0.10.0/29 -> 0/32 proxy port 21 ftp/tcp
map dc0 0.0.0.0/0 -> 0/32 proxy port 21 ftp/tcp
map dc0 10.0.10.0/29 -> 0/32

FTP 的 map 规则在 NAT 规则之前,这样当一个数据包匹配到 FTP 规则时, FTP 代理会创建临时过滤规则,以便让 FTP 会话数据包通过并进行 NAT。所有不是 FTP 的局域网数据包如果匹配到第三条规则,也会进行 NAT 处理。

如果没有 FTP 代理,将需要以下防火墙规则。请注意,如果没有代理,需要允许所有大于 1024 的端口:

# Allow out LAN PC client FTP to public Internet
# Active and passive modes
pass out quick on rl0 proto tcp from any to any port = 21 flags S keep state

# Allow out passive mode data channel high order port numbers
pass out quick on rl0 proto tcp from any to any port > 1024 flags S keep state

# Active mode let data channel in from FTP server
pass in quick on rl0 proto tcp from any to any port = 20 flags S keep state

每当编辑包含 NAT 规则的文件时,运行 ipnat 命令并使用 -CF 选项来删除当前的 NAT 规则,并清空动态转换表的内容。同时,使用 -f 选项并指定要加载的 NAT 规则集的名称。

# ipnat -CF -f /etc/ipnat.rules

显示 NAT 统计信息:

# ipnat -s

要列出 NAT 表的当前映射:

# ipnat -l

要打开详细模式并显示与规则处理、活动规则和表项相关的信息:

# ipnat -v

33.5.5. 查看 IPF 统计信息

IPF 包括 ipfstat(8),可用于检索和显示统计信息,这些统计信息是在数据包通过防火墙时匹配规则时收集的。统计信息从上次启动防火墙或使用 ipf -Z 将其重置为零的上次重置以来累积。

默认的 ipfstat 输出如下所示:

input packets: blocked 99286 passed 1255609 nomatch 14686 counted 0
 output packets: blocked 4200 passed 1284345 nomatch 14687 counted 0
 input packets logged: blocked 99286 passed 0
 output packets logged: blocked 0 passed 0
 packets logged: input 0 output 0
 log failures: input 3898 output 0
 fragment state(in): kept 0 lost 0
 fragment state(out): kept 0 lost 0
 packet state(in): kept 169364 lost 0
 packet state(out): kept 431395 lost 0
 ICMP replies: 0 TCP RSTs sent: 0
 Result cache hits(in): 1215208 (out): 1098963
 IN Pullups succeeded: 2 failed: 0
 OUT Pullups succeeded: 0 failed: 0
 Fastroute successes: 0 failures: 0
 TCP cksum fails(in): 0 (out): 0
 Packet log flags set: (0)

有几个选项可供选择。当使用 -i 参数表示入站规则,或使用 -o 参数表示出站规则时,该命令将检索并显示内核当前安装和使用的相应过滤规则列表。如果想要同时看到规则编号,可以加上 -n 参数。例如,ipfstat -on 将显示带有规则编号的出站规则表:

@1 pass out on xl0 from any to any
@2 block out on dc0 from any to any
@3 pass out quick on dc0 proto tcp/udp from any to any keep state

在每个规则前加上匹配次数的计数,请使用 -h 选项。例如,ipfstat -oh 显示出站内部规则表,每个规则前都有它的使用次数计数。

2451423 pass out on xl0 from any to any
354727 block out on dc0 from any to any
430918 pass out quick on dc0 proto tcp/udp from any to any keep state

要以类似于 top(1) 的格式显示状态表,请使用 ipfstat -t 命令。当防火墙遭受攻击时,此选项可以识别和查看攻击数据包。可选的子标志提供了实时监控目标或源 IP、端口或协议的能力。有关详细信息,请参阅 ipfstat(8)

33.5.6. IPF 日志记录

IPF 提供了 ipmon 工具,可以将防火墙的日志信息以人类可读的格式写入。使用该工具需要首先按照 配置 FreeBSD 内核 中的说明,在自定义内核中添加 options IPFILTER_LOG 选项。

该命令通常以守护进程模式运行,以提供连续的系统日志文件,以便可以回顾过去事件的记录。由于 FreeBSD 内置了 syslogd(8) 设施来自动轮转系统日志,因此默认的 rc.conf ipmon_flags 语句使用了 -Ds 选项。

ipmon_flags="-Ds" # D = start as daemon
                  # s = log to syslog
                  # v = log tcp window, ack, seq
                  # n = map IP & port to names

日志记录提供了事后审查的能力,可以查看诸如哪些数据包被丢弃、它们来自哪些地址以及它们的目的地是什么等信息。这些信息在追踪攻击者方面非常有用。

一旦在 rc.conf 中启用了日志记录功能,并使用 service ipmon start 启动后,IPF 将只记录包含 log 关键字的规则。防火墙管理员决定哪些规则应该被记录,通常只有拒绝规则会被记录。习惯上,在规则集中的最后一个规则中包含 log 关键字。这样可以查看所有未匹配规则集中任何规则的数据包。

默认情况下,ipmon -Ds 模式使用 local0 作为日志设备。以下日志级别可用于进一步分离记录的数据:

LOG_INFO - packets logged using the "log" keyword as the action rather than pass or block.
LOG_NOTICE - packets logged which are also passed
LOG_WARNING - packets logged which are also blocked
LOG_ERR - packets which have been logged and which can be considered short due to an incomplete header

为了将 IPF 设置为将所有数据记录到 /var/log/ipfilter.log 文件中,首先创建一个空文件:

# touch /var/log/ipfilter.log

然后,要将所有记录的消息写入指定的文件,请将以下语句添加到 /etc/syslog.conf 文件中:

local0.* /var/log/ipfilter.log

要激活更改并指示 syslogd(8) 读取修改后的 /etc/syslog.conf,请运行 service syslogd reload

不要忘记编辑 /etc/newsyslog.conf 来轮转新的日志文件。

ipmon 生成的消息由以空格分隔的数据字段组成。所有消息都包含以下共同字段:

  1. 数据包接收日期。

  2. 数据包接收时间。格式为 HH:MM:SS.F,表示小时、分钟、秒和秒的小数部分。

  3. 处理数据包的接口名称。

  4. 规则的组和规则编号的格式为 @0:17

  5. 动作:p 表示通过,b 表示阻止,S 表示短数据包,n 表示未匹配任何规则,L 表示日志规则。

  6. 地址写作三个字段:源地址和端口由逗号分隔,→ 符号,以及目标地址和端口。例如: 209.53.17.22,80 → 198.73.220.17,1722

  7. 以协议名称或编号后跟 PR:例如,PR tcp

  8. len 后面跟着数据包的头部长度和总长度:例如,len 20 40

如果数据包是 TCP 数据包,则会有一个额外的字段,以连字符开头,后面跟着对应于设置的任何标志的字母。请参考 ipf(5) 以获取字母和它们的标志列表。

如果数据包是一个 ICMP 数据包,那么在末尾将会有两个字段:第一个字段始终是“icmp”,下一个字段是 ICMP 消息和子消息类型,用斜杠分隔。例如:对于一个端口不可达的消息,表示为 icmp 3/3

33.6. 黑名单服务

Blacklistd 是一个守护进程,监听套接字以接收来自其他守护进程的有关连接尝试失败或成功的通知。它最常用于阻止在开放端口上进行过多的连接尝试。一个典型的例子是在互联网上运行的 SSH 收到大量来自机器人或脚本的密码猜测和入侵尝试。使用 blacklistd ,守护进程可以通知防火墙在多次尝试后创建一个过滤规则,以阻止来自单个源的过多连接尝试。 Blacklistd 最初在 NetBSD 上开发,并在版本 7 中首次出现。FreeBSD 11 从 NetBSD 导入了 blacklistd。

本章介绍了如何设置和配置 blacklistd ,并提供了如何使用它的示例。读者应该熟悉基本的防火墙概念,如规则。有关详细信息,请参阅防火墙章节。示例中使用了 PF,但在 FreeBSD 上可用的其他防火墙也应该能够与 blacklistd 一起使用。

33.6.1. 启用 Blacklistd

blacklistd 的主要配置存储在 blacklistd.conf(5) 中。还有一些命令行选项可用于更改 blacklistd 的运行时行为。重启后的持久配置应存储在 /etc/blacklistd.conf 中。要在系统启动时启用守护进程,请在 /etc/rc.conf 中添加一行 blacklistd_enable,如下所示:

# sysrc blacklistd_enable=yes

要手动启动服务,请运行以下命令:

# service blacklistd start

33.6.2. 创建一个 Blacklistd 规则集

黑名单规则在 blacklistd.conf(5) 中配置,每行一个条目。每个规则包含一个由空格或制表符分隔的元组。规则可以属于 localremote,分别适用于运行 blacklistd 的机器或外部来源。

33.6.2.1. 本地规则

一个本地规则的示例 blacklistd.conf 条目如下:

[local]
ssh             stream  *       *               *       3       24h

[local] 部分之后的所有规则都被视为本地规则(这是默认情况),适用于本地机器。当遇到 [remote] 部分时,其后的所有规则都被视为远程机器规则。

规则由七个字段组成,字段之间用制表符或空格分隔。前四个字段用于标识应该被加入黑名单的流量。接下来的三个字段定义了黑名单的行为。通配符用星号(*)表示,可以匹配该字段中的任何内容。第一个字段定义了位置,对于本地规则来说,这些是网络端口。位置字段的语法如下:

[address|interface][/mask][:port]

地址可以以数字格式指定为 IPv4 ,也可以以方括号指定为 IPv6 。还可以使用类似 em0 的接口名称。

套接字类型由第二个字段定义。TCP 套接字的类型是 stream,而 UDP 则表示为 dgram 。上面的示例使用 TCP,因为 SSH 正在使用该协议。

协议可以在 blacklistd 规则的第三个字段中使用。可以使用以下协议:tcpudptcp6udp6 或数字。通常使用通配符(如示例中)来匹配所有协议,除非有理由通过特定协议区分流量。

在第四个字段中,定义了报告事件的守护进程的有效用户或所有者。可以在这里使用用户名或 UID ,也可以使用通配符(参见上面的示例规则)。

数据包过滤规则的名称由第五个字段声明,该字段开始规则的行为部分。默认情况下,blacklistd 将所有阻止的数据包放在名为 blacklistd 的 pf 锚点下,就像这样在 pf.conf 中:

anchor "blacklistd/*" in on $ext_if
block in
pass out

对于单独的块列表,可以在此字段中使用锚点名称。在其他情况下,通配符就足够了。当名称以连字符(-)开头时,意味着应该使用带有默认规则名称前缀的锚点。使用连字符的修改示例如下所示:

ssh             stream  *       *               -ssh       3       24h

有了这样的规则,任何新的阻止列表规则都会被添加到一个名为 blacklistd-ssh 的锚点中。

为了针对单个规则违规封锁整个子网,可以在规则名称中使用 /。这会导致名称的剩余部分被解释为要应用于规则中指定的地址的掩码。例如,这个规则将封锁与 /24 相邻的每个地址。

22              stream  tcp       *               */24    3       24h

在这里指定正确的协议是很重要的。 IPv4 和 IPv6 对待 /24 的方式不同,这就是为什么在这个规则的第三个字段中不能使用“ * ”。

这条规则定义了如果该网络中的任何一个主机表现不当,那么该网络上的其他所有内容也将被阻止。

第六个字段称为 nfail,它设置了在特定远程 IP 被列入黑名单之前所需的登录失败次数。当在此位置使用通配符时,意味着永远不会发生阻止。在上面的示例规则中,定义了三次限制,这意味着在一次连接中尝试三次登录 SSH 后,该 IP 将被阻止。

在 blacklistd 规则定义中,最后一个字段指定了主机被加入黑名单的时间长度。默认单位是秒,但也可以使用后缀 mhd 分别指定分钟、小时和天。

整个示例规则的意思是,当通过 SSH 进行三次身份验证后,将为该主机生成一个新的 PF 阻止规则。规则匹配是通过首先按顺序检查本地规则来进行的,从最具体到最不具体。当发生匹配时,将应用 remote 规则,并且匹配的 remote 规则将更改名称、nfaildisable 字段。

33.6.2.2. 远程规则

远程规则用于指定 blacklistd 根据当前评估的远程主机来改变其行为的方式。远程规则中的每个字段与本地规则中的字段相同,唯一的区别在于 blacklistd 使用它们的方式。为了解释这一点,我们使用以下示例规则:

[remote]
203.0.113.128/25 *      *       *               =/25    =       48h

地址字段可以是 IP 地址(IPv4 或 IPv6),端口或两者兼有。这允许为特定的远程地址范围设置特殊规则,就像这个例子中一样。套接字类型、协议和所有者字段的解释与本地规则完全相同。

然而,名称字段是不同的:远程规则中的等号(=)告诉 blacklistd 使用匹配的本地规则的值。这意味着防火墙规则条目被采用,并添加了 /25 前缀(一个 255.255.255.128 的网络掩码)。当来自该地址范围的连接被列入黑名单时,整个子网都会受到影响。在这里也可以使用 PF 锚点名称,这样 blacklistd 将为该地址块添加到该名称的锚点的规则。当指定通配符时,将使用默认表。

可以为地址定义 nfail 列中的自定义失败次数。这对于特定规则的例外情况非常有用,可以允许某人对规则应用更宽松或更宽容的登录尝试。当在第六个字段中使用星号时,阻止功能将被禁用。

远程规则允许对登录尝试的限制进行更严格的执行,相比于来自本地网络(如办公室)的尝试。

33.6.3. 黑名单客户端配置

在 FreeBSD 中有几个软件包可以利用 blacklistd 的功能。其中最重要的两个是 ftpd(8)sshd(8) ,用于阻止过多的连接尝试。要在 SSH 守护程序中激活 blacklistd,请将以下行添加到 /etc/ssh/sshd_config 文件中:

UseBlacklist yes

在进行这些更改后,重新启动 sshd 以使其生效。

对于 ftpd(8) 的黑名单功能已启用,可以使用 -B 选项,在 /etc/inetd.conf 文件中或者作为 /etc/rc.conf 文件中的一个标志来设置,如下所示:

ftpd_flags="-B"

这就是使这些程序与 blacklistd 通信所需的全部内容。

33.6.4. 黑名单管理

Blacklistd 为用户提供了一个名为 blacklistctl(8) 的管理工具。它显示由 blacklistd.conf(5) 中定义的规则所屏蔽的地址和网络。要查看当前被屏蔽的主机列表,可以使用 dump-b 组合,如下所示。

# blacklistctl dump -b
      address/ma:port id      nfail   last access
213.0.123.128/25:22   OK      6/3     2019/06/08 14:30:19

这个例子显示,来自地址范围 213.0.123.128/25 的端口 22 上有 3 次允许的尝试中有 6 次失败。列出的尝试次数比允许的次数多,因为 SSH 允许客户端在单个 TCP 连接上尝试多次登录。正在进行中的连接不会被 blacklistd 停止。最后一次连接尝试在输出的 last access 列中列出。

要查看此主机在封锁列表上的剩余时间,请在上一个命令中添加 -r

# blacklistctl dump -br
      address/ma:port id      nfail   remaining time
213.0.123.128/25:22   OK      6/3     36s

在这个例子中,还有 36 秒的时间,直到这个主机不再被阻止。

33.6.5. 从阻止列表中移除主机

有时候在剩余时间到期之前需要将主机从阻止列表中移除。不幸的是,blacklistd 中没有这样的功能。然而,可以使用 pfctl 将地址从 PF 表中移除。对于每个被阻止的端口,在 /etc/pf.conf 中的 blacklistd 锚点内定义了一个子锚点。例如,如果有一个用于阻止端口 22 的子锚点,它被称为 blacklistd/22。在该子锚点内有一个包含被阻止地址的表。该表的名称是 port 加上端口号。在这个例子中,它将被称为 port22。有了这些信息,现在可以使用 pfctl(8) 来显示所有列出的地址。

# pfctl -a blacklistd/22 -t port22 -T show
...
213.0.123.128/25
...

在确定要从列表中解除阻止的地址之后,以下命令将其从列表中移除:

# pfctl -a blacklistd/22 -t port22 -T delete 213.0.123.128/25

该地址现在已从 PF 中移除,但仍会显示在 blacklistctl 列表中,因为它不知道在 PF 中所做的任何更改。blacklistd 数据库中的条目最终会过期并从其输出中删除。 如果主机再次匹配 blacklistd 中的阻止规则,则该条目将被再次添加。


上次修改时间: September 18, 2024 by fiercex