数十分かかる設定を数秒で実行
ファイヤーウォールでブロックすべきIPのリストは日々変動します。
そうした動的な環境に対応するために、iptablesの設定はシェルスクリプトを利用するのが一般的です。
またlogから攻撃を察知して即座に対応するには、コマンドで設定を変更するのが最適です。
その辺の詳しい設定方法は以下のページでまとめました。
「iptablesの設定ファイルをシェルスクリプトを利用して動的に作成」
「Swatchでログを監視して、攻撃に合わせた対策を自動で実行する方法」
上記の設定で問題なく運用していたのですが、登録するルールが多くなると「新しい設定が反映されるまで数十分アクセス不能になる」という点が気になっていました。
その対策のメモです。
ボトルネックの把握
まずは遅い原因を探ります。
上のリンクで紹介した方法では、3つのループが実行されていました。それぞれのループで書き出されるコマンドは以下のとおりです。
ブラックリスト「ip_deny」による書き出し「570行」。
カントリーコードJPのIPを許可するACCEPT_JP_FILTERによる書き出し「2361行」。
国単位でのブロックリストDROP_COUNTRY_FILTERによる書き出し「16036行」。
ぱっと見でDROP_COUNTRY_FILTERが一番重そうです。
そこで「test.sh」というファイルを作り時間を計測してみます。
# vi test.sh
PATH=/usr/local/sbin:/usr/local/bin:/sbin:/bin:/usr/sbin:/usr/bin SECONDS=0 # 特定の国からのアクセスを拒否する # 中国、インド、ロシア if [ -s /tmp/iplist ]; then iptables -N DROP_COUNTRY_FILTER sed -n 's/^\(CN\|IN\|RU\)\t//p' /tmp/iplist | while read address; do iptables -A DROP_COUNTRY_FILTER -s $address -j DROP done iptables -A INPUT -j DROP_COUNTRY_FILTER fi time=$SECONDS echo $time
結果をファイルに書き出すようにして実行。
# sh -x test.sh >test.txt
以下のように出力されます。
(省略)
+ time=1032
+ echo 1032
1032秒。ということは約17分かかったことになります。
新しいルールが全て書き出されるまで全てのアクセスを規制するので、他のループと合わせると約20分もの間アクセス不能になります。
月に1回もしくは週1回のメンテナンスでというならまだしも、毎日これを実行するとなると問題です。
ためしに全てのコマンドをechoでファイルに書き出すと7秒で終了しました。このことから「iptablesでコマンドを利用して大量のルール追加するのは時間がかかる」ということがわかります。
iptabelsの設定は、コマンドを利用せずに設定ファイルを直接書き出す
ということで、結論です。
大量のルールを追加する場合は、直接設定ファイルを書き出すことにしました。
書きだした設定ファイルをiptables側で読み込み直せば数万行のルールも数秒で反映できます。
以前紹介した「iptablesの設定ファイルをシェルスクリプトを利用して動的に作成」の設定を元に、そのまま設定ファイルとして動作する形式で書き出します。
この方法はiptablesの設定ファイルを直接作成します。コマンドによって補完されていたオプションを自分で指定する必要があるなど、多少難易度が高くなります。(普通のソフトはこの方法が一般的ですが…。) 設定を誤ると、最悪の場合サイトにアクセスできなくなり、サーバの再構築が必要な事態も考えられます。いきなり本番のサーバで試さず、テスト環境で試してから実行してください。
iptables2.shの作成
前回は「iptables.sh」というファイルだったので、今回は「iptables2.sh」としました。
# vi iptables2.sh
#!/bin/bash PATH=/usr/local/sbin:/usr/local/bin:/sbin:/bin:/usr/sbin:/usr/bin cat << EOS *filter :INPUT DROP [0:0] :FORWARD DROP [0:0] :OUTPUT ACCEPT [0:0] :ACCEPT_JP_FILTER - [0:0] :DNSAMP - [0:0] :DROP_COUNTRY_FILTER - [0:0] :PING_ATTACK - [0:0] # 拒否IPリストに記載されたIPを拒否する EOS if [ -s /root/deny_ip ]; then for ip in `cat /root/deny_ip` do echo -A INPUT -s $ip -j DROP done fi cat << EOS # IP Spoofing攻撃対策 -A INPUT -i eth0 -s 127.0.0.1/8 -j DROP -A INPUT -i eth0 -s 10.0.0.0/8 -j DROP -A INPUT -i eth0 -s 172.16.0.0/12 -j DROP -A INPUT -i eth0 -s 192.168.0.0/16 -j DROP -A INPUT -i eth0 -s 192.168.0.0/24 -j DROP # Ping攻撃対策 + Ping Flood攻撃対策 -A PING_ATTACK -m length --length 0:85 -m limit --limit 1/sec --limit-burst 4 -j ACCEPT -A PING_ATTACK -j LOG --log-prefix "[IPTABLES PINGATTACK] : " --log-level 7 -A PING_ATTACK -j DROP -A INPUT -p icmp --icmp-type 8 -j PING_ATTACK # Smurf攻撃対策+不要ログ破棄 -A INPUT -d 255.255.255.255/32 -j DROP -A INPUT -d 224.0.0.1/32 -j DROP -A INPUT -d 153.122.40.255/32 -j DROP # Auth/IDENT用の113番ポートは拒否 -A INPUT -i eth0 -p tcp -m tcp --dport 113 -j REJECT --reject-with tcp-reset # 日本からのアクセスを許可するチェインを作成 EOS if [ -s /tmp/iplist ]; then sed -n 's/^JP\t//p' /tmp/iplist | while read address; do echo -A ACCEPT_JP_FILTER -s $address -j ACCEPT done fi cat << EOS # 特定の国からのアクセスを拒否するチェインを作成して追加 # 中国、インド、エジプト、パキスタン、ロシア EOS if [ -s /tmp/iplist ]; then sed -n 's/^\(CN\|IN\|EG\|\PK|RU\)\t//p' /tmp/iplist | while read address; do echo -A DROP_COUNTRY_FILTER -s $address -j DROP done echo -A INPUT -j DROP_COUNTRY_FILTER fi cat << EOS # ステートフル・パケットインスペクションで正しいTCPと既に許可された接続を許可 -A INPUT -p tcp -m tcp ! --tcp-flags SYN,RST,ACK SYN -m state --state NEW -j DROP -A INPUT -m state --state ESTABLISHED,RELATED -j ACCEPT ## ここから個別のサービスで利用するポートを開放する ## # lo(ループバックインターフェース)の許可 -A INPUT -i lo -j ACCEPT -A OUTPUT -o lo -j ACCEPT # SSH用のポートを日本からのみ許可 -A INPUT -p tcp -m state --state NEW -m tcp --dport 42424 -m hashlimit --hashlimit-upto 1/hour --hashlimit-burst 100 --hashlimit-mode srcip --hashlimit-name ssh-limit --hashlimit-htable-expire 3600000 -j ACCEPT_JP_FILTER # http用のポート許可 -A INPUT -p tcp -m state --state NEW -m tcp --dport 80 -j ACCEPT -A INPUT -p tcp -m state --state NEW -m tcp --dport 8080 -j ACCEPT # DNS用ポートの許可。DNS Ampやカミンスキー攻撃対策に適宜ログを取る -A INPUT -i eth0 -p udp -m state --state NEW -m udp --dport 53 -j DNSAMP -A DNSAMP -m recent --set --name dnsamp --rsource -A DNSAMP -m recent --rcheck --seconds 60 --hitcount 10 --name dnsamp --rsource -j LOG --log-prefix "[IPTABLES DNSAMP] : " --log-level 7 -A DNSAMP -m recent --rcheck --seconds 60 --hitcount 10 --name dnsamp --rsource -j DROP -A DNSAMP -j ACCEPT -A INPUT -p tcp -m state --state NEW -m tcp --dport 53 -j ACCEPT # smtps用にポートを許可 -A INPUT -p tcp -m state --state NEW -m tcp --dport 465 -j ACCEPT # imaps(pop3 protocol over TLS/SSL)用にポートを許可 -A INPUT -p tcp -m state --state NEW -m tcp --dport 993 -j ACCEPT # pop3s(imap4 protocol over TLS/SSL)用にポートを許可 -A INPUT -p tcp -m state --state NEW -m tcp --dport 995 -j ACCEPT # smtp tcp用にポートを許可 -A INPUT -p tcp -m state --state NEW -m tcp --dport 25 -j ACCEPT # サブミッションポート用にポートを許可 -A INPUT -p tcp -m state --state NEW -m tcp --dport 587 -j ACCEPT # https(http protocol over TLS/SSL) tcp用にポートを許可 -A INPUT -p tcp -m state --state NEW -m tcp --dport 443 -j ACCEPT # NTP用にポートを許可 -A INPUT -p udp -m state --state NEW -m udp --dport 123 -j ACCEPT # 上記の条件に当てはまらない通信を記録して破棄 -A INPUT -m limit --limit 1/sec -j LOG --log-prefix "[IPTABLES DROP INPUT] : " --log-level 7 -A INPUT -j DROP COMMIT EOS
スクリプトとして実行せずにファイルへそのまま書き出す部分はechoでもいいですが複数行あると面倒なため「cat << EOS」と「EOS」を組み合わせて書き出しています。
コマンドを使った例と違い、ルールを挿入する「-I」や、チェインの追加に使う「-N」等は必要ありません。「--log-level=debug」も「--log-level 7」に直しています。
また、念のため、暗示的に呼び出してた「-m tcp」といった記述も追加しています。
作成したファイルを「/usr/script/」へ保存して実行権限を与えます。ディレクトリが無い場合は作成してください。
# cp iptables2.sh /usr/script/iptables2.sh # chmod 755 /usr/script/iptables2.sh
cyber_attack_block.shの作成
Smurf攻撃対策やSYN flood攻撃対策はコマンドで実行する必要があるので別ファイルに分けます。
これは1度設定すれば何度も実行する必要はないのですが、個人的に忘れやすいので一緒くたに処理してます。
# vi cyber_attack_block.sh
# Smurf攻撃対策 sysctl -w net.ipv4.icmp_echo_ignore_broadcasts=1 > /dev/null sed -i '/# Disable Broadcast Ping/d' /etc/sysctl.conf sed -i '/net.ipv4.icmp_echo_ignore_broadcasts/d' /etc/sysctl.conf echo "# Disable Broadcast Ping" >> /etc/sysctl.conf echo "net.ipv4.icmp_echo_ignore_broadcasts=1" >> /etc/sysctl.conf # SYN flood攻撃対策でSYN cookiesを有効に設定 sysctl -w net.ipv4.tcp_syncookies=1 > /dev/null sed -i '/# Enable SYN Cookie/d' /etc/sysctl.conf sed -i '/net.ipv4.tcp_syncookies/d' /etc/sysctl.conf echo "# Enable SYN Cookie" >> /etc/sysctl.conf echo "net.ipv4.tcp_syncookies=1" >> /etc/sysctl.conf
こちらも同じように「/usr/script/」に移動して実行権限を与えます。
# cp cyber_attack_block.sh /usr/script/cyber_attack_block.sh # chmod 755 /usr/script/cyber_attack_block.sh
iplist_check.shの作成
以前の記事では「iplist_check.sh」で全世界のカントリーコードとIPリストをCronで毎日取得をしていました。
そのなかでついでに「iptables.sh」を実行していました。今回は新しく作った「iptables2.sh」を使ってiptablesの設定ファイルである「/etc/sysconfig/iptables」へ設定を書き出します。
そして新しい設定を有効にするべく「iptables reload」を実行します。
また「cyber_attack_block.sh」も実行します。
# vi /etc/cron.daily/iplist_check.sh
#!/bin/bash PATH=/usr/local/sbin:/usr/local/bin:/sbin:/bin:/usr/sbin:/usr/bin # 実行時間の計測 SECONDS=0 # ファイルの取得 wget -q http://nami.jp/ipv4bycc/cidr.txt.gz -P /tmp # 解凍してリネーム gunzip -q -f -c /tmp/cidr.txt.gz > /tmp/iplist.new # /tmp下なので240時間で自動的に削除されますが、念のため削除 rm -f /tmp/cidr.txt.gz # iplistが存在しているかチェック if [ -s /tmp/iplist ]; then # diffでエラーが出るので実行権限の変更。「-f」を付けて初回にファイルがない場合に対応 chmod -f 700 iplist.new chmod -f 700 iplist # 差分チェック(正常にファイルが取得できたかチェック) # grep -c パターンに一致した行の行数のみを出力する。diffで差分(違い)のある行は「< hoge」と表示されるため、行頭に「<」がある行をカウントしている。「=」の前後にスペースがあると正常に代入できないので注意。 # $()の中に記述しているので、変数ipdiffに代入。 ipdiff=$(diff /tmp/iplist /tmp/iplist.new | grep -c "^<") # 上記が存在していて、差分の数が1000行以上の場合 # (通常考えられない量の変更=ファイルが正常に取得できていないと判断) if [ $ipdiff -gt 1000 ]; then # メールでIPリストが正常に取得できなかった由を伝える。 cat <<EOS | mail -s "iplist_check" info@example.com iplist false EOS # 正常に取得できなかったリストを破棄 rm -f /tmp/iplist.new else # 正常に取得できた場合ファイル名をiplistに変更 mv /tmp/iplist.new /tmp/iplist # 新しく取得したリストでiptablesの設定スクリプトを実行 # まずは設定を保存する /etc/sysconfig/iptables.bakとして保存される /sbin/service iptables save # 設定を/etc/sysconfig/iptablesに書き込む sh /usr/script/iptables2.sh >/etc/sysconfig/iptables # SYN cookies等の設定 sh /usr/script/cyber_attack_block.sh # 書き込んだファイルをiptablesで読み込む(reloadだと不具合あるので注意) /sbin/service iptables restart # メールでIPリストが正常に取得できた由と変更されたIPの数を伝える。 cat <<EOS | mail -s "iplist_check" info@example.com iplist true change IP $ipdiff EOS fi else # 初回実行時は差分をチェックすべきバックアップがないのでそのままファイル名を変更して利用 mv /tmp/iplist.new /tmp/iplist /sbin/service iptables save sh /usr/script/iptables2.sh >/etc/sysconfig/iptables sh /usr/script/cyber_attack_block.sh /sbin/service iptables restart fi time=$SECONDS echo "run time:" $time
実行速度も確認できるように「run time:<num>」も追加しました。
送信するメールアドレスは「info@example.com」から自分のアドレスに変更してください。
こうすることでroot宛に「iplist_check」というタイトルのメールが届きます。
また、コメントにもある通り速度重視で「reload」を利用して設定を読み込んで運用していましたが、一部のポートが開放できないなどの不具合が発生したため、「restart」で再起動するように変更しました。
この方法なら再起動にかかるのは数十秒程度です。当初のiptablesの更新作業を早くするという目的は達成できています。