ポートノッキング

提供: ArchWiki
移動先: 案内検索

ポートノッキングとはデフォルトではファイアウォールによって閉じられているポートを外部から密かに開く方法です。ポートノッキングでは予め決めておいた順番で閉じているポートを叩きます。正しい順番のポートの"ノック" (接続試行) を受け取ったとき、ファイアウォールは特定のポートを開いて接続を許可します。

標準的なポートスキャンに対して、ポートのサービスが使われていないかのようにみせかけることができるという利点があります。この記事ではデーモンや iptables を使ってポートノッキングを利用する方法を説明します。

警告: ポートノッキングはあくまでセキュリティ戦略の一環として考えてください。ポートノッキングだけでシステムを防護することは不可能です。遮蔽によるセキュリティ (攻撃者が知らないことを前提とするセキュリティ) は脆いものです。SSH を保護する場合、ポートノッキングと組み合わせて使うことができる強固な方法として SSH 鍵があります。また、上記の例で使われているポートは実際には使わないでください。

イントロダクション

以下に進む前に 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:
ノート: iptables ではポートノッキングを一つのルールで設定できるコマンドが開発されています。多数の高度なオプションも用意されていますが、標準カーネルではまだ利用できません (2013年8月現在)。興味がある場合、次を参照: xtables-addonsAUR

ノックに使用するポートはランダムに選択するのが良いでしょう。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-INPUTSSH-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 

ポートノッキングスクリプト

設定が完了したら、ポートノッキングを行うツールが必要です。上記で触れている 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.
警告: 上記の設定によるセキュリティを保証することはできません。あくまで特定のポートのサービスの存在を遮蔽できるというだけです。他の方法でもセキュリティを守るようにしてください。上記の設定を使うときは、自己責任で行なってください。

参照