[OpenBSD]

[前に戻る: テーブル] [目次] [次に進む: ネットワークアドレス変換 (NAT)]

PF: パケットフィルタリング


目次


はじめに

パケットフィルタリングとは、ネットワークインターフェイスからわたされるデータパケットを 選択的に通過させたりブロックしたりすることです。 pf(4) がパケットを検査する場合に使用する判断基準は、レイヤ 3 (IPv4 および IPv6) ならびにレイヤ 4 (TCPUDPICMP および ICMPv6) のヘッダに基づきます。最も良く使用される判断基準は、送信元および送信先のアドレス、 送信元および送信先のポート、そしてプロトコルでしょう。

フィルタルールには、パケットがマッチすべき判断基準、およびパケットがマッチした場合に その結果として取るべき、通過かブロックかのいずれかの動作が指定されます。 フィルタルールは、先頭から末尾の方に向けて、指定されている順序で評価されます。 パケットが、quick キーワードの指定されたルールにマッチしない限りは、 最終的な動作が行われるまでに、パケットはすべてのフィルタルールに対して 評価されることになります。最後にマッチしたルールが「勝者」であり、パケットに対して どのような動作を取るべきかが指示されることになります。フィルタリングルールセット の先頭に、暗黙のうちに pass all が指定されていると見なされるため、 パケットがどのフィルタルールにもマッチしなかった場合には、その結果として取られる 動作は通過となります。

ルールのシンタクス

一般的に、フィルタルールを高度に単純化したシンタクスは以下のとおりです。
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
マッチしたパケットに対して取るべき動作は、passblock のいずれかです。pass がその先の処理のためにパケットを通過させて カーネルに戻す動作であるのに対して、blockblock-policy オプションの 設定に基づいて反応する動作となります。 デフォルトの反応は、block dropblock return の いずれかを指定することで、こちらの方が優先されるようになります。
direction
インターフェイス上のパケットの移動する方向を inout のいずれかで指定します。
log
pflogd(8) 経由でパケットのログを取得すべきであることを指定します。 ここで、ルールに keep statemodulate statesynproxy state オプションが指定されている場合には、確立された状態のパケットのログのみ記録されます。 ですので、そのような状態とは無関係にすべてのパケットのログを取得したい場合は log-all を指定してください。
quick
パケットが quick の指定されたルールにマッチした場合には、そのルールは 最終的にマッチすべきルールと見なされるので、ここに指定された動作が 実行されます。
interface
パケットが移動するネットワークインターフェイス名を記述します。
af
パケットのアドレスファミリを指定するためのもので、IPv4 用の inet か IPv6 用の inet6 のいずれかを指定します。PF は通常は、送信元 および/または 送信先のアドレスに基づいて、このパラメータを決定することができます。
protocol
パケットのレイヤ 4 のプロトコルを指定します。
src_addr, dst_addr
IP ヘッダ中の送信元/送信先アドレスです。 以下のアドレスを指定することができます。
src_port, dst_port
レイヤ 4 のパケットヘッダ中の送信元/送信先ポートです。 以下のポートを指定することができます。
tcp_flags
proto tcp を使用する場合に、TCP ヘッダにセットされているべき フラグを指定します。このフラグは、flags check/mask として指定されます。たとえば、flags S/SA の場合、PF は S と A (SYN と ACK) フラグだけに注目し、SYN フラグが "on" の場合に マッチするよう指示します。
state
パケットがこのルールにマッチした場合に、状態の情報を保持するかどうかを 指定します。

デフォルトで拒否

ファイアウォールを設定する際のお勧めの方法は、「デフォルトで拒否」という アプローチを取ることです。それは、すべてのものを拒否することであり、 確かなトラフィックだけファイアウォールの通過を選択的に許可するというものです。 たとい間違いでも警戒するに越したことはないわけですし、またルールセットの記述が より簡単にもなりますので、このアプローチを推奨します。

デフォルトで拒否するフィルタのポリシーを作成するには、 最初のふたつのフィルタルールは以下のものでなければなりません。

block in  all
block out all

これはすべてのインターフェイス上の、どこからどこへ行くものであっても、 すべてのトラフィックを双方向ともブロックするというものです。

通過トラフィック

これで、トラフィックは明示的にファイアウォールを通過させるか、デフォルトで拒否する ポリシーによって廃棄されるかのどちらかになりました。これは、送信元/送信先のポートや 送信元/送信先のアドレス、そしてプロトコルといったパケットの判断基準が、ここで 関係してくることを意味します。トラフィックにファイアウォールの通過を許可するときは、 常に可能な限り限定的なルールを記述すべきです。 これは、意図したトラフィック、そう、本当に意図したトラフィックだけが通過を許される ということを確実にするためなのです。

いくつかの例を以下に示します。

# dc0 上に、ローカルネットワーク 192.168.0.0/24 から 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


# OpenBSD マシン上で実行される web サーバへの、fxp0 上の TCP の
# 着信トラフィックを通過させます。パケットが OpenBSD マシン宛の
# ものである場合のみこのルールにマッチするよう、インターフェイス名
# fxp0 を送信先アドレスとして使用しています。
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 がこれらのパケットの処理に要する時間を大幅に 減少させることができます。

ルールが keep state オプションを持つ場合、ルールにマッチする 最初のパケットは、送信者と受信者との間の「状態」を生成することになります。 今、送信者から受信者に送出されるパケットは状態エントリにマッチし、ルールセット の評価をバイパスするだけでなく、受信者側から送信者側に送り返される 応答パケットも同様のことが行われます。これはたとえば、以下の例のような行です。

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

これは、fxp0 インターフェイスから送出されるすべての TCP トラフィックを 通過させ、応答のトラフィックがファイアウォールを通過するのを許可しています。 状態の保持はすばらしい機能であり、状態テーブルの参照は、 パケットとフィルタルールを評価していくのに比べて劇的に高速ですので、 この機能を使用することで、ファイアウォールの性能を著しく改善します。

modulate state オプションは、それが TCP にのみ適用可能であることを除いて、 keep state と同様に機能します。 modulate state の場合、送出される接続の初期シーケンス番号 (ISN: Initial Sequence Number) がランダム化されます。これは、ISN を選択する際に、 単純に決定してしまうようなある種のオペレーティングシステムから開始した接続を 保護する上で役に立つ機能です。

送出される TCP、UDP および ICMP パケットの状態の保持、ならびに変調された TCP の ISN を指定するには、以下の例のようにします。

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

状態を保持することのもうひとつの優位性は、関連する ICMP トラフィックが ファイアウォールを通過することができるということです。たとえば、TCP 接続に keep state が指定されている場合、ICMP 始点抑制メッセージが この TCP 接続の到着を参照しますが、これは適切な状態エントリにマッチするので ファイアウォールを通過することができます。

状態を持つ接続は、その接続が生成されたインターフェイス上に制限されるということは、 注意すべき重要事項です。このことは、PF を実行するルータやファイアウォールでは 特に重要なことであり、特に「デフォルトで拒否」のポリシーが上記の概要のとおり 実装されている場合にはなおさらです。もし、ファイアウォールが外部インターフェイス上の すべての送出接続の状態を保持している場合、これらのパケットはやはり明示的に 内部インターフェイスにわたすべきでしょう。

natbinat、および rdr ルールは、マッチした接続が フィルタのルールセットにわたされる限り、暗黙のうちにその接続の 状態を生成するということに注意してください。

UDP の状態の保持

「UDP は状態を持たないプロトコルだから、UDP の状態を生成することはできない」 ということをときどき耳にすることがあるのではないでしょうか。確かに、UDP 接続の セッションは、(明示的な通信の開始や終了などの) 状態ということについて、 いかなるコンセプトも持ち合わせてはいませんが、このことは PF の UDP セッションに 対する状態の生成能力には何ら影響はありません。「開始」や「終了」パケットのない プロトコルの場合、PF はマッチしたパケットが通過してから、どれくらいの時間の 経過があったかということの追跡結果を単純に保持しています。もし、それが タイムアウトすれば状態は消去されます。このタイムアウト値は、 pf.conf ファイルの オプション の部分で設定することができます。

TCP のフラグ

TCP パケットのフラグに基づくマッチングは、新しい接続を開始しようとする TCP パケットのフィルタリングを行うためにかなり頻繁に使用されます。 TCP のフラグとその意味を以下にリストします。

ルールの評価中に TCP のフラグを PF に検査させるためには、 flags キーワードを以下のシンタクスで使用します。

flags check/mask

mask 部は指定されたフラグのみ検査することを PF に指示し、 check 部に指定されたフラグは、マッチしたパケットの ヘッダ中のフラグが "on" に違いないことをチェックするよう指示します。

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

上記のルールは、SYN ならびに ACK フラグだけに注目しているので、 SYN フラグのセットされた TCP トラフィックを通過させます。また、 SYN と ECE フラグのセットされたパケットは、やはり上記のルールに マッチしますが、SYN と ACK か ACK だけのパケットはマッチしません。

注: 以前のバージョンの OpenBSD では、 以下のシンタクスがサポートされていました。

. . . flags S

現在ではもはやこれは有効ではありません。マスクは常に指定されている必要があります。

状態エントリの生成の制御を行いやすくするために、フラグは keep state ルールと関連してしばしば使用されます。

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

これは実戦的で安全なものではありますが、このトラフィックが スクラブ (scrub) 済のものであった場合には、 不要なチェックでもあります。スクラブの処理では、(SYN と FIN や SYN と RST のような) 正しくない TCP フラグの組み合わせを持つすべての着信パケットを PF は廃棄します。これは、以下のような scrub による着信パケットの場合、 常に強く推奨されるものです。

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 はこれら 2 点間の ハンドシェイクパケットが到着するよう、通過させます。 しかし、PF には、このハンドシェイクのプロキシを行う能力があります。 このハンドシェイクのプロキシが行われると、PF 自身がクライアントと完全な ハンドシェイクを行い、PF がサーバに対してハンドシェイクを行おうとします。 その上で、これら 2 点間のパケットを通過させます。 このプロセスの利点は、クライアントがハンドシェイクを完了させるまで、 サーバに対して一切のパケットを送信しないことです。 詐称されたクライアントの接続はハンドシェイクを完了することができないので、 これによって、サーバに影響を及ぼす、詐称された 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 の機能も含んでいます。

bridge(4) 上で PF が動作している場合には SYN プロキシは動作しません。

詐称されたパケットのブロック

アドレスの「詐称」とは、悪意のあるユーザが、自身の本当のアドレスを隠すためか、 あるいはネットワーク上の他のノードになりすますため、送信されるパケットの中の 発信元の IP アドレスを偽装することです。そのユーザが、いったんアドレスを詐称 できてしまうと、攻撃の際に本当のアドレスを曝すことなくネットワーク攻撃を仕掛けたり、 ある IP アドレスだけに限定しているネットワークサービスへのアクセス権を手に入れる ための試みを行えたりします。

PF は、以下のような antispoof キーワードを使用した、 いくつかのアドレス詐称に対する防御機能を提供しています。

antispoof [log] [quick] for interface [af]
log
マッチしたパケットのログを pflogd(8) を通じて取得するよう指定します。
quick
パケットがこのルールにマッチした場合には、それを「勝者」のルールと見なし、 ルールセットの評価をここまでで終了します。
interface
詐称防御機能を有効にするネットワークインターフェイスを指定します。これはまた、 インターフェイスのリストでも構いません。
af
詐称防御機能を有効にするアドレスファミリとして、IPv4 用の inet か、IPv6 用の inet6 のいずれかを指定します。

例:

antispoof for fxp0 inet

ルールセットがロードされると、指定されている antispoof キーワードはすべて、 ふたつのフィルタルールに展開されます。インターフェイス fxp0 が IP アドレスとして 10.0.0.1 を持ち、サブネットマスクとして 255.255.255.0 (つまり /24) を持つものとすると、上記の antispoof ルールは以下のように展開されます。

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

これらのルールにより、以下のようなふたつのことができるようになります。

: antispoof ルールを展開した後のフィルタルールは、 ループバックインターフェイスからローカルアドレスに送信しようとしたパケットも ブロックします。これらのアドレスは、以下の例のように、明示的にわたすべきです。

pass in quick on lo0 all

antispoof for fxp0 inet

antispoof の使用法は、IP アドレスが割り当てられているインターフェイスに限定 すべきものです。IP アドレスが割り当てられていないインターフェイスに antispoof を使用することは、以下のようなフィルタルールに展開される結果となってしまいます。

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

これらのルールには、すべてのインターフェイスで、すべての 内向きトラフィックをブロックしてしまう危険性があります。

IP オプション

デフォルトでは、PF は IP オプションのセットされたパケットをブロックします。これによって、 nmap のような「OS の指紋を取る」ユーティリティの仕事をより困難なものにすることができます。 もし、マルチキャストや IGMP のようなパケットを通過させる必要のあるアプリケーションを 使用する場合には、以下のように allow-opts ディレクティブを使用することができます。
pass in quick on fxp0 all allow-opts

フィルタリングルールセットの例

以下はフィルタリングルールセットの一例です。PF を実行するマシンは、 小さな内部ネットワークとインターネットとの間のファイアウォールとして 動作しています。フィルタルールは以下のものだけであり、 queueingnatrdr などは、この例の対象外としています。

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

[前に戻る: テーブル] [目次] [次に進む: ネットワークアドレス変換 (NAT)]


[back] www@openbsd.org
Originally [OpenBSD: filter.html,v 1.17 ]
$Translation: filter.html,v 1.16 2004/02/01 12:35:38 toshi Exp $
$OpenBSD: filter.html,v 1.15 2004/02/25 18:31:45 jufi Exp $