リンクの悪夢:javascript:スキームと「ブラウザの迷走」を解剖する
セキュリティアーキテクトとして多くのコードベースを監査してきたが、未だにアプリケーションの根幹で、この「古くて新しい」脆弱性に足元をすくわれるエンジニアが後を絶たない。
javascript:疑似プロトコルを用いたXSSは、もはや古典的な攻撃手法だ。しかし、ブラウザがURLとして受け取った文字列をどう解釈し、どこで実行コンテキストを切り替えるのか――その低レイヤの仕様を理解せずに「とりあえずサニタイズした」気になっているのなら、あなたのアプリケーションは既に侵食されている可能性がある。
1. なぜブラウザは「コード」を「リンク」と誤認するのか
ブラウザのパーサーにとって、hrefやsrc属性は「次に遷移すべきリソースの場所」を示すポインタだ。本来、http://やhttps://といったプロトコルは、ネットワークスタックを通じて外部サーバーへリクエストを投げるための指示書である。
しかし、javascript:スキームが注入された瞬間、ブラウザのエンジン(ChromiumのBlinkやWebKit)は、「ネットワークリクエストを中断し、この後の文字列をJavaScript実行エンジン(V8など)に直接引き渡せ」という命令として解釈する。
ここで重要なのは、「攻撃者はコンテキストを遷移させる必要がない」という点だ。onclick属性のような明示的なイベントハンドラではなく、アンカータグのhrefに忍び込むことで、静的解析ツール(SAST)の追跡をすり抜けるケースが多発している。特に、動的に生成されるリンクや、ユーザー入力がURL生成ロジックに深く組み込まれている場合、防御側はどこまでが「データ」でどこからが「命令」かの境界を見失う。
2. 「ホワイトリスト」という名の防壁を設計する
不完全なブラックリスト(javascript:という文字列を置換・削除する手法)は、攻撃者に笑われるだけだ。jAvAsCrIpT:といった大文字小文字の揺らぎ、あるいはjava script:のようにHTMLエンティティを挟み込んだ難読化で、即座に回避される。
防御の鉄則は「プロトコルホワイトリスト」の適用だ。入力されたURLを解析し、許可されたプロトコル以外を徹底的に排除する。
/
- 安全なURL構築のためのプロトコル検証関数
- 許可されたスキームのみを通し、それ以外はnullまたはエラーを返す
/
function getSafeUrl(url: string): string | null {
try {
const parsed = new URL(url);
// 許可リスト:http, https, mailtoのみを許可する
const allowedProtocols = [‘http:’, ‘https:’, ‘mailto:’];
if (allowedProtocols.includes(parsed.protocol)) {
return parsed.toString();
}
} catch (e) {
// URLの形式として不正な場合は弾く(相対パスの扱いには注意が必要)
return null;
}
return null;
}
3. 深層防御:CSPによる「最後の砦」
アプリケーションロジックでの防御は必須だが、常に「実装漏れ」のリスクがある。ここでアーキテクトが頼るべきは、ブラウザのセキュリティポリシー(CSP)だ。
Content-Security-Policy: default-src 'self'; script-src 'self';
この設定を適用するだけで、インラインスクリプトやjavascript:スキームの実行はブラウザによって強制的にブロックされる。ただし、現代のWebアプリケーションでは、SPAの動的なスクリプト挿入がCSPの運用を難しくしている。ここで検討すべきは、strict-dynamicを用いたCSPの進化形だ。
4. 生成AI時代の新たな脅威:ガードレイルとコンテキスト汚染
今、我々が直面しているのは、単なる文字列注入ではない。LLM(大規模言語モデル)を組み込んだアプリケーションにおいて、AIが生成したURLが意図せずjavascript:を含んでしまう、あるいはAIがユーザーの入力から悪意あるURLを生成してしまうという「プロンプトインジェクション」の文脈だ。
AIの出力をそのままDOMに反映させることは、現代のXSSにおいて最も危険な行為と言える。AIが生成するコンテンツに対しても、必ず「出力フィルタリング層(ガードレイル)」を設け、今回解説したようなプロトコル検証を通すパイプラインを設計しなければならない。
結論:技術の枯渇を許すな
「古い脆弱性だから対策済み」という慢心は、セキュリティの世界では自殺行為に等しい。ブラウザの仕様は年々進化し、新たなHTML5の機能やAPIが増えるごとに、攻撃者が潜り込める隙間も増殖している。
javascript:スキームへの対策は、単なるコード修正ではない。「入ってくるすべてのデータは、実行可能な命令になり得る」という、ゼロトラストの精神を開発プロセス全体に徹底させるための、最初の一歩なのだ。
コードを書くとき、自問してほしい。「このURLは、ブラウザのエンジンを騙す可能性をゼロにできているか?」と。その問いこそが、我々エンジニアが守るべき最後の防壁である。
コメント