[上一小节: 表格] [总目录] [小下一节: 网络地址转换]
当数据包经过一个网络接口时PF会有选择地允许或禁止。 pf(4) 在检查数据包时使用的标准是基于OSI模型的第三层 (IPv4 及 IPv6) 和第四层 (TCP, UDP, ICMP 及 ICMPv6)的包头。最常使用的标准是源和目标的地址、源和目标的端口、协议。
过滤规则指定了数据包必须匹配和最终执行的标准, 当发现一个匹配项时遵照规则执行阻塞或放行。过滤规则以从前至后的顺序进行评估, 最后一条匹配的规则将是"胜者", 除非数据包匹配了一条包含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]
要建立一个默认拒绝的过滤策略, 最前面的两条过滤规则应该是:
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选项, 就会使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-bound 和 floating 的规则来控制。这里的每一个规则关键字与state-policy选项具有同等的作用。例如:
pass out on fxp0 proto { tcp, udp, icmp } from any \ to any modulate state (if-bound)
为了数据包匹配状态项, 这条规则限定了这些数据包必须通过 fxp0 接口进行传送。
有人也许听说过"没人能为UDP产生状态, 因为UDP是一个无状态协议!", 尽管一个UDP会话没有任何状态(有明确的通讯起点和终点)的概念是对的, 但它对PF为UDP会话产生状态的能力没有任何影响。当遇到没有"开始"和"结束"数据包的协议时, PF仅简单地检查一个匹配的数据包已通过的时间, 如果超过时限, 状态就会被清除。这个时限在 pf.conf 文件内用 timeout values 选项 进行设置。
可以用以下几种指定的选项控制过滤规则产生的状态项的动作:
选项在括号内进行指定并紧跟一个state关键字(例如:keep state, modulate state或 synproxy 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连接中单独插入一组限制以完成三次握手。
上面两个选项都能自动调用 source-track 规则选项但不兼容于 source-track global选项。
因为这些限制仅能用于TCP连接完成三次握手的过程, 还有更多的措施可以用来对付那些恶意的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的标志以及其含义:
要让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标志而非其它标志"的数据包产生状态, 这样的规则最终变成了这样:
这个理论是, 仅在TCP会话开始时产生状态, 并且这个会话应该以一个SYN标志开始, 而非其它。 可问题是一些站点开始使用ECN标志, 而且任何使用ECN的站点如果想与你建立连接都会被这条规则拒绝。一个好得多的建议是不指定任何标志, 并且让PF对你的规则应用默认标志。如果你真需要自己指定标志, 那么这种组合是安全的:. . . flags S/FSRPAUEW 馊主意!!
. . . flags S/SAFR
这是经过实践检验的和安全的, 如果通讯已经进行了净化, 它也没必须检查FIN和RST。净化会导致PF丢弃所有包含非法TCP标志组合(如:SYN 和 RST)的进站数据包, 并且规格化潜在的不明确组合(像:SYN 和 FIN)。
通常, 当客户端初始化一个到服务器的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 state 和 modulate state 同样的功能。
如果PF工作在 bridge(4) 模式时, SYN代理无法工作。
地址“欺骗”是恶意用户伪造源IP地址传送数据包, 这是为了隐藏他们真实的IP地址或冒充网络上的另一个节点。一旦用户被欺骗, 他们可以在不暴露自己真实的IP地址的情况下发起网络攻击或者获得只有授权的IP地址可以访问的服务。
PF通过 antispoof 关键字提供一些保护以抵御地址欺骗:
antispoof [log] [quick] for interface [af]
例子:
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 匹配系统未知操作系统。
注意 以下几条:
pass in quick on fxp0 all allow-opts
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
[上一小节: 表格] [总目录] [小下一节: 网络地址转换]