《4. OpenBSD PF用户指南之基本配置——包过滤》
发表于 : 2010-01-21 10:31
[上一小节: 表格] [总目录] [小下一节: 网络地址转换]
PF: 包过滤
--------------------------------------------------------------------------------
目录
介绍
规则语法
默认拒绝
允许通讯
quick关键字
保持状态
为UDP保持状态
状态化跟踪选项
TCP标志
TCP SYN代理
阻塞欺骗数据包
单播反向路径转发
被动的操作系统指纹识别
IP选项
过滤规则集实例
--------------------------------------------------------------------------------
介绍
当数据包经过一个网络接口时PF会有选择地允许或禁止。 pf(4) 在检查数据包时使用的标准是基于OSI模型的第三层 (IPv4 及 IPv6) 和第四层 (TCP, UDP, ICMP 及 ICMPv6)的包头。最常使用的标准是源和目标的地址、源和目标的端口、协议。
过滤规则指定了数据包必须匹配和最终执行的标准, 当发现一个匹配项时遵照规则执行阻塞或放行。过滤规则以从前至后的顺序进行评估, 最后一条匹配的规则将是"胜者", 除非数据包匹配了一条包含quick关键字的规则, 此时这个数据包被评估为在最终动作前可以违反所有的过滤规则。如果在过滤规则集前面有一个隐含的pass all, 这就意味着如果一个数据包没有匹配任何规则那他将会被允许通过。
(译者注:1.如果前后规则有冲突会按照后面的规则执行。 2.带quick关键字的规则有最高权限, 会被立刻执行。)
规则语法
通常, 过滤规则非常简单的语法是:对匹配数据包执行的动作, pass或block 。pass动作允许数据包回到内核做进一步处理, 而block动作则基于 block-policy 的设定, 默认的动作可以设定为block drop 或 block return 。
direction
在一个接口上数据包传送的方向, in或out 。
log
指定通过 pflogd(8).记录数据包, 如果规则产生了状态则仅纪录状态的建立。要记录所有的数据包使用 log (all).
quick
如果一个数据包匹配了一条指定了quick关键字的规则, 那么这条规则被认为是最后一条匹配的规则, 并会立即按此规则对数据包执行指定的动作。
interface
数据包经过的网络接口名称或组。可以使用 ifconfig(8) 命令将网络接口添加到任意的组中。内核也会自动产生几个组:
egress组, 包括了包含默认路由的网络接口。
克隆接口的接口家族组。例如: ppp或carp.
这使所有匹配规则的数据包可以分别通过ppp或carp接口。
af
数据包的地址族(address family), 也就是对应IPv4的inet, 或者对应IPv6的inet6。 通常PF可以通过源和/或目标的地址确定此参数类型。
protocol
数据包的第4层协议:
tcp
udp
icmp
icmp6
一个 /etc/protocols 定义的有效的协议名称。
0-255之间的一个协议编号
通过列表定义的一组协议。
src_addr, dst_addr
分别代表IP报头的源和目标地址。地址可以指定为:
一个单独的IPv4 或 IPv6 地址。
一个 CIDR 网段。
一个完全限定域名, 它必须在载入规则时能够被DNS解析, 解析出的IP地址会取代规则中的域名。
一个网络接口或组的名称。所有分派给此接口的IP地址会取代规则中的网络接口或组的名称。
一个网络接口加上 /子网掩码 (例如, /24)。 所有此接口上的IP地址与子网掩码组合成一个CIDR网段, 此地址段会替换规则中的网络接口/子网掩码。
一个( )内的网络接口或组的名称。它告诉PF如果此接口或组的IP地址发生变化时需要更新规则。有些网络接口是通过DHCP或拨号获得IP地址的, 这种方式对它们很有效, 因为当其IP地址变化时不用重新加载规则集。
一个带有如下任何一个修饰符得网络接口名称:
:network - 代替CIDR网段(例如, 192.168.0.0/24) <---24表示前三个数字相同, 也就是在192.168.0.1到192.168.0.254的范围内
:broadcast - 代替网络广播地址(例如, 192.168.0.255)
:peer - 代替点到点链接中对方的IP地址
另外, :0 修饰符可以附加到一个接口名称或上面任何修饰符的后面, 它指示PF将规则中的名称替换为IP地址时不包含别名的IP地址。 这些修饰符也可以应用在括号内的接口上。 例子: fxp0:network:0
一个表格。
关键字 urpf-failed 应用于源地址, 它指示PF通过uRPF检查执行此规则。
任何上面的规则加上了否定修饰符 ! ("not") 修饰符。
使用一个 列表 表示的一组地址。
关键字 any 代表所有地址。
关键字 all 是 from any to any 的缩写。
src_port, dst_port
分别代表第四层包头的源和目的端口。端口可以指定为:
一个1到65535之间的数字
一个 /etc/services 内指定的有效的服务名称 <---例如:www 代表web服务的80端口, ftp 代表ftp服务的21端口
用 列表 表示的一组端口
一个范围:
!= (不等于)
< (小于)
> (大于)
<= (小于等于)
>= (大于等于)
>< (范围内)
<> (范围外)
最后这两个是二元运算符(带有两个参数) 并且这两个参数不包含在范围内。
: (包含在范围内)
也是二元运算符, 但是两个参数包含在范围内。
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
指定是否在数据包匹配规则时保持状态信息。
keep state - 作用于 TCP, UDP 和 ICMP。在OpenBSD 4.1和以后的版本中, 这是所有过滤规则的默认选项。
modulate state - 仅作用于TCP。 PF将为所有匹配这一规则的数据包产生强壮的初始化序号(ISNs)。
synproxy state - 代理进入的TCP连接以保护服务器免受TCP SYN 洪水式攻击。这个选项包含了keep state和modulate state的功能。
默认拒绝
建立一个防火墙时, 推荐的常规做法是采用"默认拒绝"的方式。也就是, 先拒绝一切, 而后有选择地放行特定的通讯。 这种方式之所以被推荐是因为它慎之又慎并且也可以更简单地定义一个规则集。
要建立一个默认拒绝的过滤策略, 最前面的两条过滤规则应该是:
这将阻止所有的通讯。
允许通讯
通讯必须被确认通过防火墙, 否则将会被默认拒绝规则丢弃。 这时数据包规则诸如 源/目标的端口、源/目标的地址、以及协议就该一展身手了。无论何时, 一旦允许通讯通过防火墙, 相应规则应定义得尽可能地严厉, 这是为了保证希望的通讯、并且只有希望的通讯才可以通过防火墙。
一些例子:
quick关键字
像前面指出的那样, 每个数据包被规则集从上到下顺序评估。 默认情况下, 数据包被标志为可通行, 但是任一规则全可以修改它, 甚至可以在最后一条规则前面反复修改几次。最后一条匹配的规则是"胜者"。但这里有一个例外: 如果在一条匹配的过滤规则中包含quick选项, 就会使PF放弃处理其余的匹配规则而直接执行这条规则的动作。我么看两个例子:
错误的例子:
这种情况下, block 那行可能被评估过了, 但是永远不会起作用, 因为它后面的一行是放行所有进入的通讯。.
较好的例子:
这与第一个例子略有不同。 因为有quick选项, 所以如果一个数据包匹配了block那行, 它将会被马上阻止, 并且忽略其余的所有相匹配的规则集。
状态保持
PF的一个重要功能就是"保持状态"或"状态化检查"。 状态化检查是指PF的跟踪状态或一个网络连接过程的能力。 通过将每个连接的信息储存在一个状态表中, PF有能力快速测定一个通过防火墙的数据包是否属于一个已经建立的连接。如果这个数据包属于已经建立的连接, 就不经规则集检查而直接被允许通过防火墙。
保持状态有很多优点, 包括简化规则集和提供更好的数据包过滤性能。 PF有能力将任何方向的数据包与状态表中的项目匹配, 这意味着不需要编写一个回程通讯的规则, 并且因为数据包匹配状态化连接不需要经过规则集的检查, 所以PF处理这类数据包的时间显著减少。
当一条规则导致了状态, 第一个匹配此规则的数据包将在发送者和接收者之间建立一个"状态", 现在不仅是由发送者到接收者的数据包因为匹配这个状态项而被防火墙放行, 就连从接收者至发送者的答复数据包(回程通讯)也会被直接放行。
从OpenBSD 4.1开始, 任何过滤规则在一个数据包匹配它时会自动产生一个状态项, 先前的OpenBSD版本中过滤规则不得不明确地使用 keep state 选项。
OpenBSD 4.1及以后版本的例子:
OpenBSD 4.0及以前版本的例子:
这些规则允许在fxp0接口上的任何出站TCP通讯以及回复通讯通过防火墙。保持状态是一个非常好的特性, 使用它可以显著的提高你防火墙的性能, 因为状态查询比用规则集检验数据包快得多。
modulate state 选项的作用就像 keep state, 除了它仅能应用于TCP数据包。 使用 modulate state选项时, 出站连接的ISN(初始化序号)是随机的, 这对保护某些选择ISN有困难的操作系统的初始化连接是十分有用的, 从OpenBSD 3.5开始, modulate state 选项可以应用于指定了非TCP协议的规则。
为出站的TCP, UDP和ICMP数据包保持状态并调整TCP初始化序号:
保持状态的另一个优点是相应的ICMP通讯会被防火墙放行。例如, 一个通过防火墙的TCP连接被跟踪了状态, 这时一个与此TCP连接相关的ICMP source-quench信息抵达, PF会将这个信息和相关的状态项匹配, 并放行。(译者注:ICMP source-quench是一种Internet控制信息协议, 这个信息要求发送者减少发送给路由器或主机的信息, 在路由器和主机没有足够的缓存空间处理请求时会产生这种信息, 也可能是路由器或主机的缓存已接近限定值。想了解更多的Internet控制信息协议, 请参阅这里)
状态项的范围可以被state-policy runtime选项全局控制, 也可通过一个基于状态选项关键字if-bound, group-bound 和 floating 的规则来控制。这里的每一个规则关键字与state-policy选项具有同等的作用。例如:
这条规则指示为了数据包匹配状态项, 这些数据包必须通过fxp0接口进行传送。
注意 nat, binat, 和 rdr 规则会为匹配的连接隐含地产生状态直至连接完全通过防火墙规则集。
为UDP保持状态
有人也许听说过"没人能为UDP产生状态, 因为UDP是一个无状态协议!", 尽管一个UDP会话没有任何状态(有明确的通讯起点和终点)的概念是对的, 但它对PF为UDP会话产生状态的能力没有任何影响。当遇到没有"开始"和"结束"数据包的协议时, PF仅简单地检查一个匹配的数据包已通过的时间, 如果超过时限, 状态就会被清除。这个时限在 pf.conf 文件内用 timeout values 选项 进行设置。
状态化跟踪选项
可以用以下几种指定的选项控制过滤规则产生的状态项的动作:
max number
限制此规则可以产生状态项的最大值。如果达到了此最大值, 这条规则将无法为数据包产生状态直至已经存在的状态数降到最大值以下。
no state
防止规则自动产生一个状态项。
source-track
这个选项对每个源IP地址产生的状态数量进行跟踪, 它有两种格式:
source-track rule - 此规则可以产生状态的最大数量受max-src-nodes 和 max-src-states 选项限制。只有此规则仅限制自己产生的状态项数目。
source-track global - 这个选项用来限制所有规则产生的状态数量。每个规则可以指定不同的 max-src-nodes 和 max-src-states 选项, 然而每个规则产生的状态项都会被计入状态项总数内。
被跟踪的全局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 state, or synproxy state).如果有多个选项就以逗号分开。在OpenBSD 4.1和以后版本中 keep state 选项变成了所有过滤规则隐含的默认选项, 尽管如此, 当指定状态化选项时, 它仍然必须带有一个state关键字。
例子:
上面的规则定义了下面的动作:
限制这条规则最多产生200个状态。
启用源跟踪;限制产生的状态, 但仅限制基于此规则产生的状态
限制可同时产生状态的最大节点数为100
限制每个源IP地址最多可同时产生三个状态
可以在状态化的TCP连接中单独插入一组限制以完成三次握手。
max-src-conn number
限制一台主机用来完成三次握手的最大TCP连接数
在一个时间间隔内限制的最大新连接的数量。
上面两个选项会自动调用 source-track 规则选项并且不适用于 source-track global选项。
因为这些限制仅能用于TCP连接完成三次握手的过程, 还有更多的措施可以用来对付那些恶意的IP地址。
overload <table>
将一个恶意主机的IP地址加入以此命名的表格o
清除源地址产生的匹配此规则的其它所有状态。如果你指定了global(全局) , 将会删除这个源IP地址的所有状态, 不管是由哪个规则产生的。
例子:
上面的规则产生如下的结果:
限制每个源的最大连接数为100
每5秒最多可以有15个连接
将所有违反此限制主机的IP地址存入<abusive_hosts>表格内
对任何不良的IP地址来说, 原来由此规则产生的状态都会被清除。(译者:实际上不仅是匹配此规则的状态, 而是已经禁止接入了, quick有最高权限。)
TCP 标志
用标志匹配TCP数据包经常被用来过滤企图打开一个新连接的TCP数据包。下面列出了TCP的标志以及其含义:
F : FIN - Finish; 完成; 会话结束
S : SYN - Synchronize; 同步, 请求开始对话
R : RST - Reset; 重新设定, 丢弃一个连接
P : PUSH - Push; 推, 数据包被马上发送
A : ACK - 确认
U : URG - 紧急
E : ECE - 显式拥塞通告显示
W : CWR - 拥塞窗口减少
要让PF在评估一条规则时检查TCP标志要采用下面带有flags关键字的语法:
这里的 mask 部分告诉PF只检查指定的标志而 check 部分指定标志的头部必须是"on"的数据包才可用来匹配。使用any关键字来指定允许数据包头部使用任何组合的标志。
上面的规则放行SYN标志的TCP通讯, 同时仅看SYN和ACK标志。一个带有SYN和ECE标志的数据包会匹配上面的规则, 而一个带有SYN和ACK标志或仅有ACK标志的数据包则不匹配。
在OpenBSD 4.1和以后的版本中, 应用于TCP规则的默认标志为 flags S/SA , 加上OpenBSD 4.1默认的规则里的keep state选项, 可以看出下面两行的作用是相同的:
每条规则将匹配带有SYN标志而ACK标志为0的数据包, 并且为每个匹配的数据包产生一个状态项。默认的标志可以像上面描述的那样通过使用flags选项进行修改。
在OpenBSD 4.0和更早的版本中没有为任何规则设置默认的标志。每条规则不得不指定使用何种标志去匹配, 而且还要明确地使用keep state选项。
你应该小心地使用标志 —— 明白你正在做什么和为什么这样做, 并且谨慎地处理别人的建议, 因为很多这样的建议很糟糕。有人曾经这样建议过"仅为设置了SYN标志而非其它标志"的数据包产生状态, 这样的规则最终变成了这样:
这个理论是, 仅在TCP会话开始时产生状态, 并且这个会话应该以一个SYN标志开始, 而非其它。 可问题是一些站点开始使用ECN标志, 而且任何使用ECN的站点如果想与你建立连接都会被这条规则拒绝。一个好得多的建议是不指定任何标志, 并且让PF对你的规则应用默认标志。如果你真需要自己指定标志, 那么这种组合是安全的:
这是经过实践检验的和安全的, 如果通讯已经进行了净化, 它也没必须检查FIN和RST。净化会导致PF丢弃所有包含非法TCP标志组合(如:SYN 和 RST)的进站数据包, 并且规格化潜在的不明确组合(像:SYN 和 FIN)。
TCP SYN代理
通常, 当客户端初始化一个到服务器的TCP连接时, PF会放行两个终点间的握手数据包, 然而PF有能力代理握手过程, PF会先完成与客户端握手在发起一个与服务器的握手, 然后放行两端的数据包。这种做法的好处是在客户端完成握手过程前没有数据包发送给服务器, 它避免了欺骗式的TCP SYN floods对服务器造成的威胁, 因为伪造的客户端不能完成握手。
启用TCP SYN 代理是通过在过滤规则中使用 synproxy state 关键字完成的, 例如:
这条规则指示PF代理到web服务器的TCP连接。
因为synproxy state的工作方式包含了与keep state 和 modulate state同样的功能。
如果PF工作在 bridge(4) 模式时, SYN代理无法工作。
阻塞欺骗数据包
地址“欺骗”是恶意用户伪造源IP地址传送数据包, 这是为了隐藏他们真实的IP地址或冒充网络上的另一个节点。一旦用户被欺骗, 他们可以在不暴露自己真实的IP地址的情况下发起网络攻击或者获得只有授权的IP地址可以访问的服务。
PF通过 antispoof 关键字提供一些保护以抵御地址欺骗:
log
指定使用 pflogd(8) 记录匹配的数据包。
quick
如果一个数据包匹配了这条规则, PF会参照“胜者”规则并停止评估后面的规则集。
interface
这个网络接口将激活欺骗保护。也可以是一个接口的列表。
af
激活欺骗保护的地址, 可以是inet表示IPv4 或者inet6表示IPv6。
例子:
当一个规则集被加载后, 任何出现的 antispoof 关键字被扩展成两条过滤规则。假设网络接口fxp0有一个IP地址 10.0.0.1和子网掩码255.255.255.0(例如是一个 /24), 上面的过滤规则将扩展成:
这些规则完成两件事:
拒绝所有来自10.0.0.0/24网络的通讯, 这将阻止通过fxp0进入的数据包, 因为10.0.0.0/24网络在fxp0接口上, 所以来自那个网段的数据包被阻止, 不应该在任何其它接口上看到。
阻止来自10.0.0.1的所有进站通讯, 也就是fxp0上的IP地址。主机绝不会使用外部接口给自己发送数据包, 所以任何带有属于这台主机的源地址的数据包全会被当成恶意的。
注意: antispoof 规则扩展的过滤规则也会阻止通过loopback接口发送给本地的数据包。最佳方法是跳过loopback接口过滤, 但应用antispoof规则时这就成了一种必要:
使用antispoof应该仅限于被分配了IP地址的接口, 如果在没有IP地址的接口上应用antispoof, 规则集会变成像这样:
这些规则存在阻止所有接口上的所有进站通讯的风险。
单播反向路径转发
从OpenBSD 4.0开始, PF提供了一个单播反向路径转发(uRPF)功能。当一个数据包通过uRPF检查时, 这个数据包的源IP地址被从路由表中查出。如果在路由表项目里找到的出站接口就是该数据包进站使用的接口, 那么该数据包就通过了URPF检查, 如果接口不相同, 就说明该数据包可能使用了伪造的IP地址。
可以在规则里使用 urpf-failed 关键字对数据包进行uRPF检查:
注意uRPF检查仅在对称路由环境中才有意义。
uRPF 提供与 antispoof 规则相同的功能。
被动的操作系统指纹识别
被动OS指纹识别(OSFP)是一种被动地探测远程主机操作系统的一种方法, 它基于那台主机的TCP SYN数据包的某些特征。这些信息可以在过滤规则里做为标准。
PF通过比较TCP SYN数据包和指纹文件来判断远程主机的操作系统类型, 默认的指纹文件是 /etc/pf.os 。 一旦PF被启用, 当前的指纹列表可以通过下列命令查看:
在一个过滤规则里, 可以通过OS类型、版本、子类型、补丁级别指定一个指纹。所有这些项目可以用上面的命令列出, 过滤规则里可以使用 os 关键字指定一个指纹:
特殊的操作系统类型 unknown 匹配系统未知操作系统。
注意 以下几条:
操作系统指纹识别偶尔会出错, 原因是对方使用伪造的或经过技术处理的数据包, 这些数据包让他们的本来的操作系统看起来像是另一个。
对操作系统的某些修改和patchlevels可能改变堆栈行为并导致它不匹配本属于它的指纹文件或匹配其它指纹。
OSFP仅对TCP SYN数据包进行分析;它不能分析其它的协议或已经建立的连接。
IP选项
默认情况下, PF阻止带有IP选项集的数据包, 这可能使像nmap这样的"操作系统指纹识别程序"更难工作。如果你有一个程序需要放行诸如组播或IGMP这样的数据包, 你可以用 allow-opts 说明:
过滤规则集实例
下面是一个规则集的例子。这台计算机作为一个小型内部网络和Internet之间的一个防火墙。只有下面这些过滤规则;这个例子里不包含 queueing, nat, rdr 等规则。
[上一小节: 表格] [总目录] [小下一节: 网络地址转换]
--------------------------------------------------------------------------------
[email protected]
$OpenBSD: filter.html, v 1.50 2009/01/25 18:09:49 jasper Exp $
PF: 包过滤
--------------------------------------------------------------------------------
目录
介绍
规则语法
默认拒绝
允许通讯
quick关键字
保持状态
为UDP保持状态
状态化跟踪选项
TCP标志
TCP SYN代理
阻塞欺骗数据包
单播反向路径转发
被动的操作系统指纹识别
IP选项
过滤规则集实例
--------------------------------------------------------------------------------
介绍
当数据包经过一个网络接口时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]
action
direction
在一个接口上数据包传送的方向, in或out 。
log
指定通过 pflogd(8).记录数据包, 如果规则产生了状态则仅纪录状态的建立。要记录所有的数据包使用 log (all).
quick
如果一个数据包匹配了一条指定了quick关键字的规则, 那么这条规则被认为是最后一条匹配的规则, 并会立即按此规则对数据包执行指定的动作。
interface
数据包经过的网络接口名称或组。可以使用 ifconfig(8) 命令将网络接口添加到任意的组中。内核也会自动产生几个组:
egress组, 包括了包含默认路由的网络接口。
克隆接口的接口家族组。例如: ppp或carp.
这使所有匹配规则的数据包可以分别通过ppp或carp接口。
af
数据包的地址族(address family), 也就是对应IPv4的inet, 或者对应IPv6的inet6。 通常PF可以通过源和/或目标的地址确定此参数类型。
protocol
数据包的第4层协议:
tcp
udp
icmp
icmp6
一个 /etc/protocols 定义的有效的协议名称。
0-255之间的一个协议编号
通过列表定义的一组协议。
src_addr, dst_addr
分别代表IP报头的源和目标地址。地址可以指定为:
一个单独的IPv4 或 IPv6 地址。
一个 CIDR 网段。
一个完全限定域名, 它必须在载入规则时能够被DNS解析, 解析出的IP地址会取代规则中的域名。
一个网络接口或组的名称。所有分派给此接口的IP地址会取代规则中的网络接口或组的名称。
一个网络接口加上 /子网掩码 (例如, /24)。 所有此接口上的IP地址与子网掩码组合成一个CIDR网段, 此地址段会替换规则中的网络接口/子网掩码。
一个( )内的网络接口或组的名称。它告诉PF如果此接口或组的IP地址发生变化时需要更新规则。有些网络接口是通过DHCP或拨号获得IP地址的, 这种方式对它们很有效, 因为当其IP地址变化时不用重新加载规则集。
一个带有如下任何一个修饰符得网络接口名称:
:network - 代替CIDR网段(例如, 192.168.0.0/24) <---24表示前三个数字相同, 也就是在192.168.0.1到192.168.0.254的范围内
:broadcast - 代替网络广播地址(例如, 192.168.0.255)
:peer - 代替点到点链接中对方的IP地址
另外, :0 修饰符可以附加到一个接口名称或上面任何修饰符的后面, 它指示PF将规则中的名称替换为IP地址时不包含别名的IP地址。 这些修饰符也可以应用在括号内的接口上。 例子: fxp0:network:0
一个表格。
关键字 urpf-failed 应用于源地址, 它指示PF通过uRPF检查执行此规则。
任何上面的规则加上了否定修饰符 ! ("not") 修饰符。
使用一个 列表 表示的一组地址。
关键字 any 代表所有地址。
关键字 all 是 from any to any 的缩写。
src_port, dst_port
分别代表第四层包头的源和目的端口。端口可以指定为:
一个1到65535之间的数字
一个 /etc/services 内指定的有效的服务名称 <---例如:www 代表web服务的80端口, ftp 代表ftp服务的21端口
用 列表 表示的一组端口
一个范围:
!= (不等于)
< (小于)
> (大于)
<= (小于等于)
>= (大于等于)
>< (范围内)
<> (范围外)
最后这两个是二元运算符(带有两个参数) 并且这两个参数不包含在范围内。
: (包含在范围内)
也是二元运算符, 但是两个参数包含在范围内。
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
指定是否在数据包匹配规则时保持状态信息。
keep state - 作用于 TCP, UDP 和 ICMP。在OpenBSD 4.1和以后的版本中, 这是所有过滤规则的默认选项。
modulate state - 仅作用于TCP。 PF将为所有匹配这一规则的数据包产生强壮的初始化序号(ISNs)。
synproxy state - 代理进入的TCP连接以保护服务器免受TCP SYN 洪水式攻击。这个选项包含了keep state和modulate 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有能力快速测定一个通过防火墙的数据包是否属于一个已经建立的连接。如果这个数据包属于已经建立的连接, 就不经规则集检查而直接被允许通过防火墙。
保持状态有很多优点, 包括简化规则集和提供更好的数据包过滤性能。 PF有能力将任何方向的数据包与状态表中的项目匹配, 这意味着不需要编写一个回程通讯的规则, 并且因为数据包匹配状态化连接不需要经过规则集的检查, 所以PF处理这类数据包的时间显著减少。
当一条规则导致了状态, 第一个匹配此规则的数据包将在发送者和接收者之间建立一个"状态", 现在不仅是由发送者到接收者的数据包因为匹配这个状态项而被防火墙放行, 就连从接收者至发送者的答复数据包(回程通讯)也会被直接放行。
从OpenBSD 4.1开始, 任何过滤规则在一个数据包匹配它时会自动产生一个状态项, 先前的OpenBSD版本中过滤规则不得不明确地使用 keep state 选项。
OpenBSD 4.1及以后版本的例子:
代码: 全选
pass out on fxp0 proto tcp from any to any
OpenBSD 4.0及以前版本的例子:
代码: 全选
pass out on fxp0 proto tcp from any to any keep state
这些规则允许在fxp0接口上的任何出站TCP通讯以及回复通讯通过防火墙。保持状态是一个非常好的特性, 使用它可以显著的提高你防火墙的性能, 因为状态查询比用规则集检验数据包快得多。
modulate state 选项的作用就像 keep state, 除了它仅能应用于TCP数据包。 使用 modulate state选项时, 出站连接的ISN(初始化序号)是随机的, 这对保护某些选择ISN有困难的操作系统的初始化连接是十分有用的, 从OpenBSD 3.5开始, modulate state 选项可以应用于指定了非TCP协议的规则。
为出站的TCP, UDP和ICMP数据包保持状态并调整TCP初始化序号:
代码: 全选
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接口进行传送。
注意 nat, binat, 和 rdr 规则会为匹配的连接隐含地产生状态直至连接完全通过防火墙规则集。
为UDP保持状态
有人也许听说过"没人能为UDP产生状态, 因为UDP是一个无状态协议!", 尽管一个UDP会话没有任何状态(有明确的通讯起点和终点)的概念是对的, 但它对PF为UDP会话产生状态的能力没有任何影响。当遇到没有"开始"和"结束"数据包的协议时, PF仅简单地检查一个匹配的数据包已通过的时间, 如果超过时限, 状态就会被清除。这个时限在 pf.conf 文件内用 timeout values 选项 进行设置。
状态化跟踪选项
可以用以下几种指定的选项控制过滤规则产生的状态项的动作:
max number
限制此规则可以产生状态项的最大值。如果达到了此最大值, 这条规则将无法为数据包产生状态直至已经存在的状态数降到最大值以下。
no state
防止规则自动产生一个状态项。
source-track
这个选项对每个源IP地址产生的状态数量进行跟踪, 它有两种格式:
source-track rule - 此规则可以产生状态的最大数量受max-src-nodes 和 max-src-states 选项限制。只有此规则仅限制自己产生的状态项数目。
source-track global - 这个选项用来限制所有规则产生的状态数量。每个规则可以指定不同的 max-src-nodes 和 max-src-states 选项, 然而每个规则产生的状态项都会被计入状态项总数内。
被跟踪的全局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 state, or synproxy state).如果有多个选项就以逗号分开。在OpenBSD 4.1和以后版本中 keep state 选项变成了所有过滤规则隐含的默认选项, 尽管如此, 当指定状态化选项时, 它仍然必须带有一个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)
上面的规则定义了下面的动作:
限制这条规则最多产生200个状态。
启用源跟踪;限制产生的状态, 但仅限制基于此规则产生的状态
限制可同时产生状态的最大节点数为100
限制每个源IP地址最多可同时产生三个状态
可以在状态化的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]
例子:
代码: 全选
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)
上面的规则产生如下的结果:
限制每个源的最大连接数为100
每5秒最多可以有15个连接
将所有违反此限制主机的IP地址存入<abusive_hosts>表格内
对任何不良的IP地址来说, 原来由此规则产生的状态都会被清除。(译者:实际上不仅是匹配此规则的状态, 而是已经禁止接入了, quick有最高权限。)
TCP 标志
用标志匹配TCP数据包经常被用来过滤企图打开一个新连接的TCP数据包。下面列出了TCP的标志以及其含义:
F : FIN - Finish; 完成; 会话结束
S : SYN - Synchronize; 同步, 请求开始对话
R : RST - Reset; 重新设定, 丢弃一个连接
P : PUSH - Push; 推, 数据包被马上发送
A : ACK - 确认
U : URG - 紧急
E : ECE - 显式拥塞通告显示
W : CWR - 拥塞窗口减少
要让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
上面的规则放行SYN标志的TCP通讯, 同时仅看SYN和ACK标志。一个带有SYN和ECE标志的数据包会匹配上面的规则, 而一个带有SYN和ACK标志或仅有ACK标志的数据包则不匹配。
在OpenBSD 4.1和以后的版本中, 应用于TCP规则的默认标志为 flags S/SA , 加上OpenBSD 4.1默认的规则里的keep state选项, 可以看出下面两行的作用是相同的:
代码: 全选
pass out on fxp0 proto tcp all flags S/SA keep state
pass out on fxp0 proto tcp all
每条规则将匹配带有SYN标志而ACK标志为0的数据包, 并且为每个匹配的数据包产生一个状态项。默认的标志可以像上面描述的那样通过使用flags选项进行修改。
在OpenBSD 4.0和更早的版本中没有为任何规则设置默认的标志。每条规则不得不指定使用何种标志去匹配, 而且还要明确地使用keep state选项。
代码: 全选
pass out on fxp0 proto tcp all flags S/SA keep state
你应该小心地使用标志 —— 明白你正在做什么和为什么这样做, 并且谨慎地处理别人的建议, 因为很多这样的建议很糟糕。有人曾经这样建议过"仅为设置了SYN标志而非其它标志"的数据包产生状态, 这样的规则最终变成了这样:
代码: 全选
. . . flags S/FSRPAUEW bad idea!!
这个理论是, 仅在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 state 和 modulate 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
这些规则完成两件事:
拒绝所有来自10.0.0.0/24网络的通讯, 这将阻止通过fxp0进入的数据包, 因为10.0.0.0/24网络在fxp0接口上, 所以来自那个网段的数据包被阻止, 不应该在任何其它接口上看到。
阻止来自10.0.0.1的所有进站通讯, 也就是fxp0上的IP地址。主机绝不会使用外部接口给自己发送数据包, 所以任何带有属于这台主机的源地址的数据包全会被当成恶意的。
注意: 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 匹配系统未知操作系统。
注意 以下几条:
操作系统指纹识别偶尔会出错, 原因是对方使用伪造的或经过技术处理的数据包, 这些数据包让他们的本来的操作系统看起来像是另一个。
对操作系统的某些修改和patchlevels可能改变堆栈行为并导致它不匹配本属于它的指纹文件或匹配其它指纹。
OSFP仅对TCP SYN数据包进行分析;它不能分析其它的协议或已经建立的连接。
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
[上一小节: 表格] [总目录] [小下一节: 网络地址转换]
--------------------------------------------------------------------------------
[email protected]
$OpenBSD: filter.html, v 1.50 2009/01/25 18:09:49 jasper Exp $