やりたいこと
最初はどのページもアクセスできないけど、
秘密の順番でパスにアクセスしていくと、使えるようになる!
ようなサーバーを作りたいです
一言でいえば、ポートノッキングのHTTP版みたいなものを作りたいです。
ポートノッキングで有名なツイートです。
UDP53番、TCP443番、UDP123番とポートノッキングをするとTCP443番に10秒だけsshdが現れる、という中二病全開の設定をした。皆様にもお勧めしたい。
— hnw (@hnw) 2017年3月26日
ポートノッキングというのは、決められたポートを決められた順番で叩くことでファイアーウォールに穴を空けられるような仕組みのことです。
(引用:ポートノッキングで10秒間だけsshdを公開する設定 - hnwの日記)
HTTP上でノッキングしたい理由
ポートノッキングだとHTTPSを使って暗号化していても、ポートの部分は暗号化されないはず1 なので、傍受しようと思えばできてしまうのかなと思いました。そこで、HTTPS上で、ノッキングすればHTTPヘッダもボディも暗号化されるため、ノッキングも暗号化できるのではと思い、セキュアになるかなと思ってやりたくなりました。また、HTTPを使うので、ブラウザからも簡単にノッキングできるので、ポートノッキングより容易にできるのではないかというのも一つ理由です。
設計の方針
- なるべくあらゆるサイトに適用したいので、リバースプロキシとして作り、隠したいサーバーのURLを指定するようにする2
- Dockerイメージを用意して、Docker Composeで簡単にノッキングサーバを適用できるようにする
- オプションで自動でクローズしたりノッキングの間隔が空きすぎるとリセットするなどセキュアにする
- HTTPSの対応はHTTPS-PORTALやHerokuなどのようにHTTPをHTTPS化してくれるようなシステムの利用を想定しています
http-knockingの使い方(npm)
以下のようにnpmで簡単にインストールができるようにしました。コマンドとして利用するので-g
オプションでグローバルにインストールしてます。(Docker Composeでの使い方は次に書きます)
npm install -g http-knocking
以下が実行の方法です
- http://localhost:8181が隠したいサーバーです
- http://localhost:8282にノッキングサーバーが立ち上がります
http-knocking --port=8282 --target-host=localhost --target-port=8181 --open-knocking="/hirake,/goma" --close-knocking="/tojiyo,/goma"
大雑把な構成は以下のようになります。
(PCの絵:パソコンのイラスト(無料イラスト)フリー素材)
http://localhost:8282にアクセスすると最初は白紙のページです。以下の順番でアクセスすると、
- http://localhost:8282/hirake
-
http://localhost:8282/goma
ノッキングが成功して、localhost:8282
でlocalhost:8181
の内容にアクセスできるようになり、ちゃんと隠していたサーバーが使えるようになりました!
(閉じるときは/tojiyo
->/goma
の順番でアクセスすれば閉じます)
http-knockingの使い方(Docker Compose)
Docker Composeを使う方法だと、npmなしで利用することができます。
Alpineのおかげで、http-knockingのDockerイメージのサイズが87MBと割と小さくなりました。そのためそこまで負担なく導入しやすいのではないか思います。
隠したいWebサーバーとして、GhostのDockerイメージghostを使ってみました。
以下の起動方法は、以下の内容をdocker-compose.yml
として保存して、docker-compose up
で起動します。起動後はhttp://localhost:8282にノッキングサーバーが立ち上がります。
version: '3.1'
services:
http-knocking:
image: nwtgck/http-knocking:v0.3.0
ports:
- '8282:8282'
depends_on:
- ghost
restart: always
command: --port=8282 --target-host=ghost --target-port=2368 --open-knocking="/alpha,/foxtrot,/lima"
ghost:
image: ghost
restart: always
expose:
- "2368"
Docker Composeを使っているため、隠したいサーバー(今回のGhost)のポートにアクセスできないようにでき、よりわかりやすくてセキュアになっていると思います。あとは、これにHTTPS-PORTALを組み合わせてHTTPS化するというのを想定しています。
以下が、実際のノッキングの動作です。'/alpha' -> '/foxtrot' -> 'lima'の順番でアクセスすると、ちゃんと開くのが確認できます。(--close-knocking
を指定していなので、逆順でノッキングすることで閉じます)
その他のオプション
セキュリティを高める目的などでいくつかオプションを用意しています。
バージョン0.3.0の時点で以下のオプションが利用可能です。
Options:
--help Show help [boolean]
--version Show version number [boolean]
--port Port of knocking server [required]
--target-host Target host to hide [required]
--target-port Target port to hide [default: 80]
--open-knocking Open-knocking sequence (e.g.
"/alpha,/foxtrot,/lima") [required]
--close-knocking Close-knocking sequence (e.g.
"/victor,/kilo")
--enable-websocket Enable WebSocket proxy [default: false]
--auto-close-millis Time millis to close automatically
--open-knocking-max-interval-millis Time millis to reset open procedure
セキュリティを高めるには--auto-close-millis
や--open-knocking-max-interval-millis
が便利だと思います。
--auto-close-millis
は開いたあと何ミリ秒たったら、自動でクローズするかを指定します。デフォルトだと自動でクローズしません。
--open-knocking-max-interval-millis
はパスを指定する時間間隔の最大ミリ秒を指定します。例えば、10000と指定すれば、10秒以内に次のパスにアクセスする必要があります。その次のパスにアクセスするときもまた10秒以内にその次の次のパスにアクセスする必要があります。デフォルトだと無制限です。
WebSocket対応
0.3.0からWebSocketもリバースプロキシ対応しました。
--enable-websocket
オプションを指定することで、WebSocketの通信も通したり隠したりできるようになります。--enable-websocket
オプションを指定しない場合は、WebSocketのリバースプロキシはされません。
(追記:2018/08/21)
GitHub
TypeScriptで実装しています。
https://github.com/nwtgck/http-knocking
以下のような方法で、install -g
せずに使ってもいいと思います。
git clone https://github.com/nwtgck/http-knocking.git
cd http-knocking
npm install
npm start -- --port=8282 --target-url=<ターゲットURL> --open-knocking="/alpha,/foxtrot,/lima"
作ろうと思ったきっかけ
あんまり関係のない話ですが、最後に作ろうと思ったきっかけについてです。
curl
コマンドとかでファイルを送信したり、受信したりするサーバーを作っていて、そのサーバーの利用を制限する方法がないかなと思ったのがきっかけでした。利用を制限すると、悪意もった人が巨大なファイルを送るのを防いだり、その他悪意のあるアクセスも少しは防げるのではないかと思ってます。
あと幸運なことに、通信が確立した後にノッキングサーバーをクローズしても、ファイルの送受信は進み続けるので、確立さえすればHTTPボディが巨大なリクエストやレスポンスもhttp-knockingでクローズ後もアクセス可能でした。クローズ後は新しい送受信の始まりが確立することはないので、目指していたよりセキュアな状態が可能になったのかなと思っています。