[OpenBSD]

[上一小节: 表格] [总目录] [小下一节: 网络地址转换]

PF: 包过滤


目录


介绍

当数据包经过一个网络接口时PF会有选择地允许或禁止。 pf(4) 在检查数据包时使用的标准是基于OSI模型的第三层 (IPv4IPv6) 和第四层 (TCP, UDP, ICMPICMPv6)的包头。最常使用的标准是源和目标的地址、源和目标的端口、协议。

过滤规则指定了数据包必须匹配和最终执行的标准, 当发现一个匹配项时遵照规则执行阻塞或放行。过滤规则以从前至后的顺序进行评估, 最后一条匹配的规则将是"胜者", 除非数据包匹配了一条包含quick关键字的规则, 此时这个数据包被评估为在最终动作前可以违反所有的过滤规则。如果在过滤规则集前面有一个隐含的pass all, 这就意味着如果一个数据包没有匹配任何规则那它会被放行。

(译者注:1.如果前后规则有冲突会按照后面的规则执行。 2.带quick关键字的规则有最高权限, 会被立刻执行。)

规则语法

通常, 过滤规则非常简单的语法是:
action [direction] [log] [quick] [on interface] [af] [proto protocol] \
   [from src_addr [port src_port]] [to dst_addr [port dst_port]] \
   [flags tcp_flags] [state]
action
对匹配数据包执行的动作, passblock pass动作允许数据包回到内核做进一步处理, 而block动作则基于 block-policy 的设定, 默认的动作可以设定为block dropblock return
direction
在一个接口上数据包传送的方向, inout
log
指定通过 pflogd(8)记录数据包, 如果规则产生了状态则仅纪录状态的建立。要记录所有的数据包使用 log (all).
quick
如果一个数据包匹配了一条指定了quick关键字的规则, 那么这条规则被认为是最后一条匹配的规则, 并会立即按此规则对数据包执行指定的动作。
interface
数据包经过的网络接口的名称或组。 可以使用 ifconfig(8) 命令将网络接口添加到任意的组中。内核也会自动产生几个组: 这使所有匹配规则的数据包可以分别通过pppcarp接口。
af
数据包的地址族(address family), 也就是对应IPv4的inet, 或者对应IPv6的inet6。 通常PF可以通过源和/或目标的地址确定此参数类型。
protocol
数据包的第4层协议:
src_addr, dst_addr
 
分别代表IP报头的源和目标地址。地址可以指定为:
src_port, dst_port
分别代表第四层包头的源和目的端口。端口可以指定为:
tcp_flags
指定使用proto tcp(tcp协议)时TCP头必须设定的标志。标志设定像这样 flags check/mask. 例如: flags S/SA - 指示PF只检查 S 和 A (SYN 和 ACK) 标志, 并且只有在SYN标志为"on"时才匹配。在OpenBSD 4.1和以后的版本中默认的标志 S/SA 被应用于所有的TCP过滤规则中。
state
指定是否在数据包匹配规则时保持状态信息。

默认拒绝

建立一个防火墙时, 推荐的常规做法是采用"默认拒绝"的方式。也就是, 先拒绝一切, 而后有选择地放行特定的通讯。 这种方式之所以被推荐是因为它慎之又慎并且也可以更简单地定义一个规则集。

要建立一个默认拒绝的过滤策略, 最前面的两条过滤规则应该是:

block in all
block out all

这将阻止所有的通讯。

允许通讯

通讯必须被确认通过防火墙, 否则将会被默认拒绝规则丢弃。 这时数据包规则诸如 源/目标的端口、源/目标的地址、以及协议就该一展身手了。无论何时, 一旦允许通讯通过防火墙, 相应规则应定义得尽可能地严厉, 这是为了保证希望的通讯、并且只有希望的通讯才可以通过防火墙。

一些例子:

# Pass traffic in on dc0 from the local network, 192.168.0.0/24, 
# to the OpenBSD machine's IP address 192.168.0.1. Also, pass the
# return traffic out on dc0.
pass in  on dc0 from 192.168.0.0/24 to 192.168.0.1
pass out on dc0 from 192.168.0.1 to 192.168.0.0/24


# Pass TCP traffic in on fxp0 to the web server running on the # OpenBSD machine. The interface name, fxp0, is used as the # destination address so that packets will only match this rule if # they're destined for the OpenBSD machine. pass in on fxp0 proto tcp from any to fxp0 port www

quick关键字

像前面指出的那样, 每个数据包被规则集从上到下顺序评估。 默认情况下, 数据包被标志为可通行, 但是任一规则全可以修改它, 甚至可以在最后一条规则前面反复修改几次。最后一条匹配的规则是"胜者"但这里有一个例外: 如果在一条匹配的过滤规则中包含quick选项, 就会使PF放弃处理其余的匹配规则而直接执行这条规则的动作。我么看两个例子:

错误的例子:

block in on fxp0 proto tcp from any to any port ssh
pass in all 

这种情况下, block 那行可能被评估过了, 但是永远不会起作用, 因为它后面的一行是放行所有进入的通讯。.

较好的例子:

block in quick on fxp0 proto tcp from any to any port ssh
pass in all 

这与第一个例子略有不同。 因为有quick选项, 所以如果一个数据包匹配了block那行, 它将会被马上阻止, 并且忽略其余的所有相匹配的规则集。

保持状态

PF的一个重要功能就是"保持状态"或"状态检查"。 状态检查是指PF的跟踪状态或一个网络连接过程的能力。 通过将每个连接的信息储存在一个状态表中, PF有能力快速判定一个y要通过防火墙的数据包是否属于一个已经建立的连接。如果这个数据包属于已经建立的连接,该数据包就不经规则集检查而直接被放行。

保持状态有很多优点, 包括简化规则集和提供更好的数据包过滤性能。 PF有能力将任何方向的数据包与状态表中的项目匹配, 这意味着不需要编写一个回程通讯的规则, 并且因为数据包匹配状态化连接不需要经过规则集的检查, 所以PF处理这类数据包的时间显著减少。

当一条规则创建状态时, 会在第一个匹配此规则的数据包通过时在发送者和接收者之间建立一个"状态", 现在不仅是由发送者到接收者的数据包因为匹配这个状态项而被防火墙放行, 就连从接收者至发送者的答复数据包(回程通讯)也会被直接放行。

所有的pass规则都会在一个数据包匹配此规则时自动地创建一个状态项。 你可以用no state选项明确地禁止它。

pass out on fxp0 proto tcp from any to any

这条规则允许在fxp0 接口上放行出站TCP通讯并允许回程通讯通过防火墙。保持状态极大地提升了防火墙的性能,因为状态查询明显比对一个数据包进行规则匹配评估快得多。

modulate state 选项的作用就像 keep state,但它只处理TCP数据包。 With modulate state, 出站连接的初始化序列号Initial Sequence Number (ISN)是随机化的。 这对那些只能可怜地选择ISNs的特定操作系统来说非常有用,因为它保护了发起的连接。 为了简化规则集, modulate state 选项可被用在指定的协议不是TCP的规则里;在这种情况下它被视为 keep state.

为出战的TCP, UDP和ICMP数据包以及modulate TCP ISNs保持状态:

pass out on fxp0 proto { tcp, udp, icmp } from any \
    to any modulate state

保持状态的另一个优点是相应的ICMP通讯会被防火墙放行。 例如, 一个通过防火墙的TCP连接被跟踪了状态, 这时一个与此TCP连接相关的ICMP source-quench信息抵达, PF会将这个信息和相关的状态项匹配, 并被放行。(译者注:ICMP source-quench是一种Internet控制信息协议, 这个信息要求发送者减少发送给路由器或主机的信息, 在路由器和主机没有足够的缓存空间处理请求时会产生这种信息, 也可能是路由器或主机的缓存已接近限定值。想了解更多的Internet控制信息协议, 请参阅这里)

状态项的范围可以被state-policy runtime选项全局控制, 也可通过一个基于状态选项关键字if-bound, group-boundfloating 的规则来控制。这里的每一个规则关键字与state-policy选项具有同等的作用。例如:

pass out on fxp0 proto { tcp, udp, icmp } from any \
  to any modulate state (if-bound)

为了数据包匹配状态项, 这条规则限定了这些数据包必须通过 fxp0 接口进行传送。

为UDP保持状态

有人也许听说过"没人能为UDP产生状态, 因为UDP是一个无状态协议!", 尽管一个UDP会话没有任何状态(有明确的通讯起点和终点)的概念是对的, 但它对PF为UDP会话产生状态的能力没有任何影响。当遇到没有"开始"和"结束"数据包的协议时, PF仅简单地检查一个匹配的数据包已通过的时间, 如果超过时限, 状态就会被清除。这个时限在 pf.conf 文件内用 timeout values 选项 进行设置。

stateful跟踪选项

可以用以下几种指定的选项控制过滤规则产生的状态项的动作:

max number
限制此规则可以产生状态项的最大值。如果达到了此最大值, 这条规则将无法为数据包产生状态直至已经存在的状态数降到最大值以下。
no state
防止规则自动产生一个状态项。
source-track
这个选项对每个源IP地址产生的状态数量进行跟踪, 它有两种格式: 被跟踪的全局IP地址总数可以通过 src-nodes runtime option 进行控制。
max-src-nodes number
当使用 source-track 选项后, max-src-nodes 会限制可同时产生状态的源IP地址数量, 这个选项仅能与source-track规则一同使用。
max-src-states number
当使用 source-track 选项后, max-src-states 会限制每个源IP地址可同时产生状态项的数量。这个限制范围(例如, 仅该规则产生的状态或者所有使用source-track的规则产生的状态)依赖于 source-track 选项的设定。

选项在括号内进行指定并紧跟一个state关键字(例如:keep state, modulate statesynproxy state)。如果有多个选项就以逗号分开。在OpenBSD 4.1和以后版本中 keep state 选项变成了所有过滤规则隐含的默认选项, 尽管如此, 当指定stateful选项时, 它仍然必须带有一个state关键字。

例子:

pass in on $ext_if proto tcp to $web_server \
    port www keep state \
    (max 200, source-track rule, max-src-nodes 100, max-src-states 3)  

上面的规则定义了下面的动作:

可以在状态化的TCP连接中单独插入一组限制以完成三次握手。

max-src-conn number
限制一台主机用来完成三次握手的最大TCP连接数
max-src-conn-rate number / interval
在一个时间间隔内限制的最大新连接的数量。

上面两个选项都能自动调用 source-track 规则选项但不兼容于 source-track global选项。

因为这些限制仅能用于TCP连接完成三次握手的过程, 还有更多的措施可以用来对付那些恶意的IP地址。

overload table
将一个恶意主机的IP地址加入以此命名的表格o
flush [global]
清除源地址产生的匹配此规则的其它所有状态。如果你指定了 global , 将会删除这个源IP地址的所有状态, 不管是由哪个规则产生的。

例子:

table <abusive_hosts> persist
block in quick from <abusive_hosts>

pass in on $ext_if proto tcp to $web_server \     port www flags S/SA keep state \     (max-src-conn 100, max-src-conn-rate 15/5, overload <abusive_hosts> flush)

作用如下:

TCP 标志

用标志匹配TCP数据包经常被用来过滤企图打开一个新连接的TCP数据包。下面列出了TCP的标志以及其含义:

要让PF在评估一条规则时检查TCP标志要采用下面带有flags关键字的语法:

flags check/mask
flags any 

这里的 mask 部分告诉PF只检查指定的标志而 check 部分指定标志的头部必须是"on"的数据包才可用来匹配。使用any关键字来指定允许数据包头部使用任何组合的标志。

pass in on fxp0 proto tcp from any to any port ssh flags S/SA
pass in on fxp0 proto tcp from any to any port ssh

因为 flags S/SA 是默认设置, 上面的规则完全相同, 上面的每条规则放行带有SYN标志的TCP通讯,同时也仅查找SYN和ACK标志。 一个带有SYN和ECE标志的数据包可以匹配上面的规则, 而以恶搞带有SYN和ACK或者只带有 ACK 标志的数据包则无法匹配。

默认的标志可以通过 flags 选项来重新修改。如上。

你应该小心地使用标志 —— 明白你正在做什么和为什么这样做, 并且谨慎地处理别人的建议, 因为很多这样的建议很糟糕。有人曾经这样建议过"仅为设置了SYN标志而非其它标志"的数据包产生状态, 这样的规则最终变成了这样:

     . . . flags S/FSRPAUEW  馊主意!!
这个理论是, 仅在TCP会话开始时产生状态, 并且这个会话应该以一个SYN标志开始, 而非其它。 可问题是一些站点开始使用ECN标志, 而且任何使用ECN的站点如果想与你建立连接都会被这条规则拒绝。一个好得多的建议是不指定任何标志, 并且让PF对你的规则应用默认标志。如果你真需要自己指定标志, 那么这种组合是安全的:
. . . flags S/SAFR 

这是经过实践检验的和安全的, 如果通讯已经进行了净化, 它也没必须检查FIN和RST。净化会导致PF丢弃所有包含非法TCP标志组合(如:SYN 和 RST)的进站数据包, 并且规格化潜在的不明确组合(像:SYN 和 FIN)。

TCP SYN代理

通常, 当客户端初始化一个到服务器的TCP连接时, PF会放行两个终点间的握手数据包, 然而PF有能力代理握手过程, PF会先完成与客户端握手在发起一个与服务器的握手, 然后放行两端的数据包。这种做法的好处是在客户端完成握手过程前没有数据包发送给服务器, 它避免了欺骗式的TCP SYN floods对服务器造成的威胁, 因为伪造的客户端不能完成握手。

启用TCP SYN 代理是通过在过滤规则中使用 synproxy state 关键字完成的, 例如:

pass in on $ext_if proto tcp from any to $web_server port www \
   flags S/SA synproxy state  

这条规则指示PF代理到web服务器的TCP连接。

因为 synproxy state 的工作方式包含了与 keep statemodulate state 同样的功能。

如果PF工作在 bridge(4) 模式时, SYN代理无法工作。

拦截欺骗数据包

地址“欺骗”是恶意用户伪造源IP地址传送数据包, 这是为了隐藏他们真实的IP地址或冒充网络上的另一个节点。一旦用户被欺骗, 他们可以在不暴露自己真实的IP地址的情况下发起网络攻击或者获得只有授权的IP地址可以访问的服务。

PF通过 antispoof 关键字提供一些保护以抵御地址欺骗:

antispoof [log] [quick] for interface [af]
log
指定使用 pflogd(8) 记录匹配的数据包。
quick
如果一个数据包匹配了这条规则, PF会参照“胜者”规则并停止评估后面的规则集。
interface
这个网络接口将激活欺骗保护。也可以是一个接口的列表
af
激活欺骗保护的地址, 可以是inet表示IPv4 或者inet6表示IPv6。

例子:

antispoof for fxp0 inet

当一个规则集被加载后, 任何出现的 antispoof 关键字被扩展成两条过滤规则。假设网络接口fxp0有一个IP地址 10.0.0.1和子网掩码255.255.255.0(例如是一个 /24), 上面的过滤规则将扩展成:

block in on ! fxp0 inet from 10.0.0.0/24 to any
block in inet from 10.0.0.1 to any 

这些规则完成两件事:

注意: antispoof 规则扩展的过滤规则也会阻止通过loopback接口发送给本地的数据包。最佳方法是跳过loopback接口过滤, 但应用antispoof规则时这就成了一种必要:

set skip on lo0

antispoof for fxp0 inet

使用antispoof应该仅限于被分配了IP地址的接口, 如果在没有IP地址的接口上应用antispoof, 规则集会变成像这样:

block drop in on ! fxp0 inet all
block drop in inet all 

这些规则存在阻止所有接口上的所有进站通讯的风险。

单播反向路径转发

OpenBSD 4.0开始, PF提供了一个单播反向路径转发(uRPF)功能。当一个数据包通过uRPF检查时, 这个数据包的源IP地址被从路由表中查出。如果在路由表项目里找到的出站接口就是该数据包进站使用的接口, 那么该数据包就通过了URPF检查, 如果接口不相同, 就说明该数据包可能使用了伪造的IP地址。

可以在规则里使用 urpf-failed 关键字对数据包进行uRPF检查:

block in quick from urpf-failed label uRPF

注意uRPF检查仅在对称路由环境中才有意义。

uRPF 提供与 antispoof 规则相同的功能。

被动的操作系统指纹识别

被动OS指纹识别(OSFP)是一种被动地探测远程主机操作系统的一种方法, 它基于那台主机的TCP SYN数据包的某些特征。这些信息可以在过滤规则里做为标准。

PF通过比较TCP SYN数据包和指纹文件来判断远程主机的操作系统类型, 默认的指纹文件是 /etc/pf.os 。 一旦PF被启用, 当前的指纹列表可以通过下列命令查看:

# pfctl -s osfp

在一个过滤规则里, 可以通过OS类型、版本、子类型、补丁级别指定一个指纹。所有这些项目可以用上面的命令列出, 过滤规则里可以使用 os 关键字指定一个指纹:

pass  in on $ext_if from any os OpenBSD keep state
block in on $ext_if from any os "Windows 2000"
block in on $ext_if from any os "Linux 2.4 ts"
block in on $ext_if from any os unknown  

特殊的操作系统类型 unknown 匹配系统未知操作系统。

注意 以下几条:

IP选项

默认情况下, PF阻止带有IP选项集的数据包, 这可能使像nmap这样的"操作系统指纹识别程序"更难工作。如果你有一个程序需要放行诸如组播或IGMP这样的数据包, 你可以用 allow-opts 说明:
pass in quick on fxp0 all allow-opts

过滤规则集实例

下面是一个规则集的例子。这台计算机作为一个小型内部网络和Internet之间的一个防火墙。只有下面这些过滤规则;这个例子里不包含 queueing, nat, rdr 等规则。
ext_if  = "fxp0"
int_if  = "dc0"
lan_net = "192.168.0.0/24"
# table containing all IP addresses assigned to the firewall
table <firewall> const { self }
# don't filter on the loopback interface
set skip on lo0
# scrub incoming packets
scrub in all
# setup a default deny policy
block all
# activate spoofing protection for all interfaces
block in quick from urpf-failed
# only allow ssh connections from the local network if it's from the
# trusted computer, 192.168.0.15. use "block return" so that a TCP RST is
# sent to close blocked connections right away. use "quick" so that this
# rule is not overridden by the "pass" rules below.
block return in quick on $int_if proto tcp from ! 192.168.0.15 \
   to $int_if port ssh
# pass all traffic to and from the local network.
# these rules will create state entries due to the default
# "keep state" option which will automatically be applied.
pass in  on $int_if from $lan_net to any
pass out on $int_if from any to $lan_net
# pass tcp, udp, and icmp out on the external (Internet) interface. 
# tcp connections will be modulated, udp/icmp will be tracked
# statefully.
pass out on $ext_if proto { tcp udp icmp } all modulate state
# allow ssh connections in on the external interface as long as they're
# NOT destined for the firewall (i.e., they're destined for a machine on
# the local network). log the initial packet so that we can later tell
# who is trying to connect. use the tcp syn proxy to proxy the connection.
# the default flags "S/SA" will automatically be applied to the rule by
# PF.
pass in log on $ext_if proto tcp from any to ! <firewall> \
   port ssh synproxy state

[上一小节: 表格] [总目录] [小下一节: 网络地址转换]


[back] www@openbsd.org
$OpenBSD: filter.html, v 1.50 2009/01/25 18:09:49 jasper Exp $