Service Workerは「最強の特権」を持つ:キャッシュ汚染が招く終わりの始まり
多くのエンジニアが、Service Worker(SW)を単なる「オフライン対応のためのキャッシュ機構」だと誤解している。だが、セキュリティアーキテクトの視点から言えば、それはブラウザ内に常駐する「特権的な中間者(MITM)」に他ならない。
一度インストールされたSWは、ネットワークリクエストを完全に掌握する。もしここにXSSやキャッシュポイズニングの脆弱性が潜んでいれば、攻撃者はユーザーのセッションを永続的に乗っ取ることができる。今日は、SWを軸とした攻撃の解像度を上げ、実務でどう防衛線を敷くべきか、泥臭い現実を語ろう。
—
1. なぜ「キャッシュ汚染」が致命的なのか
Service Workerは fetch イベントをインターセプトする。この設計自体が、攻撃者にとっての「黄金のチケット」だ。
もし、CDNのキャッシュ制御ミスや、動的なJSリソース生成におけるXSSが存在する場合、攻撃者は悪意あるスクリプトをSWのキャッシュに「永続化」させる。一度キャッシュに入ってしまえば、サーバー側でパッチを当ててもユーザーのブラウザ上では汚染されたJSが動き続ける。これは従来のXSSの概念を超えた、「クライアントサイドの永続的ルートキット」だ。
攻撃のメカニズム:DOM型XSSからSWへの汚染
攻撃者がターゲットのDOM型XSSを突き、cache.put() を操作できれば、任意のペイロードをキャッシュに挿入できる。
// 攻撃者の視点:安全でないキャッシュ操作の例
self.addEventListener(‘fetch’, event => {
// 脆弱な実装:クエリパラメータをそのままキャッシュキーに使用
const url = event.request.url;
event.respondWith(
caches.match(url).then(response => {
// 攻撃者が細工したレスポンスをキャッシュへ注入可能
return response || fetch(event.request).then(res => {
const cacheCopy = res.clone();
caches.open(‘v1’).then(cache => cache.put(url, cacheCopy));
return res;
});
})
);
});
このコードは、URLの検証を怠っている。攻撃者は細工したリクエストを送ることで、本来のJSファイルを攻撃者のスクリプトで上書きし、そのユーザーの全リクエストを外部へ流出させることが可能になる。
—
2. 防衛アーキテクチャ:境界を制御せよ
SWの防御において、最も重要なのは「スコープの厳格化」と「整合性の保証」だ。
スコープの分離とパスの制限
SWのスコープは、ファイル配置場所によって自動的に決定されるが、これを過信してはいけない。Service-Worker-Allowed ヘッダーを安易に設定し、スコープをルート(/)に広げるのは自殺行為だ。
Nginx設定での対策:SWのスコープを最小化し、特定のディレクトリに限定する
location /app/sw.js {
add_header Service-Worker-Allowed /app/; # スコープを/app/配下に限定
add_header Content-Security-Policy “default-src ‘self’; script-src ‘self’;”;
}
サブリソース整合性(SRI)の活用
SWがキャッシュするリソースには、必ず integrity 属性を通したチェックを強制するべきだ。キャッシュされたデータが信頼できるものか、サーバー側のハッシュ値と照合するロジックを実装せよ。
// 防御的なキャッシュ取得ロジック
async function fetchAndValidate(request) {
const response = await fetch(request);
const data = await response.clone().text();
// サーバーから提供された期待値(例: ヘッダーやメタ情報)とハッシュを比較
if (!verifyIntegrity(data, request.headers.get(‘X-Expected-Hash’))) {
throw new Error(“キャッシュ整合性エラー: 汚染の疑い”);
}
return response;
}
—
3. 次世代の脅威:AIとプロンプトインジェクションへの備え
今、我々が警戒すべきは、生成AIが作成した「安全そうに見えるコード」に含まれる脆弱性だ。AIは「機能するコード」を生成するのは得意だが、「多層防御の文脈」を理解していない。
特に、LLMを介した動的コンテンツ生成において、その出力がSWにキャッシュされる場合、プロンプトインジェクションの結果が永続的なXSSペイロードとしてキャッシュされるという最悪のシナリオが現実味を帯びている。
ガードレイル設計の指針
1. 入力の正規化: AIからの出力をキャッシュに入れる前に、DOMPurify等で厳格にサニタイズする。
2. 実行コンテキストの分離: SWが扱うキャッシュ領域と、AIが生成するコンテンツの配信ドメインを完全に分離せよ(CORSの厳格化)。
3. 定期的なキャッシュフラッシュ: セキュリティ更新時は、全クライアントに対して強制的に caches.delete() を実行するロジックをバックエンドに持たせておくこと。
—
結論:セキュリティは「信頼の連鎖」に宿る
Service Workerを扱うということは、ブラウザというサンドボックス内に「自前のサーバー」を建てるのと同じだ。そのコードが汚染されれば、TLSによる暗号化も、HSTSも、すべて無力化される。
「クライアントは常に攻撃されている」という前提に立ち、SWのキャッシュを単なるデータ置き場ではなく、特権的なリソースとして扱うこと。それが、今のWebセキュリティにおける最低限の作法だ。
君たちが書くその数行の fetch ハンドラが、数百万人のユーザーの安全を守っている。その責任の重さを、コードの深部で常に意識してほしい。
コメント