Linuxで作るファイアウォール[パケットフィルタリング設定編]

第5回 Linuxで作るファイアウォール[パケットフィルタリング設定編]

いよいよパケットフィルタリングの設定を始める。しっかりと不要なパケットをブロックできれば、ファイアウォールの内側の安全度はより向上する。パケットの性質やiptablesの動作をここでマスターしてほしい。

 前回はNATの設定方法を説明しました。これで見かけ上の経路ができたことになります。今回はファイアウォールの仕上げとして、パケットフィルタリングの設定を行います。

パケットフィルタリングの仕組み

 パケットフィルタリングの設定とはいかなるものかを簡単に説明すると、どのようなパケットを通過させるか、あるいは到達を許可/拒否させるかを定義することです。iptablesではIPアドレスやプロトコル、ポート、フラグメントなどで制限をかけることが可能です。さらに、送信先、送信元なのかといった判断もできます。これを行うのが、filterテーブルに含まれるFORWARD、INPUT、OUTPUTという3つのチェインです。まずは3つのチェインの関係を整理しておきましょう。

図1 iptablesによるパケットフィルタリングの流れ

 パケットを受信した際、まずパケットの送信先をチェックします。送信先がそのパケットを受信したホストそのものであれば、INPUTチェインに送られて定義されているルールに従って処理され、そのパケットを受け取る場合は適切なサービスに渡されます。送信先が別のホストであればFORWARDチェインに送られ、パケットの通過が許可されれば該当するホストへと転送されます。許可されなかったパケットは、そこで破棄されるというわけです。反対に、そのホストのプログラムによって生成されたパケットはOUTPUTチェインでチェックされ、許可されれば送信先へと送り出されます。

 ファイアウォールはインターネットとLANの間でパケットを通過あるいは破棄するのが基本的な役目ですから、FORWARDの設定が中心になります。ただし、ファイアウォール自体に対して何らかの働きかけ(リモートメンテナンスなど)を行いたい場合もあるので、INPUTやOUTPUTの設定も行っておくべきでしょう。

FORWARDチェイン

 FORWARDチェインは、ファイアウォールの肝になるチェインです。必要なパケットだけにDMZ上の目的のホストやサービスへアクセスを許可する(ファイアウォールを通過させる)のが、このFORWARDチェインの仕事です。当然のことながら、FORWARDチェインの基本ポリシーはDROP(パケットを破棄)とします。そして、許可するアクセスのみ「穴をあける」設定を行います。

注:デフォルトのFORWARDチェインのポリシーはACEEPT(通過を許可)となっているため、各サーバに対してどのプロトコルでも接続できるはずです。できないようであればNATのルールを見直してください。

基本ポリシーはパケットの破棄

 基本的にFORWARDチェインのポリシーはDROPとするので、まず次のコマンドを実行します。

# /sbin/iptables -P FORWARD DROP

 -Pオプションは前回説明したとおり、指定したチェインに対して基本ポリシーを設定するオプションです。これで、ファイアウォールを通過してDMZ上のホストへ到達できる経路はすべて絶たれたことになります。

注:iptablesのデフォルトのテーブルはfilterであるため、-tオプションによるテーブルの指定は省略できます。

通過させるパケットの設定

 次にファイアウォールを通過させるパケットの設定です。まずは次のようなルールを想定して設定を行います。

インターネット上のホストからWebサーバ(192.168.0.10)に対するHTTP(80/TCP)のアクセスのみを許可する。

 FORWARDチェインに通過を許可するルールを追加するわけですが、注意しなければならないのは、iptablesでは1つのコネクションを1つのルールだけで許可することができないということです。どういうことかをTCPのコネクションを例に説明します。

図2 TCP 3Way-HandShake

 TCPのセッションは、

  1. 接続要求元から接続先へSYNフラグをセットしたパケットを送信
  2. SYNパケットを受け取った接続先のホストは、接続元のホストへSYN/ACKフラグをセットしたパケットを送信し、ACKフラグをセットしたパケットを待ち受ける
  3. 接続元のホストから送信されたACKフラグがセットされたパケットを接続先のホストが受け取る

という3つの段階を経て開始されます。

 iptablesでは、接続元のホストから接続先のホストへのルール(上図の(1)(3))と接続先のホストから接続元のホストへのルール(上図の(2))とを別々のルールで定義する必要があります()。

注:(1)と(3)を別々のルールで定義することも可能です。詳細については後ほど説明します。

 これを踏まえたうえで、実際のルールを設定していきます。インターネット上のホストから、Webサーバへのアクセスを許可するには次のコマンドを実行します。

# /sbin/iptables -A FORWARD -p tcp --dport 80 -d 192.168.0.10 -j ACCEPT

 上記のオプションは、次の内容をFORWARDチェインに追加(APPEND)したことを意味します。これにより、上図の(1)と(3)が可能になります。

  -p プロトコル:tcp
  --dport 送信先ポート:80
  -d 送信先アドレス:192.168.0.10
  -j ターゲット:ACCEPT(パケットの通過を許可)

 次に、Webサーバからインターネット上のホストへの応答パケットを許可するルールを設定します。これが上図の(2)を可能にするルールです。

# /sbin/iptables -A FORWARD -p tcp ! --syn -m state --state ESTABLISHED --sport 80 -s 192.168.0.10 -j ACCEPT

 これで、インターネット上のホストからWebサーバへのHTTPのアクセスは許可されたことになります。

 上記のオプションは、次の内容をFORWARDチェインに追加(APPEND)したことを意味します。

  -p プロトコル:tcp
  --sport 送信元ポート:80
  -s 送信元アドレス:192.168.0.10
  ! --syn フラグ:SYNフラグ以外
  -m state --state ステータス:ESTABLISHED
  -j ターゲット:ACCEPT(パケットの通過を許可)

 指定したオプションの中に、先ほどとは違うものが含まれています。それは「フラグ」と「ステータス」です。

flag(フラグ):

 フラグを条件とする拡張マッチングで、「-p tcp」(--protocol tcp)パラメータを指定したときのみ使用できます。

 TCPパケットには、フラグとしてSYN、SYN/ACK、ACK、RSTなどがセットされています。iptablesでは次のフラグを指定することができます。

SYN、ACK、FIN、RST、URG、PSH、ALL(すべて)、NONE(すべてなし)

 通常は「--tcp-flags」として、チェックするフラグとセットされているべきフラグを指定します。例えば、

-p tcp --tcp-flags ALL SYN,ACK

とした場合は「すべてのフラグ(ALL)をチェックし、SYNとACKだけがセットされているTCPパケット」を意味します。

 「--syn」は「--tcp-flags SYN,RST,ACK SYN」の省略形で、「SYN、RST、ACKフラグをチェックし、SYNだけがセットされているTCPパケット」という意味になります。上記のオプションには「! --syn」が指定されています。前回も紹介したように「!」は条件を反転させるパラメータですから、「SYNビットがクリアされてACKとFINビットがセットされているTCPパケット」となります。

 なぜこのオプションを指定することが必要なのか考えてみましょう。通常、サーバであれば自分自身からほかのホストへ接続することはほとんどないでしょう()。上記のルールは、アクセスに対する応答を許可するためのものです。接続の開始にセットされ、シーケンス番号の初期化に使われるSYNフラグがセットされたパケットの通過を許可する必要はないのです。SYNフラグがセットされたパケットの通過を拒否することは、クラッカーに仮に侵入されたとしてもほかのホストへの踏み台とさせないためにとても有効な手段となるのです。

注:SMTPやDNSなど、通常の動作として外部へアクセスするものはもちろん別です。

status(ステータス):

 パケットの状態をチェックしたりする際に使用する拡張マッチングで、--stateを使うには「-m state」が必要です。つまり、必ず「-m state --state」という形を取ります。今回の例では引数として「ESTABLISHED」が与えられています。ESTABLISHEDなセッション、つまり双方向のコネクションのパケットを許可するということになります。

NEW 新コネクションを開始するパケット
ESTABLISHED 通常の応答パケットあるいは確立中コネクションの応答
RELATED ICMPエラー、FTPデータコネクションなどのパケット
INVALID 無効なパケット
表 --stateのマッチ条件

 flagとセットで指定することで、クラッカーに侵入されてしまってもほかのホストへの踏み台とされないようにします。これも大切なセキュリティ対策の1つと考えられます。

 以上、Webサーバを例に設定方法を紹介しました。メールサーバやDNSサーバへのアクセス許可も、これを参考にすれば設定できるでしょう。

INPUT/OUTPUTチェイン

 外部のホストからファイアウォール自身に、メンテナンスなどのために接続することがあるかもしれません。それを許可するルールを追加するのがINPUTおよびOUTPUTチェインになります。INPUTおよびOUTPUTチェインの設定を行っていないと、ファイアウォールそのもののサービスが無防備になるか、逆にまったくアクセスできないということになってしまいます。

INPUT/OUTPUTチェインの設定

 メンテナンスを行うためのホストを限定し、22/TCP(ssh)での接続を許可するような場合を考えてみましょう。

メンテナンス用ホスト(172.16.1.150)から、ファイアウォールの外側のインターフェイスeth0(172.16.0.100)に対する22/TCP(ssh)による接続を許可する。

 INPUTチェインには外部からホストへ入ってくるためのルールを、OUTPUTチェインにはホストから外部へ出ていくためのルールを追加します。FORWARDチェインと同じで、入ってくるパケットに対する応答が外部に出ていくことを許可するルールを作成しなければなりません。

# /sbin/iptables -A INPUT -p tcp -s 172.16.1.150 --dport 22 -d 172.16.0.100 -i eth0 -j ACCEPT
# /sbin/iptables -A OUTPUT -p tcp ! --syn -m state --state ESTABLISHED --sport 22 -s 172.16.0.150 -d 172.16.1.100

 指定されているオプションはこれまでに説明したものばかりなので、特に説明は必要ないでしょう。ここで注意すべき点は、OUTPUTチェインです。FORWARDチェインを作成したときと同じく、flagとstatusにオプションを指定して外部へ接続できないようにすることです。

ICMPの処理

 ホストの生存確認を行うために、pingコマンドを使うことは多いのではないでしょうか。ファイアウォール自身についてはEcho Replyを返す必要はありません。しかしながら、DMZ上のホストに関しては生存確認を行いたいと思うことがあるかもしれません。iptablesでアクセス制御を行っているホストは、ICMPについても意図的に通るようにする必要があります。

生存確認に必要なICMP-TYPEは?

番号
メッセージタイプ
0 Echo Reply
3 Destination Unreachable
4 Source Quench
5 Redirect
8 Echo Request
11 Time Exceeded
12 Parameter Problem
13 Timestamp Request
14 Timestamp Reply
15 Information Request
16 Information Reply
17 Address Mask Request
18 Address Mask Reply
表 Typeフィールド

 iptablesでは、ICMPのTYPEを指定することができます。pingコマンド使うのであれば、Echo Requestを受け付け、Echo Replyを返すことができればいいのです。このICMP-TYPEについて簡単に説明しておきましょう。

 ICMPヘッダの中身は、Typeフィールドから始まります。このTypeフィールドの指定番号によって、ICMPメッセージの種類などが決定されます。ICMPには次のようなTypeメッセージが規定されています。

 iptablesでは、この番号を指定することができます。ホストの生存確認はpingコマンドを用いることが多いかと思います。pingコマンドは、Echo Requestを目的のホストに向かって送信し、その応答としてEcho Replyが返ってくるのを確認します。そこで、ここでは生存確認を目的とし、Echo RequestとEcho Replyを双方向に許可するためのルールを追加することにしましょう。iptalbesのオプションとしては「--icmp-type」を指定し、引数としてICMP Type番号を指定します。

生存確認のためメンテナンス用のホスト(172.16.1.150)から、Webサーバ(192.168.0.10)に対するEcho Requestと、その応答のためのEcho Replyを許可する。

# /sbin/iptables -A FORWARD -p icmp -s 172.16.1.150 --icmp-type 8 -d 192.168.0.10 -i eth0 -j ACCEPT
# /sbin/iptables -A FORWARD -p icmp --icmp-type 0 -s 192.168.0.10 -d 172.16.1.150 -o eth0 -j ACCEPT

 この設定もまた、これまでに説明したオプションの指定方法とそれほど違いはありません。プロトコルがICMPになったことに注意すればいいでしょう。

ブロードキャストあてICMPの拒否

 ファイアウォールの外側のインターフェイスに対する生存確認のためのEcho Requestを許可すべきではないでしょう。ましてやブロードキャストあてのicmp-echoには応答する必要もありません。ブロードキャストあてのICMPは、カーネルパラメータを調整することでまったく受け付けなくすることができます。

 ブロードキャストあてICMPを拒否する場合は、/etc/sysctl.confファイルに設定を記述します。OS起動時にsysctlコマンドが実行されることで、この設定が反映されます。ブロードキャストあてのicmp-echoを無視するには、設定ファイルに次の1行を追加します。

net.ipv4.icmp_echo_ignore_broadcasts = 1

 カーネルパラメータを調整することで、ソースアドレスを偽造していないかチェックするような設定もできますが、これには注意が必要です。あまりやり過ぎると、本来の動作をしなくなってしまう危険があります。

総仕上げ:ルールのスクリプト化

 パケットフィルタリングの設定方法について一通り解説してきました。これまでのことが理解できればファイアウォールを構築できるでしょう。では、実際にファイアウォール用のルールを作成していきましょう。

 ルールを1つ1つ入力してもよいのですが、OSをリブートするたびに同じ作業を繰り返すことになります。効率的ではないしミスも発生するでしょう。以前に紹介したipchainsと同様に、スクリプトを作成することをお勧めします。以下のようなスクリプトを適当なファイル名で作成します。

#!/bin/sh
#
# Define IP Address
FW_OUT='172.16.0.100'     “ファイアウォールの外側のインターフェイス”
FW_IN='192.168.1.1'       “ファイアウォールの内側のインターフェイス”
V_WEB='172.16.0.10'       “Webサーバの仮想IPアドレス”
V_MAIL='172.16.0.20'      “メールサーバの仮想IPアドレス”
V_DNS='172.16.0.30'       “DNSサーバの仮想IPアドレス”
R_WEB='192.168.1.10'      “Webサーバの実IPアドレス”
R_MAIL='192.168.1.20'     “メールサーバの実IPアドレス”
R_DNS='192.168.1.30'      “DNSサーバの実IPアドレス”
MAINT='172.16.1.150'      “メンテナンス用ホスト”
ANY='0.0.0.0'             “すべてのIPアドレスを表現”
#
# Flush chains
/sbin/iptables -F
#
# すべてのパケットを拒否
/sbin/iptables -P INPUT DROP
/sbin/iptables -P FORWARD DROP
/sbin/iptables -P OUTPUT DROP
#
# ループバックアドレスに関してはすべて許可
/sbin/iptables -A INPUT -s 127.0.0.1 -d 127.0.0.1 -j ACCEPT
/sbin/iptables -A OUTPUT -s 127.0.0.1 -d 127.0.0.1 -j ACCEPT
#
# メンテナンス用のホストからのみpingを許可
/sbin/iptables -A OUTPUT -p icmp -s $FW_OUT --icmp-type 0 -d $MAINT -j ACCEPT
/sbin/iptables -A INPUT -p icmp -s $MAINT --icmp-type 8 -d $FW_OUT -j ACCEPT
#
# 各サーバからファイアウォールの内側のホストへのpingを許可
/sbin/iptables -A OUTPUT -p icmp -s $V_WEB --icmp-type 0 -d $MAINT -j ACCEPT
/sbin/iptables -A INPUT -p icmp -s $MAINT --icmp-type 8 -d $V_WEB -j ACCEPT
/sbin/iptables -A OUTPUT -p icmp -s $V_MAIL --icmp-type 0 -d $MAINT -j ACCEPT
/sbin/iptables -A INPUT -p icmp -s $MAINT --icmp-type 8 -d $V_MAIL -j ACCEPT
/sbin/iptables -A OUTPUT -p icmp -s $V_DNS --icmp-type 0 -d $MAINT -j ACCEPT
/sbin/iptables -A INPUT -p icmp -s $MAINT --icmp-type 8 -d $V_DNS -j ACCEPT
#
# メンテナンス用ホストから各サーバに対してpingを許可
/sbin/iptables -A INPUT -p icmp -s $MAINT --icmp-type 0 -d $V_WEB -j ACCEPT
/sbin/iptables -A OUTPUT -p icmp -s $V_WEB --icmp-type 8 -d $MAINT -j ACCEPT
/sbin/iptables -A INPUT -p icmp -s $MAINT --icmp-type 0 -d $V_MAIL -j ACCEPT
/sbin/iptables -A OUTPUT -p icmp -s $V_MAIL --icmp-type 8 -d $MAINT -j ACCEPT
/sbin/iptables -A INPUT -p icmp -s $MAINT --icmp-type 0 -d $V_DNS -j ACCEPT
/sbin/iptables -A OUTPUT -p icmp -s $V_DNS --icmp-type 8 -d $MAINT -j ACCEPT
#
# メンテナンス用ホストからファイアウォールに対して22/TCP(ssh)を許可
/sbin/iptables -A INPUT -p TCP -s $MAINT --dport 22 -d $FW_OUT -i eth0 -j ACCEPT/sbin/iptables -A OUTPUT -p TCP ! --syn --sport 22 -s $FW_OUT -d $MAINT -o eth0 -j ACCEPT
#
# Webサーバに対して80/TCP(http)でのアクセスを許可
/sbin/iptables -A FORWARD -p TCP -s $ANY --dport 80 -d $R_WEB -j ACCEPT
/sbin/iptables -A FORWARD -p TCP ! --syn -m state --state ESTABLISHED --sport 80 -s $R_WEB -d $ANY -j ACCEPT
#
# メールサーバに対して25/TCP(smtp)でのアクセスを許可
/sbin/iptables -A FORWARD -p TCP -s $ANY --dport 25 -d $R_MAIL -j ACCEPT
/sbin/iptables -A FORWARD -p TCP ! --sny -m state --state ESTABLISHED --sport 25 -s $R_MAIL -d $ANY -j ACCEPT
#
# メールサーバから外部への25/TCP(smtp)を許可
/sbin/iptables -A FORWARD -p TCP -s $R_MAIL --dport 25 -d $ANY -j ACCEPT
/sbin/iptables -A FORWARD -p TCP ! --syn -m state --state ESTABLISHED --sport 25 -s $ANY -d $R_MAIL -j ACCEPT

# DNSサーバに対して53/UDP(dns)でのアクセスを許可
/sbin/iptables -A FORWARD -p UDP -s $ANY --dport 53 -d $R_DNS -j ACCEPT
/sbin/iptables -A FORWARD -p UDP --sport 53 -s $R_DNS -d $ANY -j ACCEPT
#
# DNSサーバから外部への53/UDP(dns)を許可
/sbin/iptables -A FORWARD -p UDP -s $R_DNS --dport 53 -d $ANY -j ACCEPT
/sbin/iptables -A FORWARD -p UDP --sport 53 -s $ANY -d $R_DNS -j ACCEPT
#
# Accept ssh connection from MAINT to All Servers
/sbin/iptables -A FORWARD -p TCP -s $MAINT --dport 22 -d $R_WEB -j ACCEPT
/sbin/iptables -A FORWARD -p TCP ! --syn --sport 22 -s $R_WEB -d $MAINT -j ACCEPT
/sbin/iptables -A FORWARD -p TCP -s $MAINT --dport 22 -d $R_MAIL -j ACCEPT
/sbin/iptables -A FORWARD -p TCP ! --syn --sport 22 -s $R_MAIL -d $MAINT -j ACCEPT
/sbin/iptables -A FORWARD -p TCP -s $MAINT --dport 22 -d $R_DNS -j ACCEPT
/sbin/iptables -A FORWARD -p TCP ! --syn --sport 22 -s $R_DNS -d $MAINT -j ACCEPT
#
# Flush Nat Rules
#
/sbin/iptables -t nat -F
#
# Nat Rules
#
# 外部のホストがDMZセグメントへ入るためのNATの定義
/sbin/iptables -t nat -A PREROUTING -d $V_WEB -i eth0 -j DNAT --to $R_WEB
/sbin/iptables -t nat -A PREROUTING -d $V_MAIL -i eth0 -j DNAT --to $R_MAIL
/sbin/iptables -t nat -A PREROUTING -d $V_DNS -i eth0 -j DNAT --to $R_DNS
#
# DMZ上のホストが外部へ出ていくときのためのNATの定義
/sbin/iptables -t nat -A POSTROUTING -s $R_MAIL -o eth0 -p UDP -j SNAT --to $V_MAIL
/sbin/iptables -t nat -A POSTROUTING -s $R_DNS -o eth0 -p UDP -j SNAT --to $V_DNS
#
# End Of Rules
サンプルスクリプト

注:メールサーバはメールの送信、DNSサーバは名前解決のために外部へ出ていくための経路が必要であろうと仮定しています。

 スクリプトの作成が終わったら、実行権限を与えて実行すればルールが反映されます。

# chmod 744 filename
# ./filename

 しかし、このままではOSの起動時にこの設定は反映されません。そこで、次のコマンドを実行します。

# /sbin/iptables-save > /etc/sysconfig/iptables

 これでファイアウォールの構築作業は取りあえず完了です。次回は、構築したファイアウォールの動作確認方法などについて説明したいと思います。