iptables 設定例

ここで取り上げる設定例は、前にも述べたように、iptables-restore コマンドでロードするための書式を採っている。また、カーネルにロードされた状態で iptables-saveで書き出すと、コメント行は除かれ、記述の順番も変わってしまい読みにくくなるので、作った設定ファイルは別のファイル名で取っておくか、iptables-save を使わないか、とにかく上書きしてしまわないよう注意したほうがいい。

なお、説明のための参照符号として、行の先頭辺りに {1-1-1} のようなものが書き添えてある。少しでも読みやすいように、符号は系統化しておいた。ユーザ定義チェーン作成時の参照符号が {1-2-x} なら その中のルールは {1-3-x} のところで設定している。例えば allowed チェーン作成ブロックの符号が {1-2-4} になっていれば、allowedチェーン内のルール作成行程は {1-3-4} のブロックに書いてある。また、シェルスクリプトへ改造したい時になるべく手間がかからないように、チェーンの作成や定義は、基本的に、親チェーンよりも子チェーンを先に、子チェーンよりも孫チェーンを先に記述するようにした。

この参照符号はここだけの勝手なオマケで、実際にこのまま読み込ませるとエラーになるので、表示サンプルをカットペーストする際には取り除くこと ! あるいは、各例の事前解説部分からリンクしてあるテキストファイル (改行コードは LF) にはこんな符号は入れてないので、そちらをダウンロードしていただくと良い。

インターネットサーバの自己防衛

この例 iptables_self_guard.txt は、マシンをファイヤーウォールにするのではなく、HTTP やメールなどインターネットサービスを走らせているサーバ自身を不正なアクセスや攻撃から守るための例。NAT ルールは必要でなく、iptables は単なるパケットフィルタとして働かせる。

インターネットへは、通常のプロバイダの ADSL や ファイバー などのいわゆるブロードバンド接続をしているとする。今時の常識としては、 PPPoE などでクライアントマシンで直接IPをもらうのではなく、モデムの内側にルータを置き (ブロードバンドルータ内蔵モデムでも同じこと) 、複数のクライアントをインターネットにつないでいるだろう。そこへ、ある時インターネットサーバを立てたくなった場合、お手軽な方法は、クライアントを1台増やすような具合でサーバを設置することだ。それを図解したのが上のチャート。敢えてブロードバンドルータの内側にサーバを置くことで、PPPoE などによる動的なグローバル IP はルータが面倒を見てくれるので、サーバのネットワークインターフェースのアドレスを固定したものとして扱え、設定が楽になるという利点が大きい。図では、普段使いのクライアントマシン群の手前にもう 1 台、ファイヤーウォール代わりにルータをかませているが、これは、インターネットに直に接している第 1 ルータが「おバカ」で、ろくな IP フィルタリング機能を備えていない場合などにはあったほうがいいが、特に必要というわけではない。内側の LAN から外へ出ずに直接サーバへアクセスする際には、第2ルータが SNAT を掛けるので、その場合のアクセスはサーバから見ると常に 192.168.1.253 が発したように見える。つまり、内側の普段使い LAN のネットワークである 172.16.1.x というアドレスは、サーバの設定では完全に無視できる。

いうまでもなく、第 1 ルータでは、ポートマッピング (ポートフォワード) を設定し、サーバが提供するサービスポートへの要求がインターネットから入ってきた場合にはサーバつまり 192.168.1.1 へパケットを流すように設定しておく必要がある。これについては ダイナミックDNS のページで少し書いた。

以下に示すのが /etc/sysconfig/iptables のコード。 HTML の都合で大変読みにくかったので、幾つかの断片に分けているが、本当はひとつのファイルだ。ルールの読める人は、単独のファイル iptables_self_guard.txt をダウンロードしていただいたほうが読みやすいだろう。

全体を大別すると、参照符号 {1-x} の filter テーブル設定と、 {2-x} の nat テーブル設定のふたつをやっている。

filterテーブルのチェーン定義

#### {1} Setting up filter table ####
{1-0  } *filter

# {1-1} Policy setup
{1-1-A} :INPUT DROP
{1-1-B} :FORWARD DROP
{1-1-C} :OUTPUT DROP

# {1-2} User defined chains
{1-2-1} :syn_flood -
{1-2-2} :bad_tcp_packets -
{1-2-3} :bad_input -
{1-2-4} :allowed -
{1-2-5} :ms_packets_udp -
{1-2-6} :ms_packets_tcp -
{1-2-7} :tcp_packets -
{1-2-8} :udp_packets -
{1-2-9} :icmp_packets -

filter テーブル {1-0} で、ここからが filter テーブルの設定であることをまず宣言している。{1-1-C} までは標準組み込みチェーンのポリシー設定で、すべて DROP にしている。個々のルールで必要なパケットだけを ACCEPT し、どのルールでも拾われなかったパケットは全部破棄させたいからだ。

{1-2} でたくさんのユーザ定義チェーンを作っているが、それは、それぞれのチェーンの役割を明確にしたいのと、内容の重複したルールを何度も書くよりも効率がいいからだ。また、スクリプティングの関数ブロックのように、幾つかのルールをまとめてオン/オフすることができて管理が楽だし、サーバ環境が変わった際にも流用がしやすい。

syn_floodチェーン

## {1-3} Rules setup
# {1-3-1} syn_flood chain rules
{1-3-1-1} -A syn_flood -m limit --limit 3/second --limit-burst 15 -j RETURN
{1-3-1-2} -A syn_flood -j DROP

{1-3} から、いよいよ各々のチェーンの中にルールを組み立てる。 {1-3-1} でルールを組み込んでいる syn_floodチェーンは、SYN パケット氾濫攻撃 (SYN flood) 避け。新たな TCP コネクション確立を要求する SYN パケットは、limit マッチで 1 秒当たり 3 個までしか呼び出し元の bad_tcp_packets チェーンへ戻してやらない。それ以上の頻度で連発して入ってくる SYN パケットは syn_flood チェーン内で破棄する。limit-busrt は 15 なので、遮断トリガが働いても、最長 5 秒後 (= 1/3 秒 x 15) には、遮断は解除される。このチェーンは全体の階層でいうと孫チェーンにあたり、INPUT, FORWARD あるいは OUTPUT チェーンから bad_tcp_packets チェーンに送られたパケットのうち、コネクション追跡機構でのステートが NEW であり、且つ TCP フラッグ SYN, RST, ACK のうち SYN ビットだけが立っている TCP パケット (つまり TCP コネクション開始要求の第 1 パケット) のみが、送られてきて審査を受ける。

なお、この syn_flood ルールは環境条件によっては問題を引き起こす可能性もある。例えば、サーバの受ける交通量によっては、1 秒に 3パケットまでという規制では厳しすぎるだろう。また、このマシンがデータベースサーバで、他のサーバマシン上の php とか perl のスクリプトを経由してTCP接続でデータを見に来ている場合も、limit の頻度制限に引っ掛かってしまうかもしれない。しかし、こちらはデータベースへの接続/切断を無駄に繰り返す下手なスクリプトがそもそもの元凶とも言える。そっちの手当てが不可能な場合は、syn_floodチェーンの一番最初に、送信元が主サーバの IP になっているパケットなら RETURN するようなルールを加えるなどの工夫が必要だ。

bad_tcp_packetsチェーン

# {1-3-2} bad_tcp_packets chain rules
{Note-1}  -A bad_tcp_packets -d 127.0.0.1 -s 127.0.0.1 -j RETURN
{1-3-2-1} -A bad_tcp_packets -p tcp --tcp-flags SYN,ACK SYN,ACK -m state --state NEW -j REJECT --reject-with tcp-reset
{1-3-2-2} -A bad_tcp_packets -p tcp ! --syn -m state --state NEW -j LOG --log-level info --log-prefix "NEW not SYN: "
{1-3-2-3} -A bad_tcp_packets -p tcp ! --syn -m state --state NEW -j DROP
{1-3-2-4} -A bad_tcp_packets -i eth0 -p tcp --syn -m state --state NEW -j syn_flood

その親 (全体の階層で見れば子レベル) である {1-3-2} bad_tcp_packets チェーンには、素性の異常なTCPパケットを早い段階で破棄する役割を持たせている。 {1-3-2-1} のルールは、インターネット上の他ホストをシーケンスナンバー予測アタック の餌食にするクラッカーがいた場合に、自分のサーバをその踏み台にさせないためのもの。TCPの SYN/ACK パケットは、コネクション確立要求の SYN パケットに対する了解のパケットであり、正常なら 、SYN/ACK を受け取った時点でのステートは必ず ESTABLISHED になっていなければおかしい。それが NEW ステートだということは、取りも直さず、問題の SYN/ACK が成りすまし攻撃の流れ弾である証拠。よって、RST パケットを送り返すことによって TCP コネクションを切断させる「努力」をしている。複数のファイヤーウォールを立てて処理を分散/階層化させている場合には、これではマズいこともあるが、そもそもこの設定例を使いたくなるような環境では、そんな高度なことをやっている人はまずいないだろう。これについては、Iptables チュートリアルSYN/ACKでNEWなパケット に詳しい。bad_tcp_packets チェーンは、INPUT チェーンだけでなく FORWARD, OUTPUT チェーンの子チェーンとしても使われる。

{1-3-2-2} と {1-3-2-3} は、対になって、ある種のパケットを、ログを取ってから破棄している。「ある種」とは、新しい TCP コネクションの確立要求であることを示しながらも、ステートが NEW でないパケットだ。こんなパケットは TCP プロトコルの規定を外れていて、通常のサーバ/ファイヤーウォール運用に限って言えばあり得ない状態なので破棄する。

bad_tcp_packets チェーンの最初にある {Note-1} と印を付けたルールは、RedHat (Fedora Core) の実情から仕方なく挿入したもの。 X-Window を使用中に、ローカルからローカルへのパケットの一部が `NEW not SYN' なパケットとして破棄されてしまう現象が多発していたからだ。破棄させていても実用上の問題はなかったが、このチェーンの段階ではとりあえず RETURN ターゲットで元のチェーンに差し戻し、追って沙汰を下すこととした。

bad_inputチェーン

# {1-3-3} bad_input chain rules
{1-3-3-1} -A bad_input -p all -s 192.168.1.1 -m limit --limit 3/minute --limit-burst 3 -j LOG --log-level info --log-prefix "Spoofed packets: "
{1-3-3-2} -A bad_input -p all -s 192.168.1.1 -j DROP
{1-3-3-3} -A bad_input -p all -s 127.0.0.0/8 -m limit --limit 3/minute --limit-burst 3 -j LOG --log-level info --log-prefix "Spoofed packets: "
{1-3-3-4} -A bad_input -p all -s 127.0.0.0/8 -j DROP
{1-3-3-5} -A bad_input -p all -s 192.168.1.0/24 -j RETURN
{1-3-3-6} -A bad_input -p all -s 192.168.0.0/16 -j DROP
{1-3-3-7} -A bad_input -p all -s 10.0.0.0/8 -j DROP
{1-3-3-8} -A bad_input -p all -s 172.16.0.0/12 -j DROP
{1-3-3-9} -A bad_input -p all -d 224.0.0.0/8 -j DROP
{Note-2}  -A bad_input -p tcp --dport 80 -j RETURN
{1-3-3-10} -A bad_input -p all -m state --state INVALID -m limit --limit 3/minute --limit-burst 3 -j LOG --log-level info --log-prefix "INVALID packets: "
{1-3-3-11} -A bad_input -p all -m state --state INVALID -j DROP

{1-3-3} の bad_input チェーンは、INPUT チェーンから、eth0 インターフェースから入ってきたパケットがプロトコルにかかわらず飛ばされて審査を受ける、不要パケット排除のための子チェーン。 {1-3-3-1} と {1-3-3-2} のセット、 {1-3-3-3} と {1-3-3-4} のセットは、ループバックでなく eth0 から入ってきているのに送り主が サーバ自身のアドレスになっているパケットについて、ログを取ってから破棄している。成りすましへの防御だ。ログの出力には limit マッチを掛けて 1 分間に最大3個だけログを取り、ログファイルが溢れないようにしている。 {1-3-3-6} から {1-3-3-9} は、規定上プライベートアドレスとして予約されているアドレス範囲からのパケットを排除している。これらの審査を受ける直前の {1-3-3-5} で、自分の住むネットワーク範囲だけは RETURN ターゲットで親チェーンへ逃がしている。第 1 ルータの内側のネットワークが、この例とは違って例えば 10.0.0.0/24 だったとしても、{1-3-3-5} のルールさえ変えればすぐに対応できるだろう。ただし、192.168.1.x を使ったルールは他のチェーンにも数個あるので、それらは別途変更しなければならない。

{1-3-3-9} に見られる、アドレス 224.0.0.0/8 宛のパケットを破棄するルールは、主にマイクロソフトネットワーククライアントに対する処置。マイクロソフトネットワークを有効にしている Windows には、 Class D アドレスへマルチキャストパケットをまき散らすという行儀の悪い癖があるので、ログを汚さないようそれらを早々に破棄したいからだ。そして最後に {1-3-3-10} と {1-3-3-11} で、 iptables のステートメカニズムがステートを割り出せない何らかの異常のあるパケットを、ログを取ってから破棄している。

{Note-2} の印を添えたルールは最近仕方なく加えたルールだ。サーバの WEB にアクセスする際、クライアントが Windows XP の Internet Explorer である場合に限って TCP 接続ができない現象が時々起こっていたからだ。ログを調べた結果、 XP の IE の送ってきた HTTP への要求が、異常 (INVALID) なパケットとして破棄されていた。このルールを加えることで現象は回避できたが、全くいいかげんにしてほしいものである。

allowedチェーン

# {1-3-4} allowed chain rules
{1-3-4-1} -A allowed -p tcp --tcp-flags SYN,ACK,RST SYN -j ACCEPT
{1-3-4-2} -A allowed -p tcp -m state --state ESTABLISHED,RELATED -j ACCEPT
{1-3-4-3} -A allowed -p tcp -j DROP

{1-3-4} の allowed チェーンは、TCPのパケットを再審査に掛ける孫チェーンであり、INPUT チェーンから、tcp_packets 子チェーンを経て、サーバサービスを提供しているポートに宛てたTCPパケットだけが迂回させられてくる。ここでは、SYN パケット (新しいコネクションの確立を要求するパケット) か、こちらから進んで開始した接続への返答か、既に確立済みのコネクションに属しているか関連しているパケットだけを許し、それ以外は破棄する。このチェーンに飛ばされてくるより前の段階で、極めて怪しいパケットはもう bad_tcp_packets チェーンや bad_input チェーンで捨てられているので、allowed チェーンで再審査を受けるのは、少なくとも「正常」であることは保証済みのものばかりのはずだ。

ms_packets_*チェーン

# {1-3-5} ms_packets_udp chain rules
{1-3-5-1} -A ms_packets_udp -p udp -d 192.168.1.255 --dport 137:139 -j DROP

# {1-3-6} ms_packets_tcp chain rules
{1-3-6-1} -A ms_packets_tcp -p tcp -d 192.168.1.255 -m multiport --dports 135,139,445 -j DROP

{1-3-5} と {1-3-6} は、マイクロソフトネットワークが送りつけてくるパケットを捨てている。せっかく、iptables による負荷を極力抑えるために、INPUT チェーンに入ってくるパケットを TCP と UDP に分けて処理しているので、マイクロソフトネットワーク関連のパケット処理も TCP と UDP に分けて行うことにした。MSパケットを排除できるのは、このサーバで Sambaを動かしていないからで、Samba を使っている人は、これでは SMB が成り立たなくなってしまうので、ルール {1-3-7-2} と {1-3-8-3} を削除するとともに、tcp_packetsチェーンと udp_packets チェーンに、SMB に必要とされるポート (つまりこの例で排除している MSパケット) を通すようなルールを書き加える必要がある。

tcp_packetsチェーン

# {1-3-7} tcp_packets chain rules
{1-3-7-1} -A tcp_packets -p tcp -m multiport --dports 80,110,25,22,443 -j allowed
{1-3-7-2} -A tcp_packets -p tcp -i eth0 -j ms_packets_tcp

許可したいポート宛てのパケットはここで選り分けて allowed 孫チェーンへ送る。

udp_packetsチェーン

# {1-3-8} udp_packets chain rules
{1-3-8-1} -A udp_packets -p udp -s 192.168.1.0/24 --dport 123 -j ACCEPT
{1-3-8-2} -A udp_packets -p udp -i eth0 --dport 67:68 -j DROP
{1-3-8-3} -A udp_packets -p udp -i eth0 -j ms_packets_udp

{1-3-8} の UDP パケット処理チェーンでやっているのは、内側の普段使いのクライアントマシンから、このサーバの NTPD を見て時計合わせができるようにするのと、このサーバでは必要のない DHCP 要求と DHCP 応答パケットを、ログせずに破棄する処理。NTP のほうのルールでは、外界の不特定多数のクライアントからこの NTP サーバを使わせるつもりは毛頭ないので、LAN のアドレスからの要求だけを許すようにしている。このサーバが外界の NTPサーバに正しい時間を訊いた際に帰ってくる回答は、この評価を受ける以前に、INPUT チェーンの {1-3-A-5} で ESTABLISHED の判定を受け ACCEPT されるので、この送信元アドレス規制には引っ掛からず、問題なく受け取れる。

蛇足だが、DHCP で「アドレスをくれ」という要求は送信元ポートが 68 (bootp client) で宛先ポートが 67 (bootp server)、逆に「このアドレスを使え」という応答は、送信元が 67 で宛先が 68 となる。ついでに言っておくと、このマシンで DNS サーバを動かしていて LAN から使わせたい場合に、その問い合わせパケットを許可するためのルールを挿入するならこのチェーンが良い。

icmp_packetsチェーン

# {1-3-9} icmp_packets chain rules
{1-3-9-1} -A icmp_packets -p icmp --icmp-type 8 -m limit --limit 1/second --limit-burst 5 -j ACCEPT
{1-3-9-2} -A icmp_packets -p icmp --icmp-type 11 -j ACCEPT

{1-3-9} は、 INPUT チェーンから ICMP パケットだけが飛ばされてくるチェーン。 ICMP の Type 8 のパケットは Echo 要求で、外界や LAN内から ping でこのサーバの存在や稼働を確認するのに必要なので通している。ただし、Ping of Deth 攻撃でサーバをダウンさせられてはたまらないので、最大頻度を1秒当たり1回に制限している。Type 11 は時間超過のエラーメッセージだ。その他の様々なエラー情報を返してくる Type 3 と、このサーバからどこかへ ping を打った時の回答である Type 0 も進入を許すべきだが、そのふたつはどちらも、常にこちらからの要求に対する返答なので、 INPUT チェーンの {1-3-A-5} にある ESTABLISHED,RELATED ルールでこれ以前に受け入れが決められているから、敢えて書くのは無駄だ。ESTABLISHEDRELATED の判定、つまりステートメカニズムは、返ってきた ICMP エラーメッセージの元となった要求が ICMP パケットだった場合はもちろん、UDP や TCP だった時にも、同様に作用する。ICMP タイプの一覧は、Iptables チュートリアルICMPタイプ の節に網羅されている。

INPUTチェーン

# {1-3-A} INPUT chain rules
{1-3-A-1} -A INPUT -p tcp -j bad_tcp_packets
{1-3-A-2} -A INPUT -p all -i eth0 -j bad_input
{1-3-A-3} -A INPUT -p all -i lo -s 127.0.0.1 -j ACCEPT
{1-3-A-4} -A INPUT -p all -i lo -s 192.168.1.1 -j ACCEPT
{1-3-A-5} -A INPUT -p all -i eth0 -m state --state ESTABLISHED,RELATED -j ACCEPT
{1-3-A-6} -A INPUT -p tcp -j tcp_packets
{1-3-A-7} -A INPUT -p udp -j udp_packets
{1-3-A-8} -A INPUT -p icmp -j icmp_packets
{1-3-A-9} -A INPUT -m limit --limit 3/minute --limit-burst 3 -j LOG --log-level info --log-prefix "IPT INPUT packs died: "

INPUT チェーンの {1-3-A-3} と {1-3-A-4} では、ループバックインターフェースに入ってくるパケットで、送信元が自分のアドレスかループバックアドレスであるパケット、つまり自分から自分へのパケットは無条件で通すというルール。INPUTチェーンの最後の {1-3-A-9} で、ここまで来ても遂に救われなかったパケットについて、頻度規制を掛けた上で、ログ中で判別しやすいように "IPT INPUT packs died: " という接頭辞付きで syslogd に送っている。 INPUT チェーンの処理はこれ以上ないので、ログされた後には、パケットは INPUT チェーンのポリシー "DROP" を受けて闇へと葬り去られる。

FORWARD, OUTPUTチェーン

# {1-3-B} FORWARD chain rules
{1-3-B-1} -A FORWARD -p tcp -j bad_tcp_packets

# {1-3-C} OUTPUT chain rules
{1-3-C-1} -A OUTPUT -p tcp -j bad_tcp_packets
{1-3-C-2} -A OUTPUT -p all -s 127.0.0.1 -j ACCEPT 
{1-3-C-3} -A OUTPUT -p all -s 192.168.1.1 -j ACCEPT
{1-3-C-4} -A OUTPUT -m limit --limit 3/minute --limit-burst 3 -j LOG --log-level info --log-prefix "IPT OUTPUT packs died: "
COMMIT

FORWARD チェーンに対しては、INPUT チェーンで利用するのと同じ bad_tcp_packets ユーザ定義チェーンを使って、望ましくないパケットを破棄する。

OUTPUT チェーンでは、最初におかしなTCPパケットを bad_tcp_packets に送ってフルイに掛けて ({1-3-C-1})、自分から出たパケットかどうか確認して許し ({1-3-C-2}, {1-3-C-3})、あとの残りはログを取ってからデフォルトポリシーに沙汰を委ねている。

natテーブル

#### {2} Setting up nat table ####
{2-0  } *nat
# {2-1} Policy setup
{2-1-A} :PREROUTING ACCEPT
{2-1-B} :POSTROUTING ACCEPT
{2-1-C} :OUTPUT ACCEPT
COMMIT

nat はこの例では何の実務も行っていないので要らないのだが、記述しなくても、 iptables-save で出力すると勝手に現れる。よって、最低限、ポリシーを確実に ACCEPT にするためだけに設定に盛り込んでいる。nat テーブルは NAT を行うテーブルであって、パケットのフィルタリングをするべきところではない。ここでパケットが捨てられては、処理に異常を来してしまう。

その他の注意点

このサーバから、インターネット上の、例えば Fedora のアップデート配布サイトや RedHat や何かにアクセスして FTP でファイルをダウンロードしたいことが間々あるだろう。 iptables を挟んでも FTP 通信が行えるようにするためには、ルールはこのままでいいが、ip_conntrack_ftp という iptables 関連のカーネルモジュールをカーネルにロードする必要がある。その都度:

root# modprobe ip_conntrack_ftp

して、使い終わったら:

root# rmmod ip_conntrack_ftp

するというのがセキュリティ上よろしいしミニマリスト向きではあるが、RedHat系Linux では、rcスクリプトで iptables が起動される際に一緒にロードさせる術が用意されているので活用するのもよい。そのようにさせるには、/etc/sysconfig/iptables-config ファイルに:

IPTABLES_MODULES="ip_conntrack_ftp"

を書いておけば iptables の起動時にモジュールもロードされる。 まずいないと思うが、サーバで IRC クライアントを使いたい人は ip_conntrack_irc のロードが必要だ。このへんは、Iptables チュートリアル複雑なプロトコルとコネクション追跡 を読むと理解しやすい。