Angular SSR: `ProcessingInstruction`ノード(`<?target data?>`、`nodeType === 7`)が、フォールバックの生コンテンツ要素(`<noscript>`, `<iframe>`, `<noembed>`, `<noframes>`)内にネストされている場合に、エスケープ処理が不十分なためにXSS脆弱性が存在する。処理指示のデータにおいて`>`が`>`に置換されるものの、親のフォールバック要素の対応する閉じタグ(例:`</noscript>`)がチェックまたはエスケープされていない。スクリプトが有効なブラウザでレンダリングされると、処理指示に含まれるエスケープされていない閉じタグのシーケンスが、フォールバックの生コンテンツタグを早期に閉じ、その結果、後続の兄弟要素が実行可能なHTMLとして解釈される。
### 技術的な説明
HTML5の解析において、フォールバックの生コンテンツ要素(`<noscript>`, `<iframe>`, `<noembed>`, `<noframes>`)は、ブラウザのHTMLトークナイザーを`RAWTEXT`モードにする。`RAWTEXT`モードでは、処理指示トークン(`<?...?>`)は、無効なコメントではなく、リテラルな生テキストとして扱われ、`>`または`?>`は無視される。コンテナを終了させる唯一のトークンシーケンスは、コンテナタグ名に対応する終了タグ(例:`</noscript>`, `</iframe>`など)である。
サーバーサイドでのHTMLシリアライゼーション中、以前は処理指示ノードにおいて`>`のみが`>`に置換されていた(通常のHTMLデータ状態における無効なコメントの発生を防ぐため)が、`<`は変更されなかった。重要な点として、処理指示のシリアライゼーションは、親のフォールバックの生コンテンツタグを一切検査しなかった。その結果、`<noscript>`内に`</noscript `という文字列を含む`ProcessingInstruction`ノードは、エスケープされずに`<?x </noscript ?>`として出力された。
### 影響と到達可能性
* **到達可能性**: プロセッシング指示ノードを直接作成することは、標準的なAngularテンプレート構文ではできません(`<?...>`はDOM上でコメントノードとして解析されます)。 この脆弱性を利用するには、アプリケーションまたはライブラリのコードが、信頼できないユーザー入力を`data`に渡し、`inject(DOCUMENT).createProcessingInstruction(target, data)`または`Renderer2`のDOM挿入メソッドを呼び出す必要があります。
* **影響**: サーバーサイドレンダリング中に、フォールバック要素内でプロセッシング指示ノードをプログラム的に構築するアプリケーションにおいて、プロセッシング指示のデータを制御する攻撃者は、コンテナから抜け出し、被害者のブラウザで任意のJavaScriptコードを実行できる可能性があります。
### 実証コード (最小限の再現)
```ts
import { Component, ElementRef, Renderer2, inject, DOCUMENT, AfterViewInit } from '@angular/core';
@Component({
selector: 'app-root',
standalone: true,
template: `<noscript id="host"></noscript>`
})
export class AppComponent implements AfterViewInit {
private r = inject(Renderer2);
private el = inject(ElementRef);
private doc = inject(DOCUMENT);
ngAfterViewInit() {
const host = this.el.nativeElement.querySelector('#host');
// 攻撃者が制御する入力がプロセッシング指示のデータとして渡される
const pi = this.doc.createProcessingInstruction('x', '</noscript ');
this.r.appendChild(host, pi);
// <noscript>タグ内で静止しているべきマークアップ
const img = this.r.createElement('img');
this.r.setAttribute(img, 'src', 'x');
this.r.setAttribute(img, 'onerror', 'alert("SSR_PI_XSS")');
this.r.appendChild(host, img);
}
}
```
**脆弱なSSR出力**:
```html
<noscript><?x </noscript ?><img src="x" onerror="alert('SSR_PI_XSS')"></noscript>
```
### 対策
* サーバーサイドレンダリング中に、ノードが `<noscript>`, `<iframe>`, `<noembed>`, または `<noframes>` に挿入される場合、`document.createProcessingInstruction(target, data)` に信頼できないユーザー入力を渡さないようにしてください。
* サーバー側で、処理命令ノードに渡される信頼できないデータ内の `<` を手動で `<` に変換するか、別の文字に置き換えてください。
影響を受けるパッケージ:
- npm (JavaScript) @angular/platform-server: 影響を受けるバージョン >= 22.0.0、< 22.1.4、修正済みバージョン 22.1.4
- npm (JavaScript) @angular/platform-server: 影響を受けるバージョン >= 21.0.0、< 21.2.22、修正済みバージョン 21.2.22
- npm (JavaScript) @angular/platform-server: 影響を受けるバージョン >= 20.0.0、< 20.3.30、修正済みバージョン 20.3.30
- npm (JavaScript) @angular/platform-server: 影響を受けるバージョン <= 19.2.25、修正済みバージョン なし