iptablesはDROPすべきか、REJECTか。tcp-resetという手も
前回 fail2ban の設定で filter を見たので、今度は action(アタックのIPアドレスが見つかったときの動作)がどうなっているのかなと見ていました。

最終的に iptables でIPアドレスからのアクセスを弾くわけですが、その方法が "DROP" ではなく "REJECT" だと気付きました。
# /etc/fail2ban/action.d/iptables-common.conf
blocktype = REJECT --reject-with icmp-port-unreachable
# /etc/fail2ban/action.d/iptables.conf
actionban = <iptables> -I f2b-<name> 1 -s <ip> -j <blocktype>
私は DROP するのが普通で、REJECT より DROP の方が良いと思っていました。
でも fail2ban は iptables で REJECT をしています。どうして REJECT なのでしょうか?
目次
DROP vs REJECT
これは fail2ban ではたまに議論となる話のようです。
Revisit REJECT vs DROP in #507 · Issue #2217 · fail2ban/fail2ban
ip - Is it better to set -j REJECT or -j DROP in iptables? - Unix & Linux Stack Exchange
私は DROP の方が侵入者のアタックに時間が掛かるので、DROP の方が良いという判断でした。
でも読んでみると REJECT 推しが結構あります。
- DROP でも REJECT でも、firewall があることはアタッカーに分かってしまう。他のポートにアクセスしてテストする手もある
- 正規のユーザーにとって DROP されると時間が掛かって迷惑だから、REJECT ですぐに反応を返すべき
- アクセス者が DROP されて接続がうまくいかないとき、どうせリトライするのだからサーバーの転送量は REJECT と変わらない
など。
ちなみに DROP 推しの意見としては、
- DROP して何も情報を渡さない方が良い
- DROP の方がアタッカーの時間が掛かる
- REJECT で何度もアクセスされて反応を返すとサーバーの転送量が増える
私にはよく引き合いに出される「正規のユーザーにとって DROP されると時間が掛かって迷惑」の理屈がよく分かりません。どういう状況を想定しているのでしょうか。
Drop versus Reject - chiark.greenend.org.uk
Realistically, you could probably get away with accepting the connection for unknown ports, blackholing the data, then closing the connection after a timeout. However, that will cause problems with legitimate users if their application makes a connection to one of these ports.
fail2ban で DROP されるようになるのは、認証が何度も失敗した後です。認証に何度も失敗した正規のユーザーってなんでしょう?
アタッカーが何度も何度もアクセスしてくることを考えると、反応を無くして1回の攻撃に時間が掛かるようになる DROP の方が良さそうに思えます。
ただしっかりした(?)DDoS アタックになってくると、反応の有無はお構いなしになり、それなら REJECT で良いじゃん、ということなのでしょう。
そのアクセス毎に REJECT で反応を返していると転送量はどのくらいになるのでしょうか。AWS とかの転送量にお金がかかるサービスを使っている場合は怖いですね。
"REJECT --reject-with tcp-reset" という手
この議論の中で FORWARD の方が良いという話があります。DROP するのではなく、実際の何も listen していないポートに転送させるのです。まぁ確かにそれもいい手かもしれません。
これと似て、"REJECT --reject-with icmp-port-unreachable" ではなく、"REJECT --reject-with tcp-reset" すると良いよという話があります。
アクセスに対して unreachable なのを伝えないことで、そのポートで何も listen していないかのように振る舞えるようです。
これについて fail2ban の開発者(sebres)がテストしています。
PoC/README.md at master · sebres/PoC
nmap --reason `hostname` -p881-885
PORT STATE SERVICE REASON
881/tcp filtered unknown no-response # - DROP
882/tcp filtered unknown no-response # - REJECT
883/tcp filtered unknown no-response # - REJECT --reject-with icmp-port-unreachable
884/tcp closed unknown conn-refused # - REJECT --reject-with tcp-reset
885/tcp closed unknown conn-refused # - NO LISTENER/SERVICE (really closed port)
ここから読み取れるのは、
- DROP でも REJECT でも "filtered" なのはアタッカーに分かってしまう
- REJECT の tcp-reset ならポートが閉じている状態と同じ反応を返す
ということ。
REJECT を使うなら tcp-reset にするのが良さそうです。
4つの中から状況に合わせて好きなのを選ぶ
iptables でアクセスを弾くときの選択肢が4つ登場しました。
- DROP
- REJECT --reject-with icmp-port-unreachable
- REJECT --reject-with tcp-reset
- FORWARD
どれにするかは状況に合わせて決める、というのが結論となりそうです。
相手に時間を掛けさせたいなら DROP ですね。無反応にしたいなら "REJECT --reject-with tcp-reset"。
相手に情報を渡さないようにするという理由で DROP にすべきだという意見もあったのですが、情報を渡さないのは tcp-reset の方が優れています。
DROP も REJECT もしないで自然な様子を振る舞うなら FORWARD。
私はとりあえず DROP にしてみます。
fail2ban の設定を変える
DROP に変える場合は "jail.local" で
[DEFAULT]
banaction = iptables-multiport[blocktype=DROP]
とするだけです。
tcp-reset にする場合はもう少し設定を加え、
https://github.com/fail2ban/fail2ban/issues/2217#issuecomment-454719666
[DEFAULT]
banaction = iptables-multiport
_action_tcp_udp = %(banaction)s[name=%(__name__)s-tcp, protocol="tcp", port="%(port)s", blocktype="REJECT --reject-with tcp-reset", chain="%(chain)s", actname=%(banaction)s-tcp]
%(banaction)s[name=%(__name__)s-udp, protocol="udp", port="%(port)s", blocktype="REJECT --reject-with icmp-port-unreachable", chain="%(chain)s", actname=%(banaction)s-udp][jail-with-udp]
action = %(_action_tcp_udp)s
とすれば OK です。
"_action_tcp_udp" の右側はインデントを揃えるようにしてください。上のコードは空白が取り除かれるのでインデントが揃っていません。
Hestiaの設定ファイルを変える
コントロールパネルの Hestia は fail2ban の action を独自に定めています。

それを見るとアクセスの遮断は fail2ban と同じ "REJECT --reject-with icmp-port-unreachable" になっています。
# /usr/local/hestia/bin/v-add-firewall-ban
$iptables -I fail2ban-$chain 1 -s $ip -j REJECT --reject-with icmp-port-unreachable 2>/dev/null
ということで、REJECT を変えたい人はここを変えておきましょう。