[緊急警報] Angular SSRに高深刻度XSS脆弱性 (CVE-2026-88060) - フロントエンド開発者は要確認
概要と影響範囲:Angular SSRにおけるXSSの脅威
この脆弱性 (GHSA-v3p8-whq6-r5jg / CVE-2026-88060) は、Angularアプリケーションのサーバーサイドレンダリング (SSR) において、HTMLのシリアライズ処理に存在するクロスサイトスクリプティング (XSS) の問題です。深刻度は「High」と評価されており、影響を受けるのは主に`@angular/platform-server`を利用しているSSRアプリケーションです。
具体的には、攻撃者が制御する悪意のある入力が、特定のHTML要素の組み合わせ(特に`<template>`要素と「fallback raw-content elements」)内でレンダリングされる際に、Angularが通常提供するはずのXSS保護機構がバイパスされ、結果として任意のJavaScriptが実行されてしまう可能性があります。
脆弱性の技術的詳細:なぜエスケープが機能しないのか
この脆弱性の根源は、HTML5のパース挙動とAngularのSSRシリアライザの内部動作のミスマッチにあります。
1. **HTML5のRAWTEXTモード**: `<noscript>`, `<iframe>`, `<noembed>`, `<noframes>`といった特定の要素(これらを「fallback raw-content elements」と呼びます)は、ブラウザのHTMLパーサーを「RAWTEXTモード」に切り替えます。このモードでは、コンテンツは特別な意味を持たず、対応する終了タグ(例:`</noscript>`)が見つかるまで、すべてがリテラルなテキストとして扱われます。
2. **SSRシリアライザの役割**: AngularのSSRシリアライザは、XSS攻撃を防ぐために、これらのRAWTEXTモードをトリガーする要素内で、内部のコンテンツに含まれる終了タグ(例:`</noscript>`)をHTMLエンティティ(`</noscript`など)にエスケープするよう設計されています。
3. **`<template>`要素の特殊性**: 問題は、`<template>`要素の子要素が独自の`DocumentFragment`(`template.content`)内に存在し、この`DocumentFragment`の`parentNode`が`null`である点です。シリアライザがノードの祖先要素をトラバースしてRAWTEXTモードのコンテナを検出する際、`DocumentFragment`の境界でその走査を停止してしまっていました。
4. **結果としてのエスケープ漏れ**: シリアライザが外側のRAWTEXTモードのコンテナ(例:`<noscript>`)を検出できなかったため、その内部、特に`<template>`要素内にあるユーザー入力に含まれる終了タグがエスケープされずにそのまま出力されてしまいます。これにより、ブラウザがレンダリングする際に、そのエスケープされていない終了タグがRAWTEXTモードを早めに終了させ、続く悪意のあるHTML/JavaScriptが実行されてしまうのです。
具体的な脆弱性発生シナリオとPoC
この脆弱性は、以下のような条件下で発生し得ます。
1. **要素の構成**: `<noscript>`, `<iframe>`などの「fallback raw-content elements」の内側に`<template>`要素がネストされ、さらにその内側に攻撃者が制御可能なユーザー入力が、生のテキストタグ(`<xmp>`, `<style>`, `<script>`)、コメント、またはテキストノードとして配置される場合。
2. **Angularの保証バイパス**: Angularは通常、標準的なテキスト補間(`{{ userInput }}`)がXSSに対して安全であることを保証していますが、この脆弱性はその保証をバイパスします。
3. **スキーマチェックの影響**: `<xmp>`や`<style>`のようなタグをテンプレート内で直接使用するには、`CUSTOM_ELEMENTS_SCHEMA`や`NO_ERRORS_SCHEMA`といった厳格でないスキーマ設定が必要になる場合があります。しかし、標準的なHTMLコメントやテキストノードであれば、これらのスキーマチェックなしで脆弱性が誘発され得ます。また、`Renderer2`などのAPIを用いてプログラムでDOM構造を構築するケースでは、テンプレートコンパイラのスキーマチェック自体がバイパスされるため、無条件に影響を受けます。
提供されている最小限の再現コード (PoC) は以下の通りです。
```typescript import { Component } from '@angular/core'; @Component({ selector: 'app-root', standalone: true, template: ` <noscript> <template> <xmp>{{ payload }}</xmp> </template> </noscript> ` }) export class AppComponent { // 攻撃者が制御する入力(標準的なテキスト補間を介してバインドされる) payload = '</noscript><img src=x onerror=alert("SSR_TEMPLATE_XSS")>'; } ```
このコードを脆弱性のあるAngular SSR環境で実行すると、以下のようなHTMLが出力されます。
```html <noscript><template><xmp></noscript><img src=x onerror=alert("SSR_TEMPLATE_XSS")></xmp></template></noscript> ```
ご覧の通り、`<xmp>`内の`payload`に含まれる`</noscript>`がエスケープされずにそのまま出力されています。ブラウザはこの`</noscript>`を本来の終了タグとして解釈し、`<noscript>`要素を prematurely に閉じてしまいます。その結果、後続の`<img src=x onerror=alert("SSR_TEMPLATE_XSS")>`がアクティブなDOM要素としてパースされ、XSS攻撃が成功します。
対策と回避策
この脆弱性への対策として、以下の点に注意してください。
1. **Angularのアップデート**: 最も推奨される対策は、この脆弱性が修正されたAngularのバージョンに速やかにアップデートすることです。CVE IDがCVE-2026-88060と未来の日付ですが、情報公開された時点で修正版が利用可能になっているはずです。公式のアナウンスやリリースノートを確認し、可能な限り早く対応してください。
2. **回避策 (Workarounds)**:
* **ユーザー入力の配置を避ける**: サーバーサイドレンダリングされるテンプレートにおいて、信頼できないユーザー入力を、`<noscript>`, `<iframe>`, `<noembed>`, `<noframes>`といった「fallback raw-content elements」内にネストされた`<template>`要素内では決してレンダリングしないでください。
* **プログラムによるDOM構築の注意**: 信頼できないデータを扱う際に、`Renderer2`などのAPIを用いてプログラムでDOM構造を構築する場合、特にこれらのfallbackコンテナ内に`<template>`要素を作成するパターンを避けてください。
この脆弱性は、Angularが提供する強力なセキュリティ保証が特定の条件下で破られる可能性を示しています。SSRを利用しているフロントエンドエンジニアの皆様は、ご自身のアプリケーションが影響を受けないか、速やかに確認し、適切な対策を講じることを強く推奨します。