パケットフィルタリングで不正パケットをDROPするかREJECTするか問題

最近iptablesのルールを弄っていて「不正パケットをDROPするのとREJECTするのでは、どちらがベターなのか?」と疑問に思って少し調べたので、メモを残しておく。

現時点での個人的見解は「外部公開サーバではDROPを使った方がよさそう。他の環境では正直DROPREJECTのどちらでも大差なさそうだが、DROPが優勢か?」といった感じだ。

考えるにあたり、まずパケットフィルタリングを使用するシチュエーションとして以下のパターンを想定してみた。

  1. HTTPサーバなどの「外部にサービスを公開するサーバ」において、公開ポート以外に届いたパケットをフィルタリングするケース。
  2. (1) に加えて、例えば「国内からのアクセスだけ許可する」などの目的で、公開ポートに届いたパケットもフィルタリングするケース。
  3. 開発用サーバで送信元IPアドレスでフィルタリングする場合のように「特定のユーザにだけサーバを公開し、それ以外からはサーバの存在を隠蔽したい」というケース。

最初に (1) の場合、外部に公開されているポート*1が存在する時点で、Nmapなどのツールにて「そのIPアドレスでホストが稼働している」ことが容易に推測できてしまう。

現在の普通の外部公開サーバでは「公開ポート以外はファイアウォールで閉じる」という構成が一般的な訳だが、そんなことは攻撃者だって百も承知だろう。だから、ホストの存在が分かっていれば、公開ポート以外のフィルタリングがDROPREJECTのどちらであっても、攻撃者からすれば「十中八九フィルタリングされてますね、これは」で済んでしまうはずだ。

あくまでも攻撃者の興味は「開いているポート」にある。「フィルタリングされているポート」も「フィルタリングされていないが、閉じているポート」も、そこから内部に侵入できないという点でほぼ等しく価値がない。

そういう意味では、DROPREJECTのどちらでも大差はない気がする。

ただしREJECTの場合はICMPエラーメッセージを送り返す。DoSやDDoSの可能性まで考慮すると、REJECTのこの振る舞いが仇となる可能性がある。

単純な話、送信元を偽装したパケットでDoSやDDoSされると、「偽装された送信元IPアドレス」に向けてICMPエラーメッセージを送り返してしまう訳で、結果的にDoSやDDoSの片棒を担ぐことになってしまう可能性がある。

それと、ICMPエラーメッセージを返すということは、単純に考えても「受信パケット数と同じ個数のICMPパケット」の送信(応答)が発生する訳で、例えば通信量による従量課金が適用される環境では懐へのダメージが大きくなるかもしれない。

こういったケースまで考慮するならば、REJECTよりもDROPを使用した方が安心だろう。

次に (2) の場合だが、公開サーバである以上は、例えばWhoisDNSなどの情報を辿ってホストの存在を探り当てることは難しくないし、極端なことを言うならproxy経由で「ホストにアクセス可能なネットワーク空間」に潜り込まれてしまえば (1) と同じ条件になってしまう。

ホストの存在が分かってしまう以上、やはりDROPREJECTのどちらでも大差はなくて、しかしREJECTの場合はICMPエラーメッセージの応答が(以下略)、といった具合だろう。

結論は (1) と同じく「REJECTよりもDROPを使用した方が安心」だ。

最後に (3) の場合だが、これはちょっと判断が難しい。このケースでは「第3者からホストの存在を隠蔽したい」という暗黙の要求があると思うのだが、それは技術的に可能だろうか?

まずDROPは、IPの仕組みからすると少々不自然である。もしも本当に「ホストが存在しない/シャットダウンしている」ならば、大抵の場合はホストの最寄りのルータがホスト到達不能を意味するICMPエラーメッセージを送り返すだろう。しかしDROPは受信パケットを破棄するだけで、何も応答しない。「応答がない」という不自然さより、Nmapなどのツールにて「そのIPアドレスではホストが稼働しているが、パケットフィルタリングされている」と容易に推測されてしまう。

つまりDROPでは「ホストの存在を隠蔽する」という目的は達成できない。

ではREJECTではどうだろうか? 例えばiptablesでは、REJECTルールに「--reject-with icmp-host-unreachable」を付加することで、送り返すICMPエラーメッセージの中身を「ホスト到達不能」にすることができる。この機能を使用することで、あたかも「ホストが存在しないからルータがホスト到達不能を送り返した」かのように見せかけることができる。これによってホストの存在を隠蔽できそうである。

残念ながらこの方法は万全ではないようだ。本当にホストが存在しない場合、大抵のルータは「ホスト到達不能」を数秒に1個送り返すという、通信レート制限がかかっているかのような振る舞いをするらしい。一方でiptablesREJECTによる「ホスト到達不能」の返信は毎度律義に実行される。だからNmapによるスキャンを2回以上行い、「ホスト到達不能」の返信の挙動を確認することで、「ホストが存在しない」と「iptablesによるREJECT」のどちらによるものなのか推測することが可能なようだ。

……書き方が伝聞調なのは、私自身は経験がないのだが、Nmapのドキュメントにそのものズバリの記述があって、そこから引っ張ってきているからである。

It seems that Demetris is receiving ICMP host unreachable messages when trying to scan these IPs (or at least this one). Routers commonly do that when a host is unavailable and so they can't determine a MAC address. It is also occasionally caused by filtering. Demetris scans the other hosts on the network and verifies that they behave the same way. It is possible that only ICMP packets are filtered, so Demetris decides to try a TCP SYN scan. He runs the command nmap -vv -n -sS -T4 -Pn --reason 10.10.10.0/24. All ports are shown as filtered, and the --reason results blame some host unreachable messages and some nonresponsive ports. The nonresponsive ports may be due to rate limiting of host unreachable messages sent by the router. Many routers will only send one of these every few seconds. Demetris can verify whether rate limiting is the cause by running the scan again and seeing if the host unreachable messages come for exactly the same set of ports. If the ports are the same, it may be a specific port-based filter. If Nmap receives host-unreachable messages for different ports each time, rate limiting is likely the cause.

Bypassing Firewall Rules | Nmap Network Scanning

Nmapのドキュメントの記述を信じるならば、REJECTで「ホスト到達不能」を送り返すように偽装しただけでは、見破られてしまう可能性は十分にあるだろう。つまり「ホストの存在を隠蔽する」という目的が達成できるかどうか怪しいところである。

ならば、結局のところDROPREJECTのどちらでも大差はない気がする。まあ、REJECTを使う方法でも気休め程度の効果はあるかもしれないが……。

(もしかしたら、頑張ればiptablesでも「通信レート制限がかかっているかのような」ホスト到達不能の返信を再現できるかもしれないが、そこまでする意味があるかどうか、私には判断しかねる)

ところでREJECTを使う場合は、全ての受信NGケースにてREJECTして「ホスト到達不能」を返すようにする必要がある。DROPREJECTが混在していたり、REJECTにて複数種類のICMPエラーメッセージを返すようになっていると、振る舞いのちぐはぐさよりホストの存在が明らかになってしまうはずだ。

だから、例えばルータのポートフォワーディング機能で自宅サーバの特定ポートを公開するようなケースでは、ルータ側のパケットフィルタ設定も含めてREJECTにできないか検討することになる――まあ大概のルータのパケットフィルタではDROPが使われていて、しかもREJECTに変更できないことも多いはずだ。下手にサーバ側のフィルタリングだけREJECTにして、DROPREJECTが混在した状況にてしまうと、攻撃者に余計な情報を与えて刺激してしまいかねない。それならば、サーバ側もDROPにして統一した方がマシだろう。

VPSの類を使用している場合も、事業者によっては「管理コンソールから設定できるパケットフィルタ」と「サーバ・インスタンスのOSのパケットフィルタ」を併用できる訳で、ここでもDROPREJECTの統一性の問題が起こりうるだろう。併用する場合、管理コンソール側でREJECTを使うように変更できる気がしないので……やはりDROPで統一することになる気がする。

――「DROPREJECTのどちらでも大差はない」と書いたが、ホスト単体ではなく周辺環境まで考慮すると、現実にはDROPを使うことが多いのかもしれない。

あれこれ考えてみたが、一般的な外部公開サーバではDROPを使うようにしておいた方が無難そうだ。第3者からホストを隠蔽したいケースでは、理論的にはDROPREJECTのどちらでも大差なさそうな感じだが、しかし運用環境を考慮してDROPを使うことが多いのかもしれない。

*1:HTTPサーバならTCPの80番や443番が定番だろう。