hnwの日記

ポートノッキングで10秒間だけsshdを公開する設定

先日Twitterに次のような書き込みをしたところ思ったより反応が良かったので、詳細の設定を紹介します。

といっても特殊なことをしたわけではなく、knockdでポートノッキングの設定を行い、iptablesと組み合わせて実現しました。

ポートノッキングとは

ポートノッキングというのは、決められたポートを決められた順番で叩くことでファイアーウォールに穴を空けられるような仕組みのことです。ポートノッキングを使えば、TCPの7000番、8000番、9000番の3ポートにパケットを送りつけると22番ポート (SSH) へのアクセスが許可される、といった設定ができます。

ポートノッキングの実現にはknockdというデーモンを使うことが多いようです。knockdはlibpcapと組み合わせて使う前提のツールで、どのポートもlistenせずにポートノックを監視できるのが特徴です。

iptablesの設定

早速ですが、筆者の行った設定を紹介しましょう。今回の対象サーバはDebian 8ベースでした。まずはiptables関連のパッケージをインストールします。

$ sudo apt-get install iptables iptables-persistent

次にiptablesの設定ですが、TCP22番ポート (SSH) へのアクセスを全てふさぎます(6行目)。ただし、トラブル時の非常用経路(下記の例では 192.0.2.0/24)からのアクセスだけは許可しておき、以降の設定はこちらから行います(5行目)。

$ sudo iptables -t mangle -A PREROUTING -p tcp --dport 443 -j MARK --set-mark 443
$ sudo iptables -A INPUT -i lo -j ACCEPT
$ sudo iptables -A INPUT -m conntrack --ctstate RELATED,ESTABLISHED -j ACCEPT
$ sudo iptables -A INPUT -p tcp -m tcp --dport 22 -m mark --mark 443 -j ACCEPT
$ sudo iptables -A INPUT -s 192.0.2.0/24 -p tcp -m tcp --dport 22 -j ACCEPT
$ sudo iptables -A INPUT -j DROP

TCP443番からのパケットにはPREROUTINGチェインでマークを付けておくように設定し、マークの付いたパケットのTCP22番へのアクセスは許可する、という設定にしてあります(1行目、4行目)。これはknockdの方の設定と組み合わせて利用します。

最後に、次のように設定を保存します。

$ sudo netfilter-persistent save

ポートノッキングの設定

今度はポートノッキングの設定です。まずはknockdをインストールしましょう。

$ sudo apt-get install knockd

次に、/etc/knockd.confを次のように書き換えます。

[options]
	UseSyslog

[SSH]
	sequence    = 53:udp,443:tcp,123:udp
	seq_timeout = 5
	command     = /sbin/iptables -t nat -A PREROUTING -s %IP% -p tcp --dport 443 -j REDIRECT --to-port 22
	tcpflags    = syn
	cmd_timeout = 10
	stop_command = /sbin/iptables -t nat -D PREROUTING -s %IP% -p tcp --dport 443 -j REDIRECT --to-port 22

sequenceはポートノッキングのポートと順序を指定するものです。今回はUDP53番 (DNS)、TCP443番 (HTTPS)、UDP123番 (NTP) という3つのポートを叩くとポートノッキングが成立するよう設定しました。もちろん、これらのポートにはデーモンは立てていません。

commandはポートノッキング成立時に実行するコマンドで、TCP443番へのパケットを22番ポートに転送するようにしました。上で紹介したiptablesの設定と組み合わさることで、443番へアクセスするとsshdが返事を返す状態になるわけです。

この設定はポートノッキング元のIPアドレスに対してのみ有効になっているということにも注目してください( command の指定にある -s %IP% で実現しています)。ポートノッキング成立と同じタイミングで別の誰かが443番ポートに接続しようとしても単にファイアウォールがパケットを落としてしまうだけで、sshdにアクセスされることはありません。

cmd_timeoutstop_commandはポートを閉じるための設定です。この例では、ポートノッキング成立から10秒後に先ほどの443番から22番への転送設定を削除しています。

最後にknockdを再起動します。

$ sudo service knockd restart

これでポートノッキングを行うことで10秒だけSSHアクセスが可能になる、という設定が完成したわけです。

ポートノッキングに利用するポートの選択

ポートノッキングに利用するポートは本来何でもいいのですが、たまに外向きのアクセスが一部制限されている環境があります。社内ネットワークなどで必要なポートのみ開放されている場合もあるでしょうし、最近は多くのインターネットプロバイダで外向きのTCP25番ポートがブロックされているはずです。

そうした観点から、多くの環境で開いているのではないか?と考えて筆者はDNS、NTP、HTTPSという3つを選びました。身近な環境で外向きアクセスが制限されていないのであれば、7000番以降など後ろの方のポートを使った方がいいかもしれません。

.ssh/configの設定

このポートノッキングは手動で行うこともできますが、毎回手動で叩くのは面倒すぎますよね。それに、モタモタしていると10秒経ってSSHのポートが閉じられてしまうかもしれません。

そこで、ポートノッキングするコマンドを.ssh/configに組み込んでポートノッキングを自動で行うようにしてみましょう。

まずはknockコマンドをインストールします。私は手元のマシンはMacなので、次のようにHomebrewでインストールします。

$ brew install knock

下記のように.ssh/configProxyCommandでknockコマンドを指定することで、SSHログインの直前にポートノッキングを行います。

Host host1.example.com
  Port 443
  ProxyCommand bash -c '/usr/local/bin/knock -d 100 %h 53:udp 443:tcp 123:udp; sleep 1; exec /usr/bin/nc %h %p'

knockコマンドの-dはポートを叩く間隔(ミリ秒)です。テザリング回線を使う場合など待ち時間が短すぎるとうまく動作しないことがありますので、サーバ側のログを見ながら調整してください。

こうしておけば、普段はポートノッキングの存在すら忘れて他のマシンと同様に利用できるわけです。

まとめ

以上で、ポートノッキングの順序を知らない人はSSHログインを試みることすらできない、それでいて設定を知っていれば全自動でポートノッキングを行ってSSHログインできるような環境が構築できました。

念のため補足しておくと、ポートノッキングはセキュリティの観点では気休め程度のテクニックです。ポートノッキングに利用するパケットはそのままネットワーク上を流れますから、途中経路でのネットワーク盗聴などがあれば簡単に再現できてしまいます。間違ってもtelnetdなどセキュアでないサービスの公開にポートノッキングを使ってはいけません。

逆に言うと、ポートノッキングは元々セキュアなサービスを隠すような用途でしか使えません。基本的には「カッコいい」以外のメリットがない仕掛けだと言えるでしょう(今回は実益があったので設定したのですが、それは別の機会に紹介します)。

また、個人的にはknockdを業務で利用することはあまりオススメしません。引き継ぎが面倒になるだけでセキュリティ面の効果は気休め程度、と考えればデメリットの方が大きいくらいだと思います。

一方で、個人用サーバであれば無駄な苦労をしても困るのは自分だけですから、面白いと感じたことはドンドンやればいいと思います。こうした遊びを通じてネットワークまわりの知識が増えていけば、いずれ業務にも生きてくるはずです。