Angularの「安全神話」を解体する:DomSanitizerの盲点とDOM-XSSの深層
多くのフロントエンドエンジニアは、Angularがデフォルトで提供するXSS(クロスサイトスクリプティング)対策を「魔法の盾」だと信じている。{{ value }}と書けば自動的にサニタイズされ、HTMLタグはエスケープされる。確かに、フレームワークレベルでの自動サニタイズは非常に強力だ。しかし、この「安全」はあくまでデフォルト設定という名のガードレールに過ぎない。
現場で我々が対峙するインシデントの多くは、開発者がこのガードレールを自ら破壊した瞬間に発生する。DomSanitizerという、諸刃の剣をどう制御するか。今日は、その内部構造と、現代の攻撃者がどこを狙っているのかを紐解いていく。
—
1. コンテキスト依存のセキュリティ:なぜ「安全」は破られるのか
AngularのDomSanitizerは、ブラウザのDOM APIが持つ「コンテキスト」を厳密に管理している。URL、HTML、Style、Scriptといった属性ごとに、Angularは内部的にホワイトリストによる検証を行っている。
しかし、攻撃者は「コンテキストの混同」を狙う。例えば、javascript:スキームを含むURLをDOMのhref属性に注入する場合、Angularはこれを危険と判断して無害化する。だが、開発者が「このデータは安全だ」と明示的にマーキングした瞬間、Angularの防衛機構は沈黙する。
脆弱性を生む「TrustedValue」の安易な利用
import { DomSanitizer } from ‘@angular/platform-browser’;
// 危険なパターン:外部入力をそのまま信頼してしまっている
// この関数をユーザー入力に対して使用すると、即座にXSSのバックドアが開く
public getTrustedHtml(userInput: string) {
// 警告:bypassSecurityTrustHtmlは、フレームワークの検閲を「無効化」するコマンドだ
// 開発者が責任を負うという契約を強制的に結ばされる
return this.sanitizer.bypassSecurityTrustHtml(userInput);
}
このbypassSecurityTrustHtmlは、いわば「セキュリティ監査のバイパスキー」だ。一度これを通せば、AngularはDOMノードの生成において一切の検証を行わない。攻撃者がを送り込めば、ブラウザはそのコードを疑うことなく実行する。
—
2. 攻撃者の視点:プロトコルハンドラーとメモリ破壊の境界
現代の攻撃者は、単なるタグの注入だけでは満足しない。彼らが狙うのは、ブラウザのパーサーが「HTMLとして解釈せざるを得ない」微妙な隙間だ。
例えば、エンコーディングを利用したバイパス。HTMLタグの中にjavascript:のような数値文字参照を混ぜると、一部のレガシーなパーサーや、サニタイズ処理が不完全なライブラリは、これを「安全なURL」として誤認する可能性がある。Angularのサニタイザーは堅牢だが、もしあなたがAngularの管理外でdangerouslySetInnerHTMLのようなDOM操作を併用しているなら、防衛ラインは一瞬で崩壊する。
—
3. 防衛アーキテクチャの構築:ゼロトラスト・フロントエンド
では、どう守るべきか。結論はシンプルだ。「信頼してはならない」。
サニタイズの多層防御(Defense in Depth)
1. Strict Content Security Policy (CSP) の厳格化:
'unsafe-inline'は絶対に排除する。万が一、アプリケーション内でXSSが成立しても、インラインスクリプトをブロックするCSPが機能していれば、攻撃者のペイロードは実行できない。
# CSPヘッダーの例
Content-Security-Policy: default-src ‘self’; script-src ‘self’; object-src ‘none’;
2. DOMPurifyの併用:
DomSanitizerを盲信するのではなく、レンダリング直前にDOMPurifyを使用して、HTMLを厳格にクレンジングすることを推奨する。AngularのバイパスAPIを使う前に、DOMPurifyを通して「本当に安全なタグと属性だけ」に絞り込むのだ。
import as DOMPurify from ‘dompurify’;
// 適切なガードレールの設計
public getSanitizedHtml(dirtyHtml: string) {
// 1. まずDOMPurifyで攻撃パターンを徹底的に除去
const clean = DOMPurify.sanitize(dirtyHtml);
// 2. その上でAngularに「信頼できる」と伝える
return this.sanitizer.bypassSecurityTrustHtml(clean);
}
—
4. チーフホワイトハッカーからの提言:AI時代のリスク管理
今、生成AIがコードを書く時代において、この脆弱性はさらに深刻化している。AIは「動くコード」を生成することには長けているが、「セキュリティ的に堅牢なコード」を書くことには興味がない。AIに「AngularのHTMLレンダリングを実装して」と頼めば、高確率でbypassSecurityTrustHtmlを提案してくるだろう。
アーキテクトへの問いかけ:
貴方のプロジェクトで、bypassSecurityTrustHtml や bypassSecurityTrustResourceUrl が使われている全ての箇所を、今すぐgrepしてほしい。もし、そこに「なぜこのバイパスが必要なのか」という理由と、「入力値がどこから来ているのか」という追跡ログ(トレース)が残っていないなら、それは既に脆弱性インシデントの予兆である。
セキュリティとは、ツールを信じることではなく、ツールが「どこまで守ってくれないのか」を理解することから始まる。Angularのサニタイズ機能は優秀な門番だが、鍵を渡す相手を間違えれば、城は簡単に陥落する。その鍵を管理するのは、フレームワークではなく、貴方自身であることを忘れないでほしい。
コメント