1. 程式人生 > >[QNX_firewall]PF防火牆 最詳細的教程(上)

[QNX_firewall]PF防火牆 最詳細的教程(上)

鋒影

email:[email protected]

如果你認為本系列文章對你有所幫助,請大家有錢的捧個錢場,點選此處贊助,贊助額0.1元起步,多少隨意

PF防火牆

PF防火牆PF ( 全稱:Packet Filter ) --- 包過濾

UNIX LIKE系統上進行TCP/IP流量過濾和網路地址轉換的軟體系統。PF同樣也能提供TCP/IP流量的整形和控制,並且提供頻寬控制和資料包優先集控制。

目錄

編輯本段   回目錄   

PF防火牆 - 基本配置

PF防火牆啟用

要啟用pf並且使它在啟動時呼叫配置檔案,編輯/etc/rc.conf檔案,修改配置pf的一行:

pf=YES

重啟作業系統讓配置生效。

也可以通過pfctl程式啟動和停止pf

# pfctl -e
# pfctl -d

注意這僅僅是啟動和關閉PF,實際它不會載入規則集,規則集要麼在系統啟動時載入,要在PF啟動後通過命令單獨載入。

配置

系統引導到在rc指令碼檔案執行PF時PF從/etc/pf.conf檔案載入配置規則。注意當/etc/pf.conf檔案是預設配置檔案,在系統呼叫rc指令碼檔案時,它僅僅是作為文字檔案由pfctl裝入並解釋和插入pf的。對於一些應用來說,其他的規則集可以在系統引導後由其他檔案載入。對於一些設計的非常好的unix程式,PF提供了足夠的靈活性。

pf.conf 檔案有7個部分:

* 巨集:              使用者定義的變數,包括IP地址,介面名稱等等
* 表:              一種用來儲存IP地址列表的結構
* 選項:          控制PF如何工作的變數
* 整形:          重新處理資料包,進行正常化和碎片整理
* 排隊:          提供頻寬控制和資料包優先順序控制.
* 轉換:          控制網路地址轉換和資料包重定向.
* 過濾規則:   在資料包通過介面時允許進行選擇性的過濾和阻止

除去巨集和表,其他的段在配置檔案中也應該按照這個順序出現,儘管對於一些特定的應用並不是所有的段都是必須的。

空行會被忽略,以#開頭的行被認為是註釋.

# pfctl -sa
# pfctl -sa

控制

引導之後,PF可以通過pfctl程式進行操作,以下是一些例子:

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 顯示任何可顯示的

完整的命令列表,請參閱pfctl的man手冊頁。

編輯本段   回目錄   

PF防火牆 - 列表和巨集

列表

一個列表允許一個規則集中指定多個相似的標準。例如,多個協議埠號網路地址等等。因此,不需要為每一個需要阻止的IP地址編寫一個過濾規則,一條規則可以在列表中指定多個IP地址。列表的定義是將要指定的條目放在{ }大括號中。

當pfctl在載入規則集碰到列表時,它產生多個規則,每條規則對於列表中的一個條目。例如:

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

展開後:

block out on fxp0 from 192.168.0.1 to any
block out on fxp0 from 10.5.32.6 to any

多種列表可以在規則中使用,並不僅僅限於過濾規則:

rdr on fxp0 proto tcp from any to any port { 22 80 } -> \
192.168.0.6
block out on fxp0 proto { tcp udp } from { 192.168.0.1, \
10.5.32.6 } to any port { ssh telnet }

注意逗號在列表條目之間是可有可無的。

巨集

巨集是使用者定義變數用來指定IP地址,埠號,介面名稱等等。巨集可以降低PF規則集的複雜度並且使得維護規則集變得容易。

巨集名稱必須以字母開頭,可以包括字母,數字和下劃線。巨集名稱不能包括保留關鍵字如:
pass, out, 以及 queue.

ext_if = "fxp0"

block in on $ext_if from any to any

這生成了一個巨集名稱為ext_if. 當一個巨集在它產生以後被引用時,它的名稱前面以$字元開頭。

巨集也可以展開成列表,如:

friends = "{ 192.168.1.1, 10.0.2.5, 192.168.43.53 }"

巨集能夠被重複定義,由於巨集不能在引號內被擴充套件,因此必須使用下面得語法:

host1 = "192.168.1.1"
host2 = "192.168.1.2"
all_hosts = "{" $host1 $host2 "}"

巨集 $all_hosts 現在會展開成 192.168.1.1, 192.168.1.2.

編輯本段   回目錄   

PF防火牆 - 表

PF防火牆簡介

表是用來儲存一組IPv4或者IPv6地址。在表中進行查詢是非常快的,並且比列表消耗更少的記憶體cpu時間。由於這個原因,表是儲存大量地址的最好方法,在50,000個地址中查詢僅比在50個地址中查詢稍微多一點時間。表可以用於下列用途:

* 過濾,整形,NAT和重定向中的源或者目的地址.
* NAT規則中的轉換地址.
* 重定向規則中的重定向地址.
* 過濾規則選項中 route-to, reply-to, 和 dup-to的目的地址.

表可以通過在pf.conf裡配置和使用pfctl生成。

配置

在pf.conf檔案中, 表是使用table關鍵字創建出來的。下面的關鍵字必須在建立表時指定。

* constant - 這類表的內容一旦創建出來就不能被改變。如果這個屬性沒有指定,可以使用pfctl新增和刪除表裡的地址,即使系統是執行在2或者更高得安全級別上。
* persist - 即使沒有規則引用這類表,核心也會把它保留在記憶體中。如果這個屬性沒有指定,當最後引用它的規則被取消後核心自動把它移出記憶體。

例項:

table  { 192.0.2.0/24 }
table  const { 192.168.0.0/16, 172.16.0.0/12, 10.0.0.0/8 }
table  persist

block in on fxp0 from { ,  } to any
pass in on fxp0 from  to any

地址也可以用“非”來進行修改,如:

table  { 192.0.2.0/24, !192.0.2.5 }

goodguys表將匹配除192.0.2.5外192.0.2.0/24網段得所有地址。

注意表名總是在符號得裡面。

表也可以由包含IP地址和網路地址的文字檔案中輸入:

table  persist file "/etc/spammers"

block in on fxp0 from  to any

檔案 /etc/spammers 應該包含被阻塞的IP地址或者CIDR網路地址,每個條目一行。以#開頭的行被認為是註釋會被忽略。

用 pfctl 進行操作

表可以使用pfctl進行靈活的操作。例如,在上面產生的表中增加條目可以這樣寫:

# pfctl -t spammers -T add 218.70.0.0/16

如果這個表不存在,這樣會創建出這個表來。列出表中的內容可以這樣:

# pfctl -t spammers -T show

-v 引數也可以使用-Tshow 來顯示每個表的條目內容統計。要從表中刪除條目,可以這樣:

# pfctl -t spammers -T delete 218.70.0.0/16

指定地址

除了使用IP地址來指定主機外,也可以使用主機名。當主機名被解析成IP地址時,IPv4和IPv6地址都被插進規則中。IP地址也可以通過合法的介面名稱或者self關鍵字輸入表中,這樣的表會分別包含介面或者機器上(包括loopback地址)上配置的所有IP地址。

一個限制時指定地址0.0.0.0/0 以及 0/0在表中不能工作。替代方法是明確輸入該地址或者使用巨集。

地址匹配

表中的地址查詢會匹配最接近的規則,比如:

table  { 172.16.0.0/16, !172.16.1.0/24, 172.16.1.100 }

block in on dc0 all
pass in on dc0 from  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 - 不匹配表,阻塞。

編輯本段   回目錄   

PF防火牆 - 包過濾

PF防火牆簡介

包過濾是在資料包通過網路介面時進行選擇性的執行通過或者阻塞。pf檢查包時使用的標準是基於的3層(IPV4或者IPV6)和4層(TCPUDPICMPICMPv6)包頭。最常用的標準是源和目的地址源和目的埠,以及協議

過濾規則集指定了資料包必須匹配的標準和規則集作用後的結果,在規則集匹配時通過或者阻塞。規則集由開始到結束順序執行。除非資料包匹配的規則包含quick關鍵字,否則資料包在最終執行動作前會通過所有的規則檢驗。最後匹配的規則具有決定性,決定了資料包最終的執行結果。存在一條潛在的規則是如果資料包和規則集中的所有規則都不匹配,則它會被通過。

規則語法

一般而言,最簡單的過濾規則語法是這樣的:

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
資料包匹配規則時執行的動作,放行或者阻塞。放行動作把資料包傳遞給核心進行進一步出來,阻塞動作根據block-policy 選項指定的方法進行處理。預設的動作可以修改為阻塞丟棄或者阻塞返回。
direction
資料包傳遞的方向,進或者出
log
指定資料包被pflogd( 進行日誌記錄。如果規則指定了keep state, modulate state, or synproxy state 選項,則只有建立了連線的狀態被日誌。要記錄所有的日誌,使用log-all
quick
如果資料包匹配的規則指定了quick關鍵字,則這條規則被認為時最終的匹配規則,指定的動作會立即執行。
interface
資料包通過的網路介面的名稱或組。組是介面的名稱但沒有最後的整數。比如pppfxp,會使得規則分別匹配任何ppp或者fxp介面上的任意資料包。
af
資料包的地址型別,inet代表Ipv4,inet6代表Ipv6。通常PF能夠根據源或者目標地址自動確定這個引數。
protocol
資料包的4層協議:
tcp
udp
icmp
icmp6
/etc/protocols中的協議名稱
0~255之間的協議號
使用列表的一系列協議.
src_addr, dst_addr
IP頭中的源/目標地址。地址可以指定為:
單個的Ipv4或者Ipv6地址.
CIDR 網路地址.
能夠在規則集載入時通過DNS解析到的合法的域名,IP地址會替代規則中的域名。
網路介面名稱。網路介面上配置的所有ip地址會替代進規則中。
帶有/掩碼(例如/24)的網路介面的名稱。每個根據掩碼確定的CIDR網路地址都會被替代進規則中。.
帶有()的網路介面名稱。這告訴PF如果網路介面的IP地址改變了,就更新規則集。這個對於使用DHCP或者撥號來獲得IP地址的介面特別有用,IP地址改變時不需要重新載入規則集。
帶有如下的修飾詞的網路介面名稱:
o :network - 替代CIDR網路地址段 (例如:192.168.0.0/24)
o :broadcast - 替代網路廣播地址(例如:192.168.0.255)
o :peer - 替代點到點鏈路上的ip地址。

另外,:0修飾詞可以附加到介面名稱或者上面的修飾詞後面指示PF在替代時不包括網路介面的其餘附加(alias)地址。這些修飾詞也可以在介面名稱在括號()內時使用。例如:fxp0:network:0

表.
上面的所有項但使用!(非)修飾詞
使用列表的一系列地址.
關鍵字 any 代表所有地址
關鍵字 all 是 from any to any的縮寫。
src_port, dst_port
4層資料包頭中的源/目標埠。埠可以指定為:
1 到 65535之間的整數
/etc/services中的合法服務名稱
使用列表的一系列埠
一個範圍:
o != (不等於)
o  (大於)
o = (大於等於)
o > (反轉範圍)

最後2個是二元操作符(他們需要2個引數),在範圍內不包括引數。

o : (inclusive range)

inclusive range 也是二元操作符但範圍內包括引數。

tcp_flags
指定使用TCP協議時TCP頭中必須設定的標記。 標記指定的格式是: flags check/mask. 例如: flags S/SA -這指引PF只檢查S和A(SYN and ACK)標記,如果SYN標記是“on”則匹配。

state
指定狀態資訊在規則匹配時是否保持。
keep state - 對 TCP, UDP, ICMP起作用
modulate state - 只對 TCP起作用. PF會為匹配規則的資料包產生強壯的初始化序列號。
synproxy state - 代理外來的TCP連線以保護伺服器不受TCP SYN FLOODs欺騙。這個選項包含了keep state 和 modulate state 的功能。

預設拒絕

按照慣例建立防火牆時推薦執行的是預設拒絕的方法。也就是說先拒絕所有的東西,然後有選擇的允許某些特定的流量通過防火牆。這個方法之所以是推薦的是因為它寧可失之過於謹慎(也不放過任何風險),而且使得編寫規則集變得簡單。

產生一個預設拒絕的過濾規則,開始2行過濾規則必須是:

block in all
block out all

這會阻塞任何通訊方在任何方向上進入任意介面的所有流量。

通過流量

流量必須被明確的允許通過防火牆或者被預設拒絕的策略丟棄。這是資料包標準如源/目的埠,源/目的地址和協議開始活動的地方。無論何時資料包在被允許通過防火牆時規則都要設計的儘可能嚴厲。這是為了保證設計中的流量,也只有設計中的流量可以被允許通過。

例項:

# 允許本地網路192.168.0.0/24流量通過dc0介面進入訪問openbsd機器的IP地址
#192.168.0.1,同時也允許返回的資料包從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關鍵字具有取消進一步往下處理的作用,使得規則指定的動作馬上執行。看一下下面的例子:

錯誤:

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

這些規則執行的結果稍有不同,如果block行被匹配,由於quick選項的原因,資料包會被阻塞,而且剩下的規則也會被忽略。

狀態保持

PF一個非常重要的功能是“狀態保持”或者“狀態檢測”。狀態檢測指PF跟蹤或者處理網路連線狀態的能力。通過存貯每個連線的資訊到一個狀態表中,PF能夠快速確定一個通過防火牆的資料包是否屬於已經建立的連線。如果是,它會直接通過防火牆而不用再進行規則檢驗。

狀態保持有許多的優點,包括簡單的規則集和優良的資料包處理效能。
PF is able to match packets moving in either direction to state table entries meaning that filter rules which pass returning trafficdon‘t need to be written. 
並且,由於資料包匹配狀態連線時不再進行規則集的匹配檢測,PF用於處理這些資料包的時間大為減少。

當一條規則使用了keep state選項,第一個匹配這條規則的資料包在收發雙方之間建立了一個狀態。現在,不僅傳送者到接收者之間的資料包匹配這個狀態繞過規則檢驗,而且接收者回復發送者的資料包也是同樣的。例如:

pass out on fxp0 proto tcp from any to any keep state

這允許fxp0介面上的任何TCP流量通過,並且允許返回的流量通過防火牆。狀態保持是一個非常有用的特性,由於狀態查詢比使用規則進行資料包檢驗快的多,因此它可以大幅度提高防火牆的效能。

狀態調整選項和狀態保持的功能在除了僅適用於TCP資料包以為完全相同。在使用狀態調整時,輸入連線的初始化序列號(ISN)是隨機的,這對於保護某些選擇ISN存在問題的作業系統的連線初始化非常有用。從openbsd 3.5開始,狀態調整選項可以應用於包含非TCP的協議規則。

對輸出的TCP, UDP, ICMP資料包保持狀態,並且調整TCP ISN

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

狀態保持的另一個優點是ICMP通訊流量可以直接通過防火牆。例如,如果一個TCP連線使用了狀態保持,當和這個TCP連線相關的ICMP資料包到來時,它會自動找到合適的狀態記錄,直接通過防火牆。

狀態記錄的範圍被state-policy runtime選項總體控制,也能基於單條規則由if-bound, group-bound, 和 floating state選項關鍵字設定。這些針對單條規則的關鍵字在使用時具有和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配置檔案中設定。

TCP 標記

基於標記的TCP包匹配經常被用於過濾試圖開啟新連線的TCP資料包。TCP標記和他們的意義如下所列:

* F : FIN - 結束; 結束會話
* S : SYN - 同步; 表示開始會話請求
* R :RST - 復位;中斷一個連線
* P : PUSH - 推送; 資料包立即傳送
* A : ACK - 應答
* U : URG - 緊急
* E : ECE - 顯式擁塞提醒迴應
* W : CWR - 擁塞視窗減少

要使PF在規則檢查過程中檢查TCP標記,flag關鍵需按如下語法設定。

flags check/mask

mask部分告訴PF僅檢查指定的標記,check部分說明在資料包頭中哪個標記設定為“on”才算匹配。

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版本中,下面的語法是支援的:

. . . flags S

現在,這個不再支援,mask必須被說明。

標記常常和狀態保持規則聯合使用來控制建立狀態條目:

pass out on fxp0 proto tcp all flags S/SA keep state

這條規則允許為所有輸出中帶SYN和ACK標記的資料包中的僅帶有SYN標記的TCP資料包建立狀態。

使用標記時必須小心,理解在做什麼和為什麼這樣做。小心聽取建議,因為相當多的建議時不好的。一些人建議建立狀態“只有當SYN標記設定而沒有其他標記”時,這樣的規則如下:
. . . flags S/FSRPAUEW 糟糕的主意!!

這個理論是,僅為TCP開始會話時建立狀態,會話會以SYN標記開始,而沒有其他標記。問題在於一些站點使用ECN標記開始會話,而任何使用ECN連線你的會話都會被那樣的規則拒絕。比較好的規則是:

. . . flags S/SAFR

這個經過實踐是安全的,如果流量進行了整形也沒有必要檢查FIN和RST標記。整形過程會讓PF丟棄帶有非法TCP標記的進入資料包(例如SYN和FIN以及SYN和RST)。強烈推薦總是進行流量整形:

scrub in on fxp0
.
.
.
pass in on fxp0 proto tcp from any to any port ssh flags S/SA \
keep state

TCP SYN 代理

通過,當客戶端向伺服器初始化一個TCP連線時,PF會在二者直接傳遞握手資料包。然而,PF具有這樣的能力,就是代理握手。使用握手代理,PF自己會和客戶端完成握手,初始化和伺服器的握手,然後在二者之間傳遞資料。這樣做的優點是在客戶端完成握手之前,沒有資料包到達伺服器。這樣就消沉了TCP SYN FLOOD欺騙影響伺服器的問題,因為進行欺騙的客戶端不會完成握手。

TCP SYN 代理在規則中使用synproxy state關鍵字開啟。例如:

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

這樣, web伺服器的連線由PF進行TCP代理。

由於synproxy state工作的方式,它具有keep state 和 modulate state一樣的功能。

如果PF工作在橋模式下,SYN代理不會起作用。

阻塞欺騙資料包

地址欺騙是惡意使用者為了隱藏他們的真實地址或者假冒網路上的其他節點而在他們傳遞的資料包中使用虛假的地址。一旦實施了地址欺騙,他們可以隱蔽真實地址實施網路攻擊或者獲得僅限於某些地址的網路訪問服務。

PF通過antispoof關鍵字提供一些防止地址欺騙的保護。

antispoof [log] [quick] for interface [af]

log
指定匹配的資料包應該被pflogd進行日誌記錄
quick
如果資料包匹配這條規則,則這是最終的規則,不再進行其他規則集的檢查。
interface
啟用要進行欺騙保護的網路介面。也可以是介面的列表。
af
啟用進行欺騙保護的地址族,inet代表Ipv4,inet6代表Ipv6。

例項:

antispoof for fxp0 inet

當規則集載入時,任何出現了antispoof關鍵字的規則都會擴充套件成2條規則。假定介面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

這些規則實現下面的2個目的:

* 阻塞任何不是由fxp0介面進入的10.0.0.0/24網路的流量。由於10.0.0.0/24的網路是在fxp0介面,具有這樣的源網路地址的資料包絕不應該從其他介面上出現。
* 阻塞任何由10.0.0.1即fxp0介面的IP地址的進入流量。主機絕對不會通過外面的介面給自己傳送資料包,因此任何進入的流量源中帶有主機自己的IP地址都可以認為是惡意的!

注意:antispoof規則擴展出來的過濾規則會阻塞loopback介面上傳送到本地地址的資料包。這些資料包應該明確的配置為允許通過。例如:

pass quick on lo0 all

antispoof for fxp0 inet

使用antispoof應該僅限於已經分配了IP地址的網路介面,如果在沒有分配IP地址的網路介面上使用,過濾規則會擴充套件成:

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

這樣的規則會存在阻塞所有介面上進入的所有流量的危險。

被動作業系統識別

被動作業系統識別是通過基於遠端主機TCP SYN資料包中某些特徵進行作業系統被動檢測的技術。這些資訊可以作為標準在過濾規則中使用。

PF檢測遠端作業系統是通過比較TCP SYN資料包中的特徵和已知的特徵檔案對照來確定的,特徵檔案預設是/etc/pf.os。如果PF起作用,可是使用下面的命令檢視當前的特徵列表。

# pfctl -s osfp

在規則集中,特徵可以指定為OS型別,版本,或者子型別/補丁級別。這些條目在上面的pfctl命令中有列表。要在過濾規則中指定特徵,需使用os關鍵字:

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

指定的作業系統型別unknow允許匹配作業系統未知的資料包。

注意以下的內容::

*作業系統識別偶爾會出錯,因為存在欺騙或者修改過的使得看起來象某個作業系統得資料包。
* 某些修改或者打過補丁得作業系統會改變棧得行為,導致它或者和特徵檔案不符,或者符合了另外得作業系統特徵。
* OSFP 僅對TCP SYN資料包起作用,它不會對其他協議或者已經建立得連線起作用。

IP 選項

預設情況下,PF阻塞帶有IP選項得資料包。這可以使得類似nmap得作業系統識別軟體工作困難。如果應用程式需要通過這樣的資料包,例如多播或者IGMP,可以使用allow-opts關鍵字

pass in quick on fxp0 all allow-opts

過濾規則例項

下面是過濾規則得例項。執行PF的機器充當防火牆,在一個小得內部網路和因特網之間。只列出了過濾規則,queueing, nat, rdr,等等沒有在例項中列出。

ext_if = "fxp0"
int_if = "dc0"
lan_net = "192.168.0.0/24"

# scrub incoming packets
scrub in all

# setup a default deny policy
block in all
block out all

# pass traffic on the loopback interface in either direction
pass quick on lo0 all

# activate spoofing protection for the internal interface.
antispoof quick for $int_if inet

# 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 flags S/SA

# pass all traffic to and from the local network
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.
# keep state on udp and icmp and modulate state on tcp.
pass out on $ext_if proto tcp all modulate state flags S/SA
pass out on $ext_if proto { udp, icmp } all keep 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.
pass in log on $ext_if proto tcp from any to { !$ext_if, !$int_if } \
port ssh flags S/SA synproxy state

編輯本段   回目錄   

PF防火牆 - 網路地址轉換(NAT)

PF防火牆簡介
網路地址轉換是對映整個網路(或者多個網路)到單個IP地址的方法。當ISP分配的IP地址數目少於要連上網際網路的計算機數目時,NAT是必需的。NAT在RFC1631中描述。

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任務的作業系統必須至少要2塊網絡卡,一塊連線到因特網,另一塊連線內部網路。NAT會轉換內部網路的所有請求,使它們看起來象是來自進行NAT工作的主機系統。

NAT如何工作

當內部網路的一個客戶端連線因特網上的主機時,它傳送目的是那臺主機的IP資料包。這些資料包包含了達到連線目的的所有資訊。NAT的作用和這些資訊相關。

* 源 IP 地址 (例如, 192.168.1.35)
* 源 TCP 或者 UDP 埠 (例如, 2132)

當資料包通過NAT閘道器時,它們會被修改,使得資料包看起來象是從NAT閘道器自己發出的。NAT閘道器會在它的狀態表中記錄這個改變,以便:
a)將返回的資料包反向轉換
b)確保返回的資料包能通過防火牆而不會被阻塞。

例如,會發生下面的改變:

* 源 IP: 被閘道器的外部地址所替換(例如, 24.5.0.5)
* 源埠:被隨機選擇的閘道器沒有在用的埠替換 (例如, 53136)

內部主機和因特網上的主機都不會意識到發生了這個轉變步驟。對於內部主機,NAT系統僅僅是個因特網閘道器,對於因特網上的主機,資料包看起來直接來自NAT系統,它完全不會意識到內部工作站的存在。

當因特網網上的主機迴應內部主機的資料包時,它會使用NAT閘道器機器的外部地址和轉換後的埠。然後NAT閘道器會查詢狀態表來確定返回的資料包是否匹配某個已經建立的連線。基於IP地址和埠的聯合找到唯一匹配的記錄告訴PF這個返回的資料包屬於內部主機192.168.1.35。然後PF會進行和出去的資料包相反的轉換過程,將返回的資料包傳遞給內部主機。

ICMP資料包的轉換也是類似的,只是不進行源埠的修改。

PF防火牆NAT和包過濾

注意:轉換後的資料包仍然會通過過濾引擎,根據定義的過濾規則進行阻塞或者通過。唯一的例外是如果nat規則中使用了pass關鍵字,會使得經過nat的資料包直接通過過濾引擎。

還要注意由於轉換是在過濾之前進行,過濾引擎所看到的是上面“nat如何工作”中所說的經過轉換後的ip地址和埠的資料包。

IP 轉發

由於NAT經常在路由器和閘道器上使用,因此IP轉發是需要的,使得資料包可以在UNIX機器的不同網路介面間傳遞。IP轉發可以通過sysctl命令開啟:

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

要使這個變化永久生效,可以增加如下行到/etc/sysctl.conf檔案中:

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

這些行是本來就存在的,但預設安裝中被#字首註釋掉了。刪除#,儲存檔案,IP轉發在機器重啟後就會發生作用。

配置NAT

一般的NAT規則格式在pf.conf檔案中是這個樣子:

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

nat
開始NAT規則的關鍵字。
pass
使得轉換後的資料包完全繞過過濾規則。
interface
進行資料包轉換的網路介面。
af
地址型別,inet代表Ipv4,inet6代表Ipv6。PF通常能根據源/目標地址自動確定這個引數。
src_addr
被進行轉換的IP頭中的源(內部)地址。源地址可以指定為:
單個的Ipv4或者Ipv6地址.
CIDR 網路地址.
能夠在規則集載入時通過DNS解析到的合法的域名,IP地址會替代規則中的域名。
網路介面名稱。網路介面上配置的所有ip地址會替代進規則中。
帶有/掩碼(例如/24)的網路介面的名稱。每個根據掩碼確定的CIDR網路地址都會被替代進規則中。.
帶有()的網路介面名稱。這告訴PF如果網路介面的IP地址改變了,就更新規則集。這個對於使用DHCP或者撥號來獲得IP地址的介面特別有用,IP地址改變時不需要重新載入規則集。
帶有如下的修飾詞的網路介面名稱:
o :network - 替代CIDR網路地址段 (例如, 192.168.0.0/24)
o :broadcast - 替代網路廣播地址(例如, 192.168.0.255)
o :peer - 替代點到點鏈路上的ip地址。

另外,:0修飾詞可以附加到介面名稱或者上面的修飾詞後面指示PF在替代時不包括網路介面的其餘附加(alias)地址。這些修飾詞也可以在介面名稱在括號()內時使用。例如:fxp0:network:0

表.
上面的所有項但使用!(非)修飾詞
使用列表的一系列地址.
關鍵字 any 代表所有地址
關鍵字 all 是 from any to any的縮寫。

src_port
4層資料包頭中的源埠。埠可以指定為:
1 到 65535之間的整數
/etc/services中的合法服務名稱
使用列表的一系列埠
一個範圍:
o != (不等於)
o  (大於)
o = (大於等於)
o > (反轉範圍)

最後2個是二元操作符(他們需要2個引數),在範圍內不包括引數。

o : (inclusive range)

inclusive range 也是二元操作符但範圍內包括引數。

Port選項在NAT規則中通常不使用,因為目標通常會NAT所有的流量而不過埠是否在使用。
dst_addr
被轉換資料包中的目的地址。目的地址型別和源地址相似。
dst_port
4層資料包頭中的目的目的埠,目的埠型別和源埠型別相似。
ext_addr
NAT閘道器上資料包被轉換後的外部地址。外部地址可以是:
單個的Ipv4或者Ipv6地址.
CIDR 網路地址.
能夠在規則集載入時通過DNS解析到的合法的域名,IP地址會替代規則中的域名。
網路介面名稱。網路介面上配置的所有ip地址會替代進規則中。
帶有/掩碼(例如/24)的網路介面的名稱。每個根據掩碼確定的CIDR網路地址都會被替代進規則中。.
帶有()的網路介面名稱。這告訴PF如果網路介面的IP地址改變了,就更新規則集。這個對於使用DHCP或者撥號來獲得IP地址的介面特別有用,IP地址改變時不需要重新載入規則集。
帶有如下的修飾詞的網路介面名稱:
o :network - 替代CIDR網路地址段 (例如, 192.168.0.0/24)
o :broadcast - 替代網路廣播地址(例如, 192.168.0.255)
o :peer - 替代點到點鏈路上的ip地址。

另外,:0修飾詞可以附加到介面名稱或者上面的修飾詞後面指示PF在替代時不包括網路介面的其餘附加(alias)地址。這些修飾詞也可以在介面名稱在括號()內時使用。例如:fxp0:network:0
使用列表的一系列地址.
pool_type
指定轉換後的地址池的型別
static-port
告訴PF不要轉換TCP和UDP資料包中的源埠

這條規則最簡單的形式如下:

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

這條規則是說對從tl0網路介面上到來的所有192.168.1.0/24網路的資料包進行NAT,將源地址轉換為24.5.0.5。

儘管上面的規則是正確的,但卻不是推薦的形式。因為維護起來有困難,當內部或者外部網路有變化時都要修改這一行。比較一下下面這條比較容易維護的規則:(tl0時外部,dc0是內部):

nat on tl0 from dc0:network to any -> tl0

優點是相當明顯的,可以任意改變2個介面的IP地址而不用改變這條規則。

象上面這樣在地址轉換中使用介面名稱時,IP地址在pf.conf檔案載入時確定,並不是憑空的。如果使用DHCP還配置外部地址,這會存在問題。如果分配的IP地址改變了,NAT仍然會使用舊的IP地址轉換出去的資料包。這會導致對外的連線停止工作。為解決這個問題,應該給介面名稱加上括號,告訴PF自動更新轉換地址。

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

這個方法對IPv4 和 IPv6地址都用效。

PF防火牆雙向對映 (1:1 對映)

雙向對映可以通過使用binat規則建立。Binat規則建立一個內部地址和外部地址一對一的對映。這會很有用,比如,使用獨立的外部IP地址用內部網路裡的機器提供web服務。從因特網到來連線外部地址的請求被轉換到內部地址,同時由(內部)web伺服器發起的連線(例如DNS查詢)被轉換為外部地址。和NAT規則不過,binat規則中的tcp和udp埠不會被修改。


例如:

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

轉換規則例外設定

使用no關鍵字可以在轉換規則中設定例外。例如,如果上面的轉換規則修改成這樣:

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

則除了192.168.1.10以外,整個192.168.1.0/24網路地址的資料包都會轉換為外部地址24.2.74.79。

注意第一條匹配的規則起了決定作用,如果是匹配有no的規則,資料包不會被轉換。No關鍵字也可以在binat和rdr規則中使用。

檢查 NAT 狀態

要檢查活動的NAT轉換可以使用pfctl帶-s state 選項。這個選項列出所有當前的NAT會話。

# pfctl -s state
fxp0 TCP 192.168.1.35:2132 -> 24.5.0.5:53136 -> 65.42.33.245:22 TIME_WAIT:TIME_WAIT
fxp0 UDP 192.168.1.35:2491 -> 24.5.0.5:60527 -> 24.2.68.33:53 MULTIPLE:SINGLE

解釋 (對第一行):

fxp0
顯示狀態繫結的介面。如果狀態是浮動的,會出現self字樣。

TCP
連線使用的協議。

192.168.1.35:2132
內部網路中機器的IP地址 (192.168.1.35),源埠(2132)在地址後顯示,這個也是被替換的IP頭中的地址。 of the machine on the internal network. The
source port (2132) is shown after the address. This is also the address
that is replaced in the IP header.

24.5.0.5:53136
IP 地址 (24.5.0.5) 和埠 (53136) 是閘道器上資料包被轉換後的地址和埠。

65.42.33.245:22
IP 地址 (65.42.33.245) 和埠 (22) 是內部機器要連線的地址和埠。

TIME_WAIT:TIME_WAIT
這表明PF認為的目前這個TCP連線的狀態。

編輯本段   回目錄   

PF防火牆 - 重定向 (埠轉發)

PF防火牆簡介

如果在辦公地點應用了NAT,內部網所有的機器都可以訪問因特網。但如何讓NAT閘道器後面的機器能夠被從外部訪問?這就是重定向的來源。重定向允許外來的流量傳送到NAT閘道器後面的機器。

看個例子:

rdr on tl0 proto tcp from any to any port 80 -> 192.168.1.20

這一行重定向了TCP埠80(web伺服器)流量到內部網路地址192.168.1.20。因此,即使192.168.1.20在閘道器後面的內部網路,外部仍然能夠訪問它。

上面rdr行中from any to any部分非常有用。如果明確知道哪些地址或者子網被允許訪問web伺服器的80埠,可以在這部分嚴格限制:

rdr on tl0 proto tcp from 27.146.49.0/24 to any port 80 -> 192.168.1.20

這樣只會重定向指定的子網。注意這表明可以重定向外部不同的訪問主機到閘道器後面不同的機器上。這非常有用。例如,如果知道遠端的使用者連線上來時使用的IP地址,可以讓他們使用閘道器的IP地址和埠訪問他們各自的桌面計算機。

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地址和埠的資料包。

* 192.0.2.1 - 因特網上的主機
* 24.65.1.13 - openbsd路由器的外部地址
* 192.168.1.5 - web伺服器的內部IP地址

PF防火牆重定向規則:

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
* 源埠: 4028 (由作業系統任意選擇)
* 目的地址: 24.65.1.13
* 目的埠: 80

資料包經過rdr規則後的模樣:

* 源地址: 192.0.2.1
* 源埠: 4028
* 目的地址: 192.168.1.5
* 目的: 8000

過濾引擎看見的IP資料包時轉換髮生之後的情況。

安全隱患

重定向確實存在安全隱患。在防火牆上開了一個允許流量進入內部網路的洞,被保護的網路安全潛在的受到了威脅!例如,如果流量被轉發到了內部的web伺服器,而web伺服器上允許的守護程式或者CGI指令碼程式存在漏洞,則這臺伺服器存在會被因特網網上的入侵者攻擊危險。如果入侵者控制了這臺機器,就有了進入內部網路的通道,僅僅是因為這種流量是允許通過防火牆的。

這種風險可以通過將允許外部網路訪問的系統限制在一個單獨的網段中來減小。這個網段通常也被稱為非軍事化區域(DMZ)或者私有服務網路(PSN)。通過這個方法,如果web伺服器被控制,通過嚴格的過濾進出DMZ/PSN的流量,受影響的系統僅限於DMZ/PSN網段。

重定向和反射

通常,重定向規則是用來將因特網上到來的連線轉發到一個內部網路或者區域網的私有地址。例如:

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埠。

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

下面的重定向規則轉發內部介面的80埠到代理:

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

RDR 和 NAT 結合

通過對內部介面增加NAT規則,上面說的轉換後源地址不變的問題可以解決。

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提供了許多方法來進行規則集的簡化。一些好的例子是使用巨集和列表。另外,規則集的語言或者語法也提供了一些使規則集簡化的捷徑。首要的規則是,規則集越簡單,就越容易理解和維護。

使用巨集

巨集是非常有用的,因為它提供了硬編碼地址,埠號,介面名稱等的可選替代。在一個規則集中,伺服器的IP地址改變了?沒問題,僅僅更新一下巨集,不需要弄亂花費了大量時間和精力建立的規則集。

通常的慣例是在PF規則集中定義每個網路介面的巨集。如果網絡卡需要被使用不同驅動的卡取代,例如,用intel代替3com,可以更新巨集,過濾規則集會和以前功能一樣。另一個優點是,如果在多臺機器上安裝同樣的規則集,某些機器會有不同的網絡卡,使用巨集定義網絡卡可以使的安裝的規則集進行最少的修改。使用巨集來定義規則集中經常改變的資訊,例如埠號,IP地址,介面名稱等等,建議多多實踐!

# define macros for each network interface
IntIF = "dc0"
ExtIF = "fxp0"
DmzIF = "fxp1"

另一個慣例是使用巨集來定義IP地址和網路,這可以大大減輕在IP地址改變時對規則集的維護。

# define our networks
IntNet = "192.168.0.0/24"
ExtAdd = "24.65.13.4"
DmzNet = "10.0.0.0/24"

如果內部地址擴充套件了或者改到了一個不同的IP段,可以更新巨集為:

IntNet = "{ 192.168.0.0/24, 192.168.1.0/24 }"

當這個規則集重新載入時,任何東西都跟以前一樣。

使用列表

來看一下一個規則集中比較好的例子使得RFC1918定義的內部地址不會傳送到因特網上,如果發生傳送的事情,可能導致問題。

block in quick on tl0 inet from 127.0.0.0/8 to any
block in quick on tl0 inet from 192.168.0.0/16 to any
block in quick on tl0 inet from 172.16.0.0/12 to any
block in quick on tl0 inet from 10.0.0.0/8 to any
block out quick on tl0 inet from any to 127.0.0.0/8
block out quick on tl0 inet from any to 192.168.0.0/16
block out quick on tl0 inet from any to 172.16.0.0/12
block out quick on tl0 inet from any to 10.0.0.0/8

看看下面更簡單的例子:

block in quick on tl0 inet from { 127.0.0.0/8, 192.168.0.0/16, \
172.16.0.0/12, 10.0.0.0/8 } to any
block out quick on tl0 inet from any to { 127.0.0.0/8, \
192.168.0.0/16, 172.16.0.0/12, 10.0.0.0/8 }

這個規則集從8行減少到2行。如果聯合使用巨集,還會變得更好:

NoRouteIPs = "{ 127.0.0.0/8, 192.168.0.0/16, 172.16.0.0/12, \
10.0.0.0/8 }"
ExtIF = "tl0"
block in quick on $ExtIF from $NoRouteIPs to any
block out quick on $ExtIF from any to $NoRouteIPs

注意雖然巨集和列表簡化了pf.conf檔案,但是實際是這些行會被pfctl(8)擴充套件成多行,因此,上面的例子實際擴充套件成下面的規則:

block in quick on tl0 inet from 127.0.0.0/8 to any
block in quick on tl0 inet from 192.168.0.0/16 to any
block in quick on tl0 inet from 172.16.0.0/12 to any
block in quick on tl0 inet from 10.0.0.0/8 to any
block out quick on tl0 inet from any to 10.0.0.0/8
block out quick on tl0 inet from any to 172.16.0.0/12
block out quick on tl0 inet from any to 192.168.0.0/16
block out quick on tl0 inet from any to 127.0.0.0/8

可以看到,PF擴充套件僅僅是簡化了編寫和維護pf.conf檔案,實際並不簡化pf(4)對於規則的處理過程。

巨集不僅僅用來定義地址和埠,它們在PF的規則檔案中到處都可以用:

pre = "pass in quick on ep0 inet proto tcp from "
post = "to any port { 80, 6667 } keep state"

# David‘s classroom
$pre 21.14.24.80 $post

# Nick‘s home
$pre 24.2.74.79 $post
$pre 24.2.74.178 $post

擴充套件後:

pass in quick on ep0 inet proto tcp from 21.14.24.80 to any \
port = 80 keep state
pass in quick on ep0 inet proto tcp from 21.14.24.80 to any \
port = 6667 keep state
pass in quick on ep0 inet proto tcp from 24.2.74