FW (iptables) の設定
固定IPアドレスを割り当ててサーバを外部へ公開する前に、サーバのFW(Firewall)の見直しを行います。間違っていたら指摘していただけると幸いです、
FWの設定
iptablesについて
ここで扱う純粋なFWは、トランスポート層以下のヘッダ内容を見てパケットの処理を決めるものです。LinuxではFWのプログラムとしてIPv4用にiptables、IPv6用にip6tablesが提供されており、今回はiptablesを利用します。設定ファイルは/etc/sysconfig/iptablesになります。
iptablesでは、パケットの処理の規則をルールと呼び、処理内容(受け入れ、遮断など)をtargetと呼びます。このルールを列挙したものをチェインと呼び、このチェインをまとめたものをテーブルと呼びます。デフォルトで用意されているテーブルにはフィルタ目的のfilterやIPマスカレードのためのnatなどがあります。パケットがFWに到達したら、まず適用するテーブルを選択し、その中で該当するチェインを選択し、その中の該当するルールに沿って処理を実行します。
パケットフィルタリングで標準的に使われるのはfilterテーブルですので、今回もfileterテーブルのチェインとルールを操作して、遮断するパケットと通過させるパケットを制御していきたいと思います。filterテーブルには、流入パケットを扱うINPUTチェイン、転送パケットを扱うFORWARDチェイン、流出パケットを扱うOUTPUTチェインがあります。また、それぞれのチェインには、ルールに該当しないパケットへの処理方針を表すPolicyというものがあります。
また、ルール設定を行う上で*1様が凄くわかりやすいのでおすすめです。
設定方針
ルールに欠陥があり不用意にパケットを受け入れてしまうことを懸念してINPUTのPolicyをDROPとします。また中継器として機能させる予定はないのでFORWARDのPolicyもDROPとしています。OUTPUTのPolicyのみACCEPTとしています。また、INPUTでスキャン活動や攻撃と思われるパケットを落とします。
設定内容
設定変更後、service iptables restartで適用できます。設定したiptablesの内容を以下に記述します。
# Firewall configuration written by system-config-firewall
# Manual customization of this file is not recommended.
*filter
:INPUT DROP [0:0]
:FORWARD DROP [0:0]
:OUTPUT ACCEPT [0:0]
###############################################################
#ローカルホストI/F宛は通過
-A INPUT -i lo -j ACCEPT
###############################################################
#コネクション確立済みのパケットは通す
-A INPUT -m state --state ESTABLISHED,RELATED -j ACCEPT
###############################################################
#Ping flood対策(icmp echo requestをhashlimitにより受信制限)
#ICMP echo requestは毎秒1個に制限
-A INPUT -p icmp --icmp-type echo-request -m limit --limit 1/s -j ACCEPT
#log
-A INPUT -p icmp --icmp-type echo-request -j LOG --log-prefix "ping_flood:"
-A INPUT -p icmp --icmp-type echo-request -j DROP
##############################################################
#HTTP Flood(F5)対策(HTTPパケットをhashlimitで制限)
#送信元IPアドレスごとにパケットカウント
#個数が50を越えると10/sの制限をかける,期間は120000ms
-A INPUT -p tcp -m multiport --dports 80,443 -m state --state NEW -m hashlimit --hashlimit-name t_http_flood --hashlimit 10/s --hashlimit-burst 50 --hashlimit-mode srcip --hashlimit-htable-expire 120000 -j ACCEPT
#log
-A INPUT -p tcp -m multiport --dports 80,443 -m state --state NEW -j LOG --log-prefix "ping_flood:"
-A INPUT -p tcp -m multiport --dports 80,443 -m state --state NEW -j DROP
###############################################################
#SYN flood対策(SYNパケットをlimitにより受信制限)
#SYNパケットが1個到着したらすぐに制限(ほぼ常に制限状態)
#制限状態では、サーバ宛のSYNパケットを毎秒50個へ制限
-A INPUT -p tcp -m multiport --dports 80,443 --syn -m limit --limit 50/s --limit-burst 1 -j ACCEPT
#80,443以外のSYNはSSHを許可
#SSHログインを総当たり攻撃で突破することを先延ばしする
#300秒間に5回までのアクセス制限
-A INPUT -p tcp --syn --dport 22 -m recent --name sshattack --set
-A INPUT -p tcp --syn --dport 22 -m recent --name sshattack --rcheck --seconds 300 --hitcount 5 -j LOG --log-prefix "ssh_bruteforce:"
-A INPUT -p tcp --syn --dport 22 -m recent --name sshattack --rcheck --seconds 300 --hitcount 5 -j DROP
-A INPUT -p tcp --dport 22 -m tcp -j ACCEPT
#log
-A INPUT -p tcp --syn -j LOG --log-prefix "syn_flood:"
-A INPUT -p tcp --syn -j DROP
##############################################################
#ステルススキャン対策
#3wayハンドシェイクを完遂しない、コネクションの足がつかないスキャン
#nmapでありがちなスキャンパケットはとりあえず落としてログをとる
#該当ルールが多すぎるのでSTEALTH_SCANチェインを作成
-N STEALTH_SCAN
-A STEALTH_SCAN -j LOG --log-prefix "stealth_scan: "
-A STEALTH_SCAN -j DROP
#ステルススキャンと思われるパケットは全てステルススキャンチェインへ
-A INPUT -p tcp --tcp-flags SYN,ACK SYN,ACK -m state --state NEW -j STEALTH_SCAN
-A INPUT -p tcp --tcp-flags ALL NONE -j STEALTH_SCAN
-A INPUT -p tcp --tcp-flags SYN,FIN SYN,FIN -j STEALTH_SCAN
-A INPUT -p tcp --tcp-flags SYN,RST SYN,RST -j STEALTH_SCAN
-A INPUT -p tcp --tcp-flags ACK,FIN FIN -j STEALTH_SCAN
-A INPUT -p tcp --tcp-flags ACK,PSH PSH -j STEALTH_SCAN
-A INPUT -p tcp --tcp-flags ACK,URG URG -j STEALTH_SCAN
-A INPUT -p tcp --tcp-flags FIN,RST FIN,RST -j STEALTH_SCAN
-A INPUT -p tcp --tcp-flags FIN,URG,PSH FIN,URG,PSH -j STEALTH_SCAN
-A INPUT -p tcp --tcp-flags ALL FIN -j STEALTH_SCAN
-A INPUT -p tcp --tcp-flags ALL ACK -j STEALTH_SCAN
-A INPUT -p tcp --tcp-flags ALL FIN,ACK -j STEALTH_SCAN
#########################################################
#DNS用
-A INPUT -p udp --sport 53 -j ACCEPT
-A INPUT -p tcp --sport 53 -j ACCEPT
COMMIT
ステルススキャン時のフラグの組み合わせを網羅するのは骨が折れるので、一部*2様のやり方を流用させていただきました(いろいろな攻撃や偵察を考慮していらっしゃるのでとても参考になりました)。
ステルススキャンは、1番目がNULLスキャン、2番目と3番目と7番目、8番目は名称はわからないものの正規の手順ではありえない組み合わせによる挙動 を見るスキャン、4番目はFINスキャン、5番目と6番目も名称不明ですがコネクション確立前には送られないはずのフラグが立ったパケットによるスキャン かと思われます。9番目以降は独自に追加したルールで、それぞれ、クリスマスツリースキャン、FINスキャン、ACKスキャン、Maimonスキャンを検知するルールです。もっと様々なスキャン方法を網羅したい場合nmapのスキャンテクニック*3などを参考にすると良さそうです。
HTTP Floodは送信元IPアドレスをスプーフィングできないのでhashlimitで送信元を取り、制限をかけるようにしています。これによって攻撃者は制限され正規ユーザは制限を受けません。
ホストのリソースを食いつぶすSYN Flood対策ですが、スプーフィング可能な攻撃なのでhashlimitを使って送信元IPアドレス単位での制限は有用ではないんじゃないかなぁと考え、limitを用いています。これらhashlimitやlimitの制限パラメータは環境に合わせた改善の余地がありそうなので適宜チューニングしていこうと思います。また、万全を期して別途SYN Cookie法も有効にする予定です。
同様にICMP Floodも容易に送信元IPアドレスを詐称できるのでlimitで制限をかけました。hashlimitをかけている方をたまに見かけますが、もしかしたらスプーフィングしていない純粋なpingを用いたfloodのみを対象にしているのかなぁと(pingコマンドは送信元IPアドレスを指定できますが、確か未割り当てのアドレスを指定すると送ってくれないので…)。
ICMP Smurf攻撃はネットワーク管理者がルータでブロードキャストアドレス宛のリクエストパケットを破棄するなどの対策を行っていれば防げるので今回はiptablesに対策を記述していません。もし記述するならicmp echo replyの流入を制限したり、踏み台となるのを防ぐためにicmp echo replyの流出を制限するルールを記述すればよいと思います。
DNS amp対策の一貫として、dnsパケットの受信制限を追加するのもよいかもしれません。
もっとも、ここまでで登場した中で帯域型攻撃として働くものはエンドホストで落としてもエンドホストに至るまでのリンク/中継器に影響が出ているので根本的な解決にはなりません。しかしやらないよりはマシなので実装しています。
sshポートへの総当たり攻撃を回避するためにsshポートへも接続回数制限を設ける方が良いと思ったのでこれもhashlimitで設定しようと思ったのですが、*4様の手法の方が明解だったのでこちらを参考に記述しました。
ログについて
dmesgコマンドで取り急ぎメッセージログを見ることはできますが、rsyslogの設定を変更しないと/var/log/下にログファイルは生成されません。そこで*5様の記述に沿ってログの設定を行いました。
すなわち、/etc/rsyslog.confのRULES配下に
kern.info /var/log/iptables.log
を追記。設定変更後は/etc/init.d/rsyslog restartで変更適用。これでログを吐かせるようにしました。
また、ログの肥大化を防ぐためにログのローテーションも行おうと思います。まだ実行しておらず後で行う予定です。
実際のログ
閉じたローカルネットワーク環境で実際にSYN Floodを観測させました。この場合、/var/log/iptables.logに以下のようなログが大量に残ります。192.168.11.5はサーバのローカルIPアドレスです。
ただ、今回のこのログ、実は他ホストからTCPポートスキャン(SYNスキャン)をかけたものです。SYNスキャンはステルススキャンの中でも最もポートスキャンとして検知するのが難しいので今回はsyn_floodでログを出すようにしています。
また、スキャン実行を行うとともにiptables.logをtail -fコマンドでリアルタイム監視していたのですが、RTTを考慮してもログが出力されるまで4,5秒ほどレイテンシがありました。
ping floodも以下のようにちゃんとログを吐いてくれました。
ping floodは以下のようにシェルスクリプトを記述して実行しました。※悪用厳禁。
#!/bin/sh COUNT=0 while [ $COUNT -lt 1000] do ping 192.168.11.5 -n 1000 -s 1400 > /dev/null & COUNT=$(( $COUNT+1 )) done
nmapを用いてFINスキャンを行った場合のログは以下のようになりました。
ログメッセージがstealth_scanだけでも、ログからフラグまで見えるのでどんなスキャンかが判別できますね。
sshポートについて
botのスキャン活動などではsshポートはよく標的になるので、他のポートへ変更するのが無難のようです。これも今後の予定です。