Debian に iptables を設定

前回から少し間があいてしまったが、sshd と httpd が立っている Debian に対して、必要最低限のアクセスのみを許可するような設定を施したい。ただ iptables で出来る設定はネットワークに関する知識が乏しい自分にはなかなか難しく、手探り状態ながら作業してみる。
方針としてはその辺のサイトの解説でもよくあるようなホワイトリスト方式。ただし作業元の環境は固定IPアドレスは持っていないので、IPによる接続元制限はできない。

念のため netstat で使用中のポート一覧も確認して知らぬ間にサーバが立っていないか確認しておく。
以降のコマンドは全て su で root になった状態で叩いている。
netstat -anp
問題なさそうだったので、/etc/firewall.sh というファイルを新規作成して、vim で内容を作る。
touch /etc/firewall.sh
chmod 700 /etc/firewall.sh
vim /etc/firewall.sh
#! /bin/sh
LOCALNET=192.168.11.0/255.255.255.0
MY_SSHD_CONFIG=/etc/ssh/sshd_config

iptables -F
iptables -X
iptables -P INPUT DROP
iptables -P OUTPUT DROP
iptables -P FORWARD DROP

# Loopback
iptables -A INPUT  -i lo -j ACCEPT
iptables -A OUTPUT -o lo -j ACCEPT

# SYN Cookiesを有効にする
# ※TCP SYN Flood攻撃対策
sysctl -w net.ipv4.tcp_syncookies=1 > /dev/null
sed -i '/net.ipv4.tcp_syncookies/d' /etc/sysctl.conf
echo "net.ipv4.tcp_syncookies=1" >> /etc/sysctl.conf

# ブロードキャストアドレス宛pingには応答しない
# ※Smurf攻撃対策
sysctl -w net.ipv4.icmp_echo_ignore_broadcasts=1 > /dev/null
sed -i '/net.ipv4.icmp_echo_ignore_broadcasts/d' /etc/sysctl.conf
echo "net.ipv4.icmp_echo_ignore_broadcasts=1" >> /etc/sysctl.conf

# ICMP Redirectパケットは拒否
sed -i '/net.ipv4.conf.*.accept_redirects/d' /etc/sysctl.conf
for dev in `ls /proc/sys/net/ipv4/conf/`
do
    sysctl -w net.ipv4.conf.$dev.accept_redirects=0 > /dev/null
    echo "net.ipv4.conf.$dev.accept_redirects=0" >> /etc/sysctl.conf
done

# Source Routedパケットは拒否
sed -i '/net.ipv4.conf.*.accept_source_route/d' /etc/sysctl.conf
for dev in `ls /proc/sys/net/ipv4/conf/`
do
    sysctl -w net.ipv4.conf.$dev.accept_source_route=0 > /dev/null
    echo "net.ipv4.conf.$dev.accept_source_route=0" >> /etc/sysctl.conf
done

# フラグメント化されたパケットはログを記録して破棄
iptables -A INPUT -f -j LOG --log-prefix '[IPTABLES FRAGMENT] : '
iptables -A INPUT -f -j DROP

# 外部とのNetBIOS関連、Dropbox関連のアクセスはログを記録せずに破棄
# ※不要ログ記録防止
iptables -A INPUT -s ! $LOCALNET -p tcp -m multiport --dports 135,137,138,139,445,17500 -j DROP
iptables -A INPUT -s ! $LOCALNET -p udp -m multiport --dports 135,137,138,139,445,17500 -j DROP
iptables -A OUTPUT -d ! $LOCALNET -p tcp -m multiport --sports 135,137,138,139,445,17500 -j DROP
iptables -A OUTPUT -d ! $LOCALNET -p udp -m multiport --sports 135,137,138,139,445,17500 -j DROP

# 1秒間に4回を超えるpingはログを記録して破棄
# ※Ping of Death攻撃対策
iptables -N LOG_PINGDEATH
iptables -A LOG_PINGDEATH -m limit --limit 1/s --limit-burst 4 -j ACCEPT
iptables -A LOG_PINGDEATH -j LOG --log-prefix '[IPTABLES PINGDEATH] '
iptables -A LOG_PINGDEATH -j DROP
iptables -A INPUT -p icmp --icmp-type echo-request -j LOG_PINGDEATH

# ポートスキャン対策
iptables -N LOG_PORTSCAN
iptables -A LOG_PORTSCAN -m limit --limit 1/s --limit-burst 4 -j RETURN
iptables -A LOG_PORTSCAN -j LOG --log-level info --log-prefix '[IPTABLES PORTSCAN] '
iptables -A LOG_PORTSCAN -j DROP
iptables -A INPUT -p tcp --tcp-flags SYN,ACK,FIN,RST RST -j LOG_PORTSCAN

# 不正パケット対策
iptables -N LOG_NEWSYNCHECK
iptables -A LOG_NEWSYNCHECK -j LOG --log-prefix '[IPTABLES NEWSYNCHECK] '
iptables -A LOG_NEWSYNCHECK -j DROP
iptables -A INPUT -p tcp ! --syn -m state --state NEW -j LOG_NEWSYNCHECK

# 全ホスト(ブロードキャストアドレス、マルチキャストアドレス)宛パケットはログを記録せずに破棄
# ※不要ログ記録防止
iptables -A INPUT -d 255.255.255.255 -j DROP
iptables -A INPUT -d 224.0.0.1 -j DROP

# 113番ポート(IDENT)へのアクセスには拒否応答
# ※メールサーバ等のレスポンス低下防止
iptables -A INPUT -p tcp --dport 113 -j REJECT --reject-with tcp-reset

# 確立に成功した tcp 接続は許可
iptables -A INPUT  -p tcp -m state --state ESTABLISHED,RELATED -j ACCEPT
iptables -A OUTPUT -p tcp -m state --state ESTABLISHED,RELATED -j ACCEPT

# Ping 送受信
iptables -A INPUT  -p icmp --icmp-type echo-request -j ACCEPT
iptables -A INPUT  -p icmp --icmp-type echo-reply -j ACCEPT
iptables -A OUTPUT -p icmp --icmp-type echo-request -j ACCEPT
iptables -A OUTPUT -p icmp --icmp-type echo-reply -j ACCEPT

# DNS 送信
iptables -A INPUT  -p udp --sport 53 -j ACCEPT
iptables -A OUTPUT -p udp --dport 53 -j ACCEPT
iptables -A OUTPUT -p tcp --dport 53 -m state --state NEW -j ACCEPT

# HTTP, HTTPS, POP3(SSL), IMAP, SMTP(TLS/STARTTLS, SSL), git, hkp 送信
iptables -A OUTPUT -p tcp -m state --state NEW -m multiport --dports 80,443,995,993,587,465,9418,11371 -j ACCEPT

# HTTP 受信
iptables -A INPUT -p tcp -m state --state NEW --dport 80 -j ACCEPT

# SSH 受信 / ブルートフォース攻撃対策
# sshd_config ファイルの中からポート番号を指定している箇所を探す
SSHD_LISTEN_PORT=`sed -e 's/^Port\s\+\([0-9]\+\)/\1/p' -e d $MY_SSHD_CONFIG`
iptables -N LOG_SSHBRUTEFORCE
iptables -A LOG_SSHBRUTEFORCE -m limit --limit 3/min --limit-burst 4 -j ACCEPT
iptables -A LOG_SSHBRUTEFORCE -j LOG --log-level info --log-prefix '[IPTABLES SSHBRUTEFORCE] '
iptables -A LOG_SSHBRUTEFORCE -j DROP
iptables -A INPUT -p tcp -m state --state NEW --dport $SSHD_LISTEN_PORT -j LOG_SSHBRUTEFORCE

# 拒否IPアドレスからのアクセスはログを記録せずに破棄
# ※拒否IPアドレスは/root/deny_ipに1行ごとに記述しておくこと
# (/root/deny_ipがなければなにもしない)
if [ -s /root/deny_ip ]; then
    for ip in `cat /root/deny_ip`
    do
        iptables -I INPUT -s $ip -j DROP
    done
fi

# 上記のルールにマッチしなかったアクセスで外部からのものはログを記録(その後自動的に破棄)
iptables -A INPUT -s ! $LOCALNET  -m limit --limit 1/s -j LOG --log-prefix '[IPTABLES NOMATCHINPUT] '
以下のサイトを参考にさせてもらいながら設定を作った。
基本的には参考文献の上2つで大体完結するぐらいの内容。下手に変えない方がよかったかも知れない。

ファイアウォール構築(iptables) - Fedoraで自宅サーバー構築
連載記事 「習うより慣れろ! iptablesテンプレート集」
Man page of IPTABLES
D: Software/iptables - Debian GNU/Linux スレッドテンプレ
セキュリティ強化対策(Iptables編)
ルータ・ファイアウォールiptables)/Fedora Core6

・基本的には全部塞いで必要なところだけ開く(内側から外部へのアクセスも全面的には許可しない)
・ループバックインターフェイスで完結する通信だけは全面的に許可する
・各種攻撃からは防御を試みる
・外部の DNS サーバへの問い合わせを許可する
・HTTP、HTTPS、git、あと GMail で何かすることがあれば使う可能性がある 995,993,587,465 は外部への接続を許可する
・sources.list にリポジトリを追加する際に hkp 鍵サーバと通信する必要があるので 11371 番も外部への接続を許可
・外部からHTTPサーバへの接続を許可する
・外部からSSHサーバへの接続を許可する。ただしブルートフォース攻撃は怖いので過剰なアクセスはDROPするように

この他例えば SSH への接続が日本以外だったら蹴るような対策もすべきだが、現状は仮想PCで外部からは一切アクセスできない状態でテストが面倒なのでとりあえず保留にしてある。
あと外部の HTTP サーバなどに接続する際、ポート番号に 8080 などが使われていると接続できないという問題もある。この手の問題はできれば遭遇するたびに甘くしていく方針にしたい。
今回は仮想サーバで直接マシンのコンソールが開けるので簡単にテストが出来て、SSH が塞がれたままになっても問題なく作業が続行できたものの、リモートのサーバとなるとそうも行かない。
その辺に関してはここにいい説明があって、Windows でモニタを設定を変更するときのように「数秒後に元の状態に戻す」というところまでまとめてコマンドを投げておくことでリモートでのテストをしやすくする解説がある。
実際にリモートでしかできないような環境で設定することになったらこれも試してみようと思う。

参考文献のひとつめの URL では例えば LOCALNET を netstat から自動取得したりするようなコードがあるのだが、Firewall を設定するタイミングではまだその辺は上手く取得できないので IP に関しては直書きしておくことにした。
また国別IPリストの取得処理なども含まれているが、この辺はよく見てないものの設定切り替え時に一瞬無防備になっているような気がして怖いのでとりあえず何もしてない(恐らくiptables-save が吐き出すテキストの中の最後に commit があるので、iptables-restore を使って新しい設定に移行できればいいんじゃないか、という気はちょっとする)。

/etc/firewall.sh を実行して、例えば Windows 側で DOS 窓を2つ開いてサーバに対して同時に ping を打ったりして Ping of Death 対策の確認、Putty を何度も開いてブルートフォース攻撃対策の確認をしたりして、行った設定が上手くいっていることが確認できたら実際にこれがマシン起動時に有効になるようにする。
ln -s /etc/firewall.sh /etc/network/if-pre-up.d/iptables
これで再起動して、Ping of Death の処理がうまく動いていることや SSH への過剰接続がブロックされることを確認したら iptables の設定は完了。
不安が残るもののとりあえずは大丈夫ということにしておこう。