ポートノッキング
ポートノッキングとはデフォルトではファイアウォールによって閉じられているポートを外部から密かに開く方法です。ポートノッキングでは予め決めておいた順番で閉じているポートを叩きます。正しい順番のポートの"ノック" (接続試行) を受け取ったとき、ファイアウォールは特定のポートを開いて接続を許可します。
標準的なポートスキャンに対して、ポートのサービスが使われていないかのようにみせかけることができるという利点があります。この記事ではデーモンや iptables を使ってポートノッキングを利用する方法を説明します。
目次
イントロダクション
以下に進む前に iptables をインストール・設定する必要があります。iptables については iptables を読んでください。
iptables の recent モジュールを使うことで (成功あるいは失敗した) ポート接続に基づく IP アドレスのリストを動的に作成することができます。recent を利用して、特定の IP アドレスから正しいポートがノックされたかどうかをファイアウォールに判断させ、正解なら、特定のポートを開きます。
ポートノッキングをするセッションは以下のようになります:
$ ssh username@hostname # No response (Ctrl+c to exit) ^C $ nmap -Pn --host_timeout 201 --max-retries 0 -p 1111 host #knocking port 1111 $ nmap -Pn --host_timeout 201 --max-retries 0 -p 2222 host #knocking port 2222 $ ssh user@host # Now logins are allowed user@host's password:
ノックに使用するポートはランダムに選択するのが良いでしょう。random.org で 1 から 65535 の間のポート番号を生成できます。一般的に使われているポートは選択しないように注意してください。ポートデータベース や /etc/services
ファイルを参照してください。
シンプルなポートノッキング
サーバーサイド
デーモンヘルパーによるポートノッキング
専用のデーモンを使うことでポートノッキングを管理できます。ヘルパープログラムを使えばルールを簡単に設定できる他、高度な機能も存在します。
knockd はネットワークにセキュリティレイヤーを追加する ポートノッキング デーモンです。knockd の wiki にはポートノッキングの設定例が3つ載っています。設定例を多少改変することで iptables ファイアウォールに簡単に統合できます。シンプルなステートフルファイアウォールに従って iptables を設定した場合、INPUT
チェインの設定をファイアウォールで使用する open
チェインに置き換えてください。
例:
[options] logfile = /var/log/knockd.log [opencloseSSH] sequence = 8881:tcp,7777:tcp,9991:tcp seq_timeout = 15 tcpflags = syn,ack start_command = /usr/bin/iptables -A TCP -s %IP% -p tcp --dport 22 -j ACCEPT cmd_timeout = 10 stop_command = /usr/bin/iptables -D TCP -s %IP% -p tcp --dport 22 -j ACCEPT
iptables によるポートノッキング
以下では /etc/iptables/iptables.rules
ファイルを作成して SSH のポートノッキングを処理します。8881
, 7777
, 9991
ポートが順番に"ノック"されたときに SSH の標準ポートである 22 を開くようにルールを設定します。
まずサンプルスクリプトの filter ポリシーとチェインを定義します。サンプルにある OUTPUT ACCEPT は必須で、これがないと SSH ポートが開いてもトラフィックが拒否されるため接続できません。最後の3つのチェインは後のルールでポートノッキングをするのに必要です。
# Filter definition
*filter :INPUT DROP [0:0] :FORWARD DROP [0:0] :OUTPUT ACCEPT [0:0] :TRAFFIC - [0:0] :SSH-INPUT - [0:0] :SSH-INPUTTWO - [0:0]
次にメインのチェインである TRAFFIC
のルールを追加します。ポートノッキングでは適切なポートに順番で接続リクエストを送信します。ネットワークトラフィックを制御して SSH などの接続を確立するために ICMP が必要です。
# INPUT definition
-A INPUT -j TRAFFIC -A TRAFFIC -m state --state ESTABLISHED,RELATED -j ACCEPT -A TRAFFIC -m state --state NEW -m tcp -p tcp --dport 22 -m recent --rcheck --seconds 30 --name SSH2 -j ACCEPT
最後のルールは、接続してきた IP が SSH2
のリストに載っている場合に、ポート22を30秒間開きます。最初に条件が確認されるチェインの一番上に持ってくることで、まず接続試行のリストを作成して、後にポートノッキングの判別に使います。上記の例では、30秒後にポートは閉じられますが、他には何も行われません。したがって、同じ IP から再度ポートノッキングを行うことができます。
最後のルールがトラフィックを承認しなかった場合 (例: 30秒間接続がなかった場合) でもリストの IP が SSH2
に接続できるように、リストから IP を削除して最初からノックするようにします。シーケンスを適切に処理するために、リストを確認したら直後に削除するのが重要です。
-A TRAFFIC -m state --state NEW -m tcp -p tcp -m recent --name SSH2 --remove -j DROP
最初に処理したシーケンスの末端にきて、ここで以下のルールでポートシーケンスの確認を行います。一つずつノックするポートが正しいかどうか確認するルールを作成します。シーケンスが問題なければ、IP をリストに追加して次のノックにジャンプします。SSH-INPUT
や SSH-INPUTTWO
にジャンプされなかった場合、間違ったポートがノックされた、あるいは (こちらの方が多いですが) 他のトラフィックだということです。したがって、次のルールで SSH2
のルールと同じように、リストから IP を削除してトラフィックを拒否します。
-A TRAFFIC -m state --state NEW -m tcp -p tcp --dport 9991 -m recent --rcheck --name SSH1 -j SSH-INPUTTWO -A TRAFFIC -m state --state NEW -m tcp -p tcp -m recent --name SSH1 --remove -j DROP
次のポートのノックも同じように設定します。同じリストに対応するルールが組み合っていて正しい順番であれば、TRAFFIC
チェインのシーケンスの順番は自由にできます。
-A TRAFFIC -m state --state NEW -m tcp -p tcp --dport 7777 -m recent --rcheck --name SSH0 -j SSH-INPUT -A TRAFFIC -m state --state NEW -m tcp -p tcp -m recent --name SSH0 --remove -j DROP
ルールの最後のブロックでは、接続試行した IP を各自の recent リストに設定して、IP がノッキングシーケンスの次のステップに行けるように許可します。
まずはシーケンスの最初のノックのルールで、新しい接続試行がポートノッキングの開始かもしれないのでメインチェインの TRAFFIC
の一部としてチェックします。正解 (正しいポート) ならノックを最初のリスト SSH0
にセットします。ルールの最後のブロックで SSH0
を確認して、2番目のノック (7777) を確認するルールでは1番目のポートの recent ノックが必要とし、問題ない場合に次の recent リスト (SSH1
) をセットします。これによってリストがシーケンス処理されます。
-A TRAFFIC -m state --state NEW -m tcp -p tcp --dport 8881 -m recent --name SSH0 --set -j DROP -A SSH-INPUT -m recent --name SSH1 --set -j DROP -A SSH-INPUTTWO -m recent --name SSH2 --set -j DROP -A TRAFFIC -j DROP COMMIT
正しいポートがノックされた場合でも、トラフィックは最後のルールでドロップしていることに注意してください。この DROP は接続試行が実はノックであることを他人に悟られないように隠すために設定しています。
ルールの設定が完了したら、新しく設定したルールを使って iptables.service
を起動します:
# systemctl daemon-reload # systemctl restart iptables
上記のコマンドを全て実行した後の iptables.rules
ファイルは以下のようになります:
*filter :INPUT DROP [0:0] :FORWARD DROP [0:0] :OUTPUT ACCEPT [0:0] :TRAFFIC - [0:0] :SSH-INPUT - [0:0] :SSH-INPUTTWO - [0:0] # TRAFFIC chain for Port Knocking. The correct port sequence in this example is 8881 -> 7777 -> 9991; any other sequence will drop the traffic -A INPUT -j TRAFFIC -A TRAFFIC -p icmp --icmp-type any -j ACCEPT -A TRAFFIC -m state --state ESTABLISHED,RELATED -j ACCEPT -A TRAFFIC -m state --state NEW -m tcp -p tcp --dport 22 -m recent --rcheck --seconds 30 --name SSH2 -j ACCEPT -A TRAFFIC -m state --state NEW -m tcp -p tcp -m recent --name SSH2 --remove -j DROP -A TRAFFIC -m state --state NEW -m tcp -p tcp --dport 9991 -m recent --rcheck --name SSH1 -j SSH-INPUTTWO -A TRAFFIC -m state --state NEW -m tcp -p tcp -m recent --name SSH1 --remove -j DROP -A TRAFFIC -m state --state NEW -m tcp -p tcp --dport 7777 -m recent --rcheck --name SSH0 -j SSH-INPUT -A TRAFFIC -m state --state NEW -m tcp -p tcp -m recent --name SSH0 --remove -j DROP -A TRAFFIC -m state --state NEW -m tcp -p tcp --dport 8881 -m recent --name SSH0 --set -j DROP -A SSH-INPUT -m recent --name SSH1 --set -j DROP -A SSH-INPUTTWO -m recent --name SSH2 --set -j DROP -A TRAFFIC -j DROP COMMIT # END or further rules
nftables によるポートノッキング
nftables のみによる、ポートノッキングの例。
クライアントスクリプト
設定が完了したら、ポートノッキングを行うツールが必要です。上記で触れている knockd には簡単に設定できる knock
ツールが付属しています。上流のサイトでは他の OS 用の knock
ツールが配布されています。
ここでは nmap を使います。シンプルなシェルスクリプト (knock.sh
) でポートノッキングを自動化します:
knock.sh
#!/bin/bash HOST=$1 shift for ARG in "$@" do nmap -Pn --host_timeout 100 --max-retries 0 -p $ARG $HOST done
以下では上記のスクリプトを使います。他の影響を考慮して、テストはローカルホストで行います。
まず、ネットワークケーブルを接続してから、SSHD が listen する IP を設定します:
[user@host ~]# ip link set up dev enp8s0 [user@host ~]# ip address add 192.168.1.1/24 dev enp8s0 [user@host ~]# ip route add default via 192.168.1.1 [user@host ~]# systemctl status sshd |grep listening Aug 21 14:36:53 host sshd[3572]: Server listening on 192.168.1.1 port 22
次に、SSHD が接続を承認するかどうか確認、そしてスクリプトを実行した後に SSH ログインに成功することを確認します:
$ ssh user@host # No response (Ctrl+c to exit) ^C $ sh knock.sh host 8881 7777 9991 $ history -r $ ssh user@host # Now logins are allowed user@host's password: Last login: Tue Aug 20 23:00:27 2013 from host
最初の接続試行は、接続が DROP されて何も応答しないため停止するはずです。最後のルールの DROP を REJECT に変更しておけば、代わりに $ connection refused
が返ってきます。最後に、ログインが成功したら、カーネルの recent リストでノックが成功していることを確認します:
[user@host ~]$ cat /proc/net/xt_recent/SSH* src=192.168.1.1 ttl: 64 last_seen: 296851 oldest_pkt: 1 296851 src=192.168.1.1 ttl: 64 last_seen: 297173 oldest_pkt: 1 297173 src=192.168.1.1 ttl: 64 last_seen: 297496 oldest_pkt: 1 297496 [user@host ~]$ exit logout Connection to 192.168.1.1 closed.
fwknop
fwknop は、より単純なポートノッキング方法について、上記の制限のいくつかを克服しようとするものです。その代償として、より複雑で、主に保護されるべきサーバのリソースを使用することになります。ポートノックと Single Package Authorization (SPA) を提供します。libpcap
と暗号化方式を使用することにより、その目的を達成します。tcpdump の基盤ライブラリである libpcap を使用することで、受信パケットをすべて検査することが可能です。ファイアウォールが通さないようなパケットも含まれます。ファイアウォールが通さないパケットも、どのサービスもオープンに聞いていないパケットも含めてです。暗号化技術により、攻撃者が以前のパケットを再送することで fwknop を迂回することを防止します。