OpenBSD PF(Packet Filter)学习

MacOS的内核属于Unix,而在Unix中并没有iptables,而是pf(packet filter)。

开始

激活

1
2
> pfctl -e  # 启动PF
> pfctl -d # 关闭PF

注意,这仅仅只是启动和关闭PF,实际不会载入规则集(ruleset)。规则集需要单独载入,或者在启动时载入。

配置

系统启动时会从/etc/pf.conf(默认配置文件)载入配置规则。对于其他的规则,可以在运行过程中载入,具备足够的灵活性。

/etc/pf.conf文件有7个部分:

  • Macros(宏):用户定义的变量,包括IP地址,接口名称等等
  • Tables(表): 一种用来保存IP地址列表的结构
  • Options(选项): 控制PF如何工作的变量
  • Scrub(整形): 重新处理数据包,进行正常化和碎片整理
  • Queue(排队): 提供带宽控制和数据包优先级控制
  • Translation(转换): 控制网络地址转换和数据包重定向
  • Filter Rules(过滤规则): 在数据包通过接口时允许进行选择性的过滤和阻止

除去宏和表,其他的部分在配置文件中应该按照如上顺序出现,否则会报错。
空行会被忽略,# 为行注释符。

控制

通过pfctl对pf进行管理,示例如下:

  • pfctl -f /etc/pf.conf 载入 pf.conf 文件
  • pfctl -nf /etc/pf.conf 解析文件,但不载入
  • pfctl -Nf /etc/pf.conf 只载入文件中的NAT规则
  • pfctl -Rf /etc/pf.conf 只载入文件中的过滤规则

  • pfctl -sn 显示当前的NAT规则

  • pfctl -sr 显示当前的过滤规则
  • pfctl -ss 显示当前的状态表
  • pfctl -si 显示过滤状态和计数
  • pfctl -sa 显示任何可显示的

完整命令查看man手册。

列表和宏

列表 - Lists

使用{}大括号将相似条目(协议、端口、地址等)括起来,条目之间的逗号可有可无。 如:

block out on en0 from { 192.168.0.1, 10.5.32.6 } to any

宏 - Macros

宏是用户定义的变量,用来指定IP地址、端口、接口等等。一次配置,多处使用。 

宏名称以字母开头,可以包括字母,数字和下划线,不能为关键字。 引用时,在宏名称前加$

表 - Table

表用来存储IP地址的,特点是比列表快且剩资源。

表配置

使用table关键字创建表,表名总是被<>符号括起来。如下关键字必须在创建表时指定:

  • constant - 这类表得内容一旦创建出来就不能被改变。如果这个属性没有指定,可以使用pfctl添加和删除表里的地址。
  • persist - 即使没有规则引用这类表,内核也会把它保留在内存中。如果这个属性没有指定,当最后引用它的规则被取消后内核自动把它移出内存。
1
2
3
4
# pf.conf
table <goodguys> { 192.0.2.0/24 } #建表
table <spammers> persist file "/etc/spammers" # 从文件中加载条目
pass in on en0 from <goodguys> to any # 使用

命令操作

1
2
3
> pfctl -t spammers -T add 218.70.0.0/16    # 在spammers表中添加条目
> pfctl -t spammers -T show # 显示
> pfctl -t spammers -T delete 218.70.0.0/16

指定地址

除了使用IP地址外,也可以使用主机名指定主机(会被解析为IP地址)

地址匹配

表中多条规则,地址查询时匹配最接近的规则。

1
2
3
table <goodguys> { 172.16.0.0/16, !172.16.1.0/24, 172.16.1.100 } 
block in on dc0 all
pass in on dc0 from <goodguys> to any

任何自dc0上数据包都会把它的源地址和goodguys表中的地址进行匹配:

  • 172.16.50.5 - 精确匹配172.16.0.0/16; 数据包符合可以通过
  • 172.16.1.25 - 精确匹配!172.16.1.0/24; 数据包匹配表中的一条规则,但规则是“非”(使用“!”进行了修改);数据包不匹配表会被阻塞。
  • 172.16.1.100 - 准确匹配172.16.1.100; 数据包匹配表,运行通过
  • 10.1.4.55 - 不匹配表,阻塞。

包过滤

包过滤是在数据包通过网络接口时进行选择性的pass或者block。pf检查包时使用的标准是基于的3层(IPV4或者IPV6)和4层(TCP, UDP, ICMP, ICMPv6)包头。最常用的标准地址、端口、协议。

规则集指定数据包在规则集匹配时通过或者阻塞。规则集从前往后顺序执行。除非数据包匹配的规则包含quick关键字,否则数据包在最终执行动作前会通过所有的规则检验。最后匹配的规则具有决定性,决定了数据包最终的执行结果。

一条潜规则: 如果数据包和规则集中的所有规则都不匹配,则它会被pass。

规则语法

action direction [log] [quick] on interface [af] [proto protocol] from src_addr [port src_port] to dst_addr [port dst_port] [tcp_flags] [state]

  • action

    pass 数据包传递给核心进行进一步处理。
    block 根据block-policy选项指定的方法进行处理。默认的动作可以修改为阻塞丢弃或者阻塞返回。

  • direction

    in 进来
    out 出去

  • log

    指定数据包被pflogd(进行日志记录。如果规则指定了keep state, modulate state,synproxy state 选项,则只有建立了连接的状态被记录,log-all记录所有日志。

  • quick

    如果数据包匹配的规则指定了 quick 关键字,则这条规则被认为时最终的匹配规则,指定的动作会立即执行。

  • interface

    数据包通过的网络接口的名称或组。组是接口的名称但没有最后的整数。比如ppp 或 fxp,会使得规则分别匹配任何ppp或者fxp接口上的任意数据包。

  • af

    Address Family IPv4 或者 IPv6。 通常自动识别,不用管。

  • protocol

    /etc/protocols 中的协议名称 /
    0-255 的协议号

  • src_addr / dst_addr

    各种地址: ip/CIDR/域名/网络接口名称/带掩码的网络接口名称/带括号()的网络接口名称(自动更新接口IP)
    /其它(看不懂的部分不管)

  • src_port, dst_port

    1-65535//etc/services中的名称/端口list/范围(!= < > <= >= <> ><)

  • tcp_flags

    指定使用TCP协议时TCP头中必须设定的标记。 标记指定的格式是: flags check/mask,例如: flags S/SA -这指引PF只检查S和A(SYN and ACK)标记,如果SYN标记是“on”则匹配。 OpenBSD 4.1之后,flags S/SA 默认应用到TCP的过滤规则。

  • state

    指定状态信息在规则匹配时是否保持。
    keep state - 对 TCP, UDP, ICMP起作用。modulate state - 只对 TCP起作用。PF会为匹配规则的数据包产生强壮的初始化序列号。 synproxy state - 代理外来的TCP连接以保护服务器不受TCP SYN FLOODs欺骗。这个选项包含了keep state 和 modulate state 的功能。

默认拒绝

为了安全,可以配置默认拒绝。 在过滤规则的开头配置:

1
2
block in all 
block out all

通过流量

流量必须被明确的允许通过防火墙或者被默认拒绝的策略丢弃。

quick关键字

匹配后立即执行。

状态保持

PF一个非常重要的功能是“状态保持”或者“状态检测”。状态检测指PF跟踪或者处理网络连接状态的能力。通过存贮每个连接的信息到一个状态表中,PF能够快速确定一个通过防火墙的数据包是否属于已经建立的连接。如果是,它会直接通过防火墙而不用再进行规则检验。

从OpenBSD 4.1开始,所有的过滤规则自动添加keep state

modulate state(状态调整) – 搞不懂

UDP状态保持

大家都听说过,“不能为UDP产生状态,因为UDP是无状态的协议”。确实,UDP通信会话没有状态的概念(明确的开始和结束通信),这丝毫不影响PF为 UDP会话产生状态的能力。对于没有开始和结束数据包的协议,PF仅简单追踪匹配的数据部通过的时间。如果到达超时限制,状态被清除,超时的时间值可以在 pf.conf配置文件中设定。

状态跟踪

对连接状态进行控制管理。

  • max number

    限制连接数

  • no state

    禁止创建状态保持

  • source-track

    source-track rule 限制由特定规则产生的状态条目数量,由max-src-nodesmax-src-states共同决定。

    source-track global 全局限制状态条目数量。通过src-nodes控制全局跟踪的源IP地址总数。

  • max-src-nodes number

    使用source-track的情况下,限制原IP数量。

  • max-src-states number

    使用source-track的情况下,限制单IP的连接数量。

示例:

1
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)

  • max-src-conn number

    Limit the maximum number of simultaneous TCP connections which have completed the 3-way handshake that a single host can make.

  • max-src-conn-rate number / interval

    限制新连接的建立速度。

  • overload \<table>

    把有问题的主机的IP地址放到指定的表中。

  • flush [global]

    关闭任何符合此规则并由此源IP创建的其他状态。当指定全局时,不管哪个规则创建该状态,都将终止与该源IP匹配的所有状态。

示例:

1
2
3
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标记和他们的意义如下所列:

  • F : FIN - 结束; 结束会话
  • S : SYN - 同步; 表示开始会话请求
  • R : RST - 复位;中断一个连接
  • P : PUSH - 推送; 数据包立即发送
  • A : ACK - 应答
  • U : URG - 紧急
  • E : ECE - 显式拥塞提醒回应
  • W : CWR - 拥塞窗口减少

默认 flags S/SA。 【比较复杂,略过】

TCP SYN 代理 (os X下不稳定)

就是代理握手,防止TCP SYN FLOOD DOS攻击. 由于synproxy state工作的方式,它具有keep state 和 modulate state一样的功能。 如果PF工作在桥接模式下,SYN代理不会起作用(不懂)。

示例:

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

阻塞欺骗数据包

地址欺骗相关,,,, 忽略。

Unicast Reverse Path Forwarding

OpenBSD 4.0引入,类似于交换机的端口MAC地址绑定,避免ARP欺骗。
固定使用:

1
block in quick from urpf-failed label uRPF

被动操作系统识别

被动操作系统识别是通过基于远端主机TCP SYN数据包中某些特征进行操作系统被动检测的技术。这些信息可以作为标准在过滤规则中使用。PF检测远端操作系统是通过比较TCP SYN数据包中的特征和已知的特征文件对照来确定的,特征文件默认是/etc/pf.os。如果PF起作用,可是使用下面的命令查看当前的特征列表。
pfctl -s osfp

使用:

1
2
3
4
pass in on $ext_if any os OpenBSD keep state 
block in on $ext_if any os "Windows 2000"
block in on $ext_if any os "Linux 2.4 ts"
block in on $ext_if any os unknown

IP 选项

默认情况下,PF阻塞带有IP选项得数据包。这可以使得类似nmap得操作系统识别软件工作困难。如果你的应用程序需要通过这样的数据包,例如多播或者IGMP,你可以使用allow-opts关键字。
pass in quick on fxp0 all allow-opts

过滤规则实例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
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

NAT(网络地址转换)

NAT就是NAT。
NAT允许使用RFC1918中定义的保留地址族:

  • 10.0.0.0/8 (10.0.0.0 - 10.255.255.255)
  • 172.16.0.0/12 (172.16.0.0 - 172.31.255.255)
  • 192.168.0.0/16 (192.168.0.0 - 192.168.255.255)

NAT如何工作

NAT转换表。

NAT和包过滤

转换后的数据包仍然会通过过滤引擎,根据定义的过滤规则进行阻塞或者通过。唯一的例外是如果nat规则中使用了pass关键字,会使得经过nat的数据包直接通过过滤引擎。

还要注意由于转换是在过滤之前进行,过滤引擎所看到的是经过转换后的ip地址和端口的数据包。

IP转发

实现该功能需要转发数据,所以需要如下配置。

1
2
> sysctl -w net.inet.ip.forwarding=1 
> sysctl -w net.inet6.ip6.forwarding=1 (if using IPv6)

以上配置是临时生效,可以增加如下行到/etc/sysctl.conf文件中,可长期有效。

1
2
net.inet.ip.forwarding=1 
net.inet6.ip6.forwarding=1

配置NAT

nat [pass] on interface [af] from src_addr [port src_port] to dst_addr [port dst_port] -> ext_addr [pool_type] [static-port]

示例1: 正确,但不推荐。

1
nat on tl0 from 192.168.1.0/24 to any -> 24.5.0.5

示例2:推荐用法(tl0为外部网卡,dc0为内部网卡,dc0:network表示dc0的整个网段。(tl0)表示当tl0网卡的IP变动时,可以更新到规则库中。)

1
nat on tl0 from dc0:network to any -> (tl0)

双向映射 (1:1 映射)

双向映射可以通过使用binat规则建立。Binat规则建立一个内部地址和外部地址一对一的映射。和NAT规则不同,binat规则中的tcp和udp端口不会被修改。

1
2
3
web_serv_int = "192.168.1.100" 
web_serv_ext = "24.5.0.6"
binat on tl0 from $web_serv_int to any -> $web_serv_ext

转换规则例外

1
2
no nat on tl0 from 192.168.1.10 to any 
nat on tl0 from 192.168.1.0/24 to any -> 24.2.74.79

检查 NAT 状态

1
pfctl -s state

重定向 (端口转发)

外网流量转发到NAT网关内的主机。
例如:

1
2
3
rdr on tl0 proto tcp from 27.146.49.14 to any port 80 -> 192.168.1.20 
rdr on tl0 proto tcp from 16.114.4.89 to any port 80 -> 192.168.1.22
rdr on tl0 proto tcp from 24.2.74.178 to any port 80 -> 192.168.1.23

重定向和包过滤

注意:转换后的数据包仍然会通过过滤引擎,根据定义的过滤规则进行阻塞或者通过。唯一的例外是如果rdr规则中使用了pass关键字,会使得重定向的数据包直接通过过滤引擎。

还要注意由于转换是在过滤之前进行,过滤引擎所看到的是在匹配rdr规则经过转换后的目标ip地址和端口的数据包。

rdr on tl0 proto tcp from 192.0.2.1 to 24.65.1.13 port 80 -> 192.168.1.5 port 8000

rdr前 :192.0.2.1(某随机端口) –> 24.65.1.13:80

rdr后 :192.0.2.1(某随机端口) –> 192.168.1.5:8000

安全隐患(略)

重定向和反射

通常,重定向规则是用来将因特网上到来的连接转发到一个内部网络或者局域网的私有地址。例如:

1
2
server = 192.168.1.40 
rdr on $ext_if proto tcp from any to $ext_if port 80 -> $server port 80

但是,当一个重定向规则被从局域网上的客户端进行测试时,它不会正常工作。这是因为重定向规则仅适用于通过指定端口(上述例子中的外部端口$ext_if)的数据包。从局域网上的主机连接防火墙的外部地址,并不意味着数据包会实际的通过外部接口。防火墙上的TCP/IP栈会把到来的数据包的目的地址 在通过内部接口时与它自己的IP地址或者别名进行对比检测。那样的数据包不会真的通过外部接口,栈在任何情况下也不会建立那样的通道。因而,PF永远也不会在外部接口上看到那些数据包,过滤规则由于指定了外部接口也不会起作用。

即使为内部接口配置重定向规则也达不到预期效果。当本地的客户端连接防火墙的外部地址时,初始化的TCP握手数据包是通过内部接口到达防火墙的。重定向规则 确实起作用了,目标地址被替换成了内部服务器,数据包通过内部接口转发到了内部的服务器。但源地址没有进行转换,仍然包含的是本地客户端的IP地址,因此 服务器把它的回应直接发送给了客户端。防火墙永远收不到应答不可能返回客户端信息,客户端收到的应答不是来自它期望的源(防火墙)会被丢弃,TCP握手失败,不能建立连接。

针对如上问题,有如下解决方案:

  • 水平分割 DNS

    针对内网和外网配置不同的DNS解析结果。

  • 将服务器移到独立的本地网络

    增加单独的网络接口卡到防火墙,把本地的服务器从和客户端同一个网段移动到专用的网段(DMZ)可以让本地客户端按照外部重定向连接的方法一样重定向。

  • TCP 代理

    在防火墙上设置一个TCP代理用于接收请求,同时额外建立一条与目标服务器的连接,从而充当起中转者的角色。
    简单的代理可以使用 inetdnc 建立。下面的 /etc/inetd.conf 中的条目建立一个监听套接字绑定到lookback地址(127.0.0.1)和端口5000。连接被转发到服务器192.168.1.10的80端口。

    1
    127.0.0.1:5000 stream tcp nowait nobody /usr/bin/nc nc -w 20 192.168.1.10 80

下面的重定向规则转发内部接口的80端口到代理:

1
rdr on $int_if proto tcp from $int_net to $ext_if port 80 -> 127.0.0.1 port 5000

  • RDR和NAT结合

    通过对内部接口增加NAT规则,上面说的转换后源地址不变的问题可以解决。

    1
    2
    3
    rdr on $int_if proto tcp from $int_net to $ext_if port 80 -> $server 
    no nat on $int_if proto tcp from $int_if to $int_net
    nat on $int_if proto tcp from $int_net to $server port 80 -> $int_if

这会导致由客户端发起的初始化连接在收到内部接口的返回数据包时转换回来,客户端的源ip地址被防火墙的内部接口地址代替。内部服务器会回应防火墙的内部 接口地址,在转发给本地客户端时可以反转NAT和RDR。这个结构是非常复杂的,因为它为每个反射连接产生了2个单独的状态。必须小心配置防止NAT规则 应用到了其他流量,例如连接由外部发起(通过其他的重定向)或者防火墙自己。注意上面的rdr规则会导致TCP/IP栈看到来自内部接口带有目的地址是内部网络的数据包。

规则生成捷径

PF提供了许多方法来进行规则集的简化。一些好的例子是使用宏和列表。另外,规则集的语言或者语法也提供了一些使规则集简化的捷径。首要的规则是,规则集越简单,就越容易理解和维护。

使用宏 (略)

使用列表 (略)

PF 语法

  • 减少关键字

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    # 原语法
    block in all
    block out all
    # 简化语法 没有指明方向即为双向。
    block all
    # ---------------------------------
    # 原语法
    block in on rl0 all
    pass in quick log on rl0 proto tcp from any to any port 22 keep state
    # 简化语法 "from any to any" 和 "all" 在规则中省略。
    block in on rl0
    pass in quick log on rl0 proto tcp to port 22 keep state
  • Return 简化

    1
    2
    3
    4
    5
    6
    7
    8
    9
    # 用于阻塞数据包,回应TCP RST或者ICMP不可到达的规则集。 
    block in all
    block return-rst in proto tcp all
    block return-icmp in proto udp all
    block out all
    block return-rst out proto tcp all
    block return-icmp out proto udp all
    # 简化语法 当PF看到return关键字,PF可以智能回复合适应答,或者完全不回复,取决于要阻塞的数据包使用的协议
    block return
  • 关键字顺序

    1
    2
    3
    4
    # 在大多数情况下,关键字的顺序是非常灵活的。
    pass in log quick on rl0 proto tcp to port 22 flags S/SA keep state queue ssh label ssh
    # 也可以这么写
    pass in quick log on rl0 proto tcp to port 22 queue ssh keep state label ssh flags S/SA

运行选项

运行选项是控制pf操作的选择。这些选项在pf.conf中使用set指定。

set block-policy option

设定过滤规则中指定的block动作的默认行为。 注意单独的过滤规则可以重写默认的响应。

  • drop - 数据包悄然丢弃.
  • return - TCP RST 数据包返回给遭阻塞的TCP数据包,ICMP不可到达数据包返回给其他。

set debug option

设定pf的调试级别。

  • none - 不显示任何调试信息。
  • urgent - 为严重错误产生调试信息,这是默认选择。
  • misc - 为多种错误产生调试信息。(例如,收到标准化/整形的数据包的状态,和产生失败的状态)。.
  • loud - 为普通条件产生调试信息(例如,收到被动操作系统检测信息状态)。

set fingerprints file

设定应该装入的进行操作系统识别的操作系统特征文件来,默认是 /etc/pf.os.

set limit option value

  • frags - 在内存池中进行数据包重组的最大数目。默认是5000。
  • src-nodes - 在内存池中用于追踪源地址(由stick-address 和 source-track选项产生)的最大数目,默认是10000。
  • states - 在内存池中用于状态表(过滤规则中的keep state)的最大数目,默认是10000。

set loginterface interface

设定PF要统计进/出流量和放行/阻塞的数据包数目的接口。同一时间只能统计一个接口。注意不管loginterface是否设置,match、bad-offset等计数器和状态表计数器总会被记录。

set optimization option

为以下的网络环境优化PF:

  • normal - 适用于绝大多数网络,这是默认项。
  • high-latency - 高延时网络,例如卫星连接。
  • aggressive - 自状态表中主动终止连接。这可以大大减少繁忙防火墙的内存需求,但要冒空闲连接被过早断开的风险。
  • conservative - 特别保守的设置。这可以避免在内存需求过大时断开空闲连接,会稍微增加CPU的利用率。

set ruleset-optimization option

Control operation of the PF ruleset optimizer.

  • none - disable the optimizer altogether.
  • basic - enables the following ruleset optimizations:
  1. remove duplicate rules
  2. remove rules that are a subset of another rule
  3. combine multiple rules into a table when advantageous
  4. re-order the rules to improve evaluation performance
  • profile - uses the currently loaded ruleset as a feedback profile to tailor the ordering of quick rules to actual network traffic.
    Starting in OpenBSD 4.2, the default is basic. See pf.conf(5) for a more complete description.

set skip on interface

Skip all PF processing on interface. This can be useful on loopback interfaces where filtering, normalization, queueing, etc, are not required. This option can be used multiple times. By default this option is not set.

set state-policy option

设定PF在状态保持时的行为。这种行为可以被单条规则所改变。

  • if-bound - 状态绑定到产生它们的接口。如果流量匹配状态表种条目但不是由条目中记录的接口通过,这个匹配会失败。数据包要么匹配一条过滤规则,或者被丢弃/拒绝。
  • group-bound - 行为基本和if-bound相同,除了数据包允许由同一组接口通过,例如所有的ppp接口等。
  • floating - 状态可以匹配任何接口上的流量。只要数据包匹配状态表条目,不管是否匹配它通过的接口,都会放行。这是默认的规则。

set timeout option value

  • interval - seconds between purges of expired states and packet fragments. The default is 10.
  • frag - seconds before an unassembled fragment is expired. The default is 30.
  • src.track - seconds to keep a source tracking entry in memory after the last state expires. The default is 0 (zero).

例如:

1
2
3
4
5
6
7
8
9
set timeout interval 10
set timeout frag 30
set limit { frags 5000, states 2500 }
set optimization high-latency
set block-policy return
set loginterface dc0
set fingerprints "/etc/pf.os.test"
set skip on lo0
set state-policy if-bound

流量整形 (数据包标准化)

流量整形是将数据包标准化避免最终的数据包出现非法的目的。流量整形指引同时也会重组数据包碎片,保护某些操作系统免受某些攻击,丢弃某些带有非法联合标记的TCP数据包。

scrub in all 对所有接口上的数据包进行流量整形。

选项

  • no-df

    在IP数据包头中清除不分片位的设置。某些操作系统已知产生设置不分片位的分片数据包。尤其是对于NFS。流量整形(scrub)会丢弃这类数据包除非设 置了no-df选项。某些操作系统产生带有不分片位和用0填写IP包头中分类域,推荐使用no-df和random-id 联合使用解决。

  • random-id

    用随机值替换某些操作系统输出数据包中使用的可预测IP分类域的值这个选项仅适用于在选择的数据包重组后不进行分片的输入数据包。

  • min-ttl num

    增加IP包头中的最小存活时间(TTL)。

  • max-mss num

    增加在TCP数据包头中最大分段值(MSS)。

  • fragment reassemble

    在传递数据包到过滤引擎前缓冲收到的数据包碎片,重组它们成完整的数据包。优点是过滤规则仅处理完整的数据包,忽略碎片。缺点是需要内存缓冲数据包碎片。这是没有设置分片选项时的默认行为。这也是能和NAT一起工作的唯一分片选项。

  • fragment crop

    导致重复的碎片被丢弃,重叠的被裁剪。与碎片重组不同,碎片不会被缓冲,而是一到达就直接传递。

  • fragment drop-ovl

    fragment crop 相似,除了所有重复和重叠的碎片和其他更多的通信碎片一样被丢弃。

  • reassemble tcp

    TCP连接状态标准化。当使用了 scrub reassemble tcp时,方向(进/出)不用说明,会执行下面的标准化过程:

    1. 连接双方都不允许减少它们的IP TTL值。这样做是为了防止攻击者发送数据包到防火墙,影响防火墙保持的连接状态,使数据包在到达目的主机前就过期。所有数据包的TTL都为了这个连接加到了最大值。
    2. 用随机数字调整TCP数据包头中的 RFC1323 时间戳。这可以阻止窃听者推断主机在线的时间和猜测NAT网关后面有多少主机。

示例:

1
2
scrub in on fxp0 all fragment reassemble min-ttl 15 max-mss 1400 scrub in on fxp0 all no-df
scrub on fxp0 all reassemble tcp

锚(Anchors)

除了主规则集,PF也提供子规则集。 由于子规则集能通过pfctl动态操作,所以可以通过子规则集动态的调整filter、nat、rdr和binat规则。

子规则集通过使用锚(Anchor)来附加到主规则集。有四种类型的anchor 规则:

  • anchor name - evaluate(解析)锚中的所有过滤器规则
  • binat-anchor name - 评估锚名称中的所有binat规则
  • nat-anchor name - 评估锚名称中的所有nat规则
  • rdr-anchor name - 评估锚名称中的所有rdr规则

Anchor可以嵌套,允许子规则集串联起来。anchor规则在当前位置进行解析,也就是说锚点下的规则是受限于锚规则的。(大概理解)

Anchors

anchor是一个过滤器和/或转换规则、其他anchors的集合,并进行了命名。 当PF在主规则集中遇到anchor规则时,它将评估(evaluate)anchor中包含的规则。

示例:

1
2
3
ext_if = "fxp0"
block on $ext_if all # 默认拒绝所有的进出流量。
pass out on $ext_if all keep state anchor goodguys # 允许符合goodguys锚规则的流量出。

anchor的三种使用方式:

  • using a load rule

    1
    2
    3
    4
    # 从文本文件中读取规则,并声明一个名称。先声明anchor名称,再载入规则。

    anchor goodguys # 声明anchor名称
    load anchor goodguys from "/etc/anchor-goodguys-ssh" # 为anchor载入具体的规则
  • using pfctl

    1
    2
    3
    4
    5
    6
    7
    > echo "pass in proto tcp from 192.0.2.3 to any port 22" | pfctl -a goodguys -f -

    # 也可以从文本中载入
    > cat >> /etc/anchor-goodguys-www
    pass in proto tcp from 192.0.2.3 to any port 80
    pass in proto tcp from 192.0.2.4 to any port { 80 443 }
    > pfctl -a goodguys -f /etc/anchor-goodguys-www
  • 内联规则

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    allow = "{ 192.0.2.3 192.0.2.4 }"
    anchor "goodguys" {
    anchor { # 内联anchor可以匿名
    pass in proto tcp from 192.0.2.3 to port 80
    }
    pass in proto tcp from $allow to port 22 #宏变量不可以跨anchor读取,此处为内联anchor,可行。
    }
    ------------------
    # 由于锚可以嵌套,因此可以指定子锚。
    anchor "spam/*" # 锚内所有规则。

    # 锚内执行的操作只作用于本锚内规则。另外,主规则集内移除锚点不会清除依存于该锚的规则和子锚。除非使用 pfctl -F 进行flush。

锚定选项

配置ssh锚,声明只允许通过来之fxp0接口的22端口流量。

1
2
3
4
ext_if = "fxp0"
block on $ext_if all
pass out on $ext_if all keep state
anchor ssh in on $ext_if proto tcp from any to any port 22

后续添加的ssh锚中的规则如下:

1
> echo "pass in from 192.0.2.10 to any" | pfctl -a ssh -f -

尽管这个规则没有指定接口、协议、端口,但由于ssh锚规则的定义,所以192.0.2.10只能连接SSH。

同样的语法能用于内联anchor。

1
2
3
4
5
6
7
8
9
allow = "{ 192.0.2.3 192.0.2.4 }"
anchor "goodguys" in proto tcp { # tcp 入流量
anchor proto tcp to port 80 { # to80端口
pass from 192.0.2.3 # from 192.0.2.3 通过
} # 合起来就是:允许192.0.2.3访问内部的tcp 80。
anchor proto tcp to port 22 {
pass from $allow
} # 允许192.0.2.3和192.0.2.4访问内部的tcp 22。
}

操作已命名规则集

1
2
> pfctl -a ssh -s rules     # 罗列ssh锚中的过滤规则
> pfctl -a ssh -F rules # 刷新ssh锚中的过滤规则

日志(Logging)(略)

FTP问题(Issues with FTP) (略)


参考资料:

https://www.freebsdchina.org/forum/topic_24641.html

http://murusfirewall.com/Documentation/OS%20X%20PF%20Manual.pdf

https://man.openbsd.org/pfctl

https://ikawnoclast.com/security/mac-os-x-pf-firewall-avoiding-known-bad-guys/

http://www.hanynet.com/icefloor/