軽い気持ちでポートが閉じてるのを確認するポートスキャナーを作り始めました。
サーバリストを渡すと指定したポートを検査して閉じてるか確認します。ポートが開いてたらアラートをあげるツールです。
TCPのポートスキャンはsyn/syn-ackの通信を行うためポートの開閉確認はすごく簡単でした。
UDPも0サイズのパケットを投げれば簡単に開閉が確認できるだろうと思ってたら予想以上に難易度が高いのがわかりました。
UDPのポートスキャン
UDPの場合はステートレスのためUDPパケットを投げてみてもレスポンスがあるかどうかはそのサーバ次第です。
一番簡単にわかるケースは、UDPパケットを何かしら投げてポートが閉じられていると、ICMP port unreachable errorや何かしらのICMPエラーメッセージが返ってくる場合で、これであれば瞬時にポートが閉じられてるのがわかります。
一番難しいケースは、ポートが開いてようと閉じてようとUDPパケットを送っても何もレスポンスが返らない場合です。
有名なポートスキャンツールNmapのこのドキュメントにあるTable 5.3のopen|filteredのステータスになるケースです。
UDP Scan (-sU) | Nmap Network Scanning
例えばAWSのセキュリティグループを使って特定のUDPポートだけ開けている場合でも、そのポートに0サイズのUDPパケットを送って何もレスポンスがない場合と、空いてないポートに0サイズのUDPパケットを送る場合は同じような現象(何もレスポンスメッセージが返らない)のため、ポートの開閉が判別できません。
Nmapは何をしているか
ではNmapの検査では何をしているのでしょうか。詳しくみていくとwell known port(例えばポート53はDNSなど)の場合は、そのデーモンが扱うプロトコル用のメッセージを個別に送ってレスポンスが返るかみています。
実際にどのポートにどのメッセージを送るかはこちらのNmapのペイロードで実装されています。
https://github.com/ParrotSec/nmap/blob/master/nmap-payloads
それでも、何かしらそれっぽいUDP上のプロトコルのメッセージをポート毎に送ったとしても、メッセージが返ってくるかはそのデーモンの実装次第なため、UDPのポートの検査は難易度が相当高いようです。
例えば手元のVMでopenvpnサーバを立ち上げてそこにNmapでスキャンしても、ポートが開いてるかどうかは不明(open|filtered)になりました。
結論
自作ツールでUDPポートスキャンは諦めます!
やるとしても自分が本当に調べたい箇所に絞って、そのデーモンのリクエストレスポンスをtcpdumpで調べて同じようなリクエストを送って確認できるかぐらいにしようと思います。汎用的にすべてのUDPポートをうまく検査するのは難しいため。
ポートスキャナーを作るのは車輪の再発明なのですが、それでも実際に作ってみたいと思って作り始めると、思ってもみないような課題に直面します。少し前のDNSドメインハイジャック検査ツールを自作した時もdigコマンドを使わずにDNSパケットを自作してみたら大変勉強になったし、より深い知識が身につきました。
やはり既存のツールをうまく組み合わせるだけでは味わえないエキサイティングな瞬間があるので、どんどん再発明していきましょう!
メモ