コンテンツセキュリティポリシー(CSP)ディレクティブまとめ

[Qiita 10周年記念イベント - 10年後のために今勉強しておきたい技術](https://qiita.com/official-events/12fc7bacec894d33a981)に参加しました！

# はじめに
久しぶりのQiita投稿です。最近、Webサイトのセキュリティ、特にコンテンツセキュリティポリシー(CSP)に触れる機会がありました。今まで理解が曖昧だった、フェッチディレクティブのフォールバックルールや設定可能な値などをはっきりさせることができたので、調べたことを記事としてまとめることにしました。内容は、基本的にReactなどのWebアプリケーション制作時の経験に基づいています。

# 0. Webアプリケーション
![csp-0.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/89544/cb811085-342c-3b93-97ed-e6c80eeb3107.png)

Webアプリケーションは通常、複数のアプリケーションから成り立っています。それらのアプリケーションは各々サーバー上に存在し、ブラウザーがインターネットを経由してやり取りします。ブラウザーは、現在アクセス中のWebアプリケーション自身(オリジン)と異なるサーバーにアクセスする際にHTTPリクエストを送信し、HTTPレスポンスを受け取ります。それらのリクエストやレスポンスはボディーとよばれる中身と、ヘッダーとよばれるメタデータで構成されています。

# 1. CSPとは
`Content-Secuirty-Policy`(CSP)とはHTTPレスポンスヘッダーの一つで、セキュリティ強化のために設定することが推奨されています。ブラウザーは、Webアプリケーションを構成している画像やフォント、JavaScriptのスクリプトなどのコンテンツを取得する際に通信を行います。CSPはブラウザーに対して、信用できるコンテンツの出どころを明示するためのセキュリティレイヤーです。目的は、クロスサイトスクリプティング(XSS)やクリックジャッキングなどを防止し、Webアプリケーションとそのユーザーを守ることです。

HTTPレスポンスヘッダーに設定したCSPは、ブラウザーがWebアプリケーション内の特定のDOMの特定の属性をチェックするために使用されます。ポリシーに違反する予期しないソース(URL)が見つかった場合、ブラウザーがそのコンテンツの表示をブロックしてくれます。また`Content-Security-Policy-Report-Only`ヘッダーを設定することで、コンテンツをブロックすることなくポリシーを検証することができます。

ちなみにCSPと目的が似ているヘッダーに、オリジン間リソース共有(CORS)があります。どちらもブラウザーがHTTPレスポンスヘッダーをみて判断する点では同じですが、CSPはコンテンツを取得する側が読み込むコンテンツを制限するために設定するのに対し、CORSはコンテンツを提供する側がアクセス権を与える先を制限するために設定するものです。

# 2. ディレクティブ一覧
![csp-1.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/89544/bf2b2752-c7ed-c422-df7e-362bc1acfd65.png)

Content-Security-Policyのディレクティブは、大きく5種類に分けられます。重要なものからそれぞれフェッチディレクティブ、文書ディレクティブ、ナビゲーションディレクティブ、報告ディレクティブ、その他のディレクティブと呼ばれています。(本記事のディレクティブは、ブラウザーによってはまだサポートされていないものもあります。)

### 2-1. フェッチディレクティブ
![csp-2.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/89544/c9012529-7777-6e7b-fdea-136e6c1224f3.png)

フェッチディレクティブには、親子関係が存在します。特定のディレクティブを指定しない場合、フォールバック先のディレクティブの値が適用されるものがあります。図の右側のディレクティブの値が指定されていないまたは対応していない場合、左側のディレクティブの値が使用されます。`default-src`がすべてのフェッチディレクティブのフォールバック先です。

| ディレクティブ | 値 | 説明 |
|:--|:--|:--|
| `default-src` | ソースリスト | すべてのフェッチディレクティブのフォールバック先。 |
| `child-src` | ソースリスト | `frame-src`と`worker-src`のフォールバック先。 |
| `connect-src` | ソースリスト | APIリクエストURLの値を制限。HTTPリクエストやWebSocketなど。 |
| `script-src` | ソースリスト | JavaScriptのソースを制限。`script-src-elem`と`script-src-attr`のフォールバック先。 |
| `script-src-elem` | ソースリスト | `<script>`のsrcに指定するソースを制限。 |
| `script-src-attr` | ソースリスト | JavaScriptの`onclick`などのイベントハンドラーに指定するソースを制限。 |
| `worker-src` | ソースリスト | WorkerやSharedWorker、ServiceWorkerなどのスクリプトソースを制限。 |
| `font-src` | ソースリスト | `@font-face`で読み込む値を制限。 |
| `frame-src` | ソースリスト | `<iframe>`のsrc の値を制限。|
| `img-src` | ソースリスト | `<img>`や`<picture>`のsrcやファビコンなどの画像のソースを制限。 |
| `manifest-src` | ソースリスト | アプリケーションマニフェストファイルのソースを制限。 |
| `media-src` | ソースリスト | `<audio>`や`<video>`、`<track>`のsrcに指定する音声や動画ソースを制限。 |
| `object-src` | ソースリスト | `<object>`のdataや`<embed>`のsrcのソースを制限。古い可能性が高いため、可能であれば`'none'`を指定すること。|
| `style-src` | ソースリスト | スタイルシート(CSS)のソースを制限。`style-src-elem`と`style-src-attr`のフォールバック先。 |
| `style-src-elem` | ソースリスト | `style`内で読み込むソースや、`link`のsrcに指定するスタイルシート(CSS)のソースを制限。 |
| `style-src-attr` | ソースリスト | DOMに`style={...}`と直接適用するインラインスタイルのソースを制限。 |
| `prefetch-src` | ソースリスト | Resource Hintsの`prefetch`または`prerender`段階で読み込むソースを制限。 |

### 2-2. 文書ディレクティブ
![csp-3.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/89544/1f57f613-5f21-45c4-9a2d-5d5d9ffd931e.png)

| ディレクティブ | 値 | 説明 |
|:--|:--|:--|
| `base-uri` | ソースリスト | `<base>`のurl(document.baseURI)に指定する、相対URLの基点となる値を制限。 |
| `plugin-types` | メディア(MIME)タイプ | `<object>`や`<embed>`のtypeに許可するコンテンツ形式を制限。`image/png`など。 |
| `sandbox` | [sandboxの値](https://developer.mozilla.org/ja/docs/Web/HTML/Element/iframe#attr-sandbox) | `<iframe>`のsandboxの値を指定でき、sandboxような環境を適用する。 |

### 2-3. ナビゲーションディレクティブ
![csp-4.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/89544/5db8f4f6-ad72-cee3-cc4f-88eda4b29860.png)

| ディレクティブ | 値 | 説明 |
|:--|:--|:--|
| `form-action` | ソースリスト | `<form>`のactionで送信先として使用されるURLを制限。 |
| `frame-ancestors` | ソースリスト | `<iframe>`のsrcや`<embed>`のsrc、`<object>`のdataを使って、このアプリケーションを埋め込むことができるURLを制限。 |
| `navigate-to` | ソースリスト | `<form>`のactionや`<a>`のhref`、`window.open`などのページを遷移するURLを制限。form-action`のフォールバック先。 |

### 2-4. 報告ディレクティブ
![csp-5.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/89544/7d787fc2-e72c-93c3-0e6e-04221e7600dd.png)

| ディレクティブ | 値 | 説明 |
|:--|:--|:--|
| `report-to` | URL | 違反しているCSPが見つかった場合、指定先URIにJSONをPOSTする。未対応のブラウザーがある場合、非推奨だが`report-url`を併用する。|

### 2-5. その他のディレクティブ
![csp-6.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/89544/650e1cd8-b2f2-6d47-9e6c-052fc2be1c00.png)

| ディレクティブ | 値 | 説明 |
|:--|:--|:--|
| `upgrade-insecure-requests` | - | `<a>`のhrefや`<iframe>`のsrcのスキーマ名がhttpでも、httpsとみなしてリクエストを送信する。応急処置用。 |
| `require-sri-for` | トークン | `<link>`や`<script>`のintegrityのハッシュ値を制限できる。改ざんされたCDNのソースのダウンロードを防ぐ。 |
| `require-trusted-types-for` | 'script'など | `element.innerHTML`などによる、DOM-based XSSを防ぐ。 |
| `trusted-types` | カスタムタイプ | `TrustedTypes.createPolicy`で作成したタイプを制限。 |

# 3. ソースリスト型のディレクティブに指定可能な値一覧
![csp-3.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/89544/c58b10b3-e021-e63f-e876-baca351245af.png)

値を必要とするディレクティブのほとんどはソースリスト型なため、これらの値を指定することができます。主にキーワードやURL、スキーマを組み合わせてポリシーを作成します。

### 3-1. キーワード
`'self'`を代表する文字列系の値。シングルクォーテーションで囲う必要があります。`'unsafe-inline'`や`'unsafe-eval'`を使用する場合もあります。

* 'self'
* 'none'
* 'unsafe-inline'
* 'unsafe-eval'
* 'unsafe-hashed'
* 'unsafe-allow-redirects'
* 'strict-dynamic'
* 'report-sample'

### 3-2. ホスト名やURL
スキーマ名やホスト名、ポート番号、パスを組み合わせた値で、`*`も使用可能です。

* example.com
* *.example.com
* https://example.com
* https://example.com:1/file.js

### 3-3. スキーマ名
例えばbase64でエンコードされた画像が使用されている場合、`data:`が必要です。

* https:
* data:
* blob:
* filesystem:

上記の他にも、Nonceやトークンもサポートされています。

# 4. CSPの管理方法
CSPなどのHTTPヘッダーは、サーバーで設定する方法が主流です。サードパーティのサービスなども利用していると大量のポリシーを設定する必要があるため、ディレクティブの値はkey-valueペアで定義し、使うときに関数で文字列化すると管理しやすいです。もっと良い方法があればぜひ教えて下さい。

```ts
// 定数の定義
const hostname = '*.example.com';
const self = "'self'";
const https = 'https:';
const data = 'data:';
const blob = 'blob:';
const google = 'https://www.google.com';

// CSPの値を配列で管理
const directiveValueMap = {
  'default-src': [https, self, blob],
  'img-src': [https, self, data],
  'script-src': [https, self, self],
  'style-src': [https, self],
  'object-src': [https, self],
  'frame-src': [https, self, hostname, google, blob],
};
```

```ts
// オブジェクトで定義したCSPの値を文字列化する関数
function toStringCSP(valueMap) {
  return Object.entries(valueMap)
    .map(([directive, values]) => `${directive} ${values.join(' ')};`)
    .join(' ');
}
```

```ts
// 使い方の例 - Node.js Expressの場合
  ...
  res.setHeader(
    'Content-Security-Policy',
    toStringCSP(directiveValueMap),
  );
  ...
```

GoogleやAdobe、Paypal、Stripeなどをはじめとしたサードパーティサービスを使用している場合、下記のサイトが便利です。最低限設定しなければならないCSPがまとめられています。

* [RapidSec](https://rapidsec.com/csp-packages)

# おわりに
CSPの仕組みを学び、フェッチディレクティブとナビゲーションディレクティブを一通り理解すると、追加対応やエラーに問題なく対処できるようになりました！`Content-Security-Policy-Report-Only`を併用して、なるべく厳しいポリシーを設定したいですね。`default-src *;`は良くないという理由が、この記事を通して伝われば嬉しいです。

ちなみにURLからWebサイトをスキャンし、安全性の評価および改善点のアドバイスをしてくれるサービスもあります。CSPが評価対象に含まれています。

* [Mozilla Observatory](https://observatory.mozilla.org/)
* [Analyse your HTTP response headers](https://securityheaders.com/)

# 参考URL
* [Content Security Policy Level 3 | W3C](https://www.w3.org/TR/CSP3/)
* [Content-Security-Policy | MDN](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Content-Security-Policy#fetch_directives)
* [Content Security Policy | 俺のリファレンス](https://www.gowest1.com/csp/)
* [CSP Cheat Sheet | Scott Helme](https://scotthelme.co.uk/csp-cheat-sheet/)
* [Content Security Policy Cheat Sheet | OWASP Cheat Sheet Series](https://cheatsheetseries.owasp.org/cheatsheets/Content_Security_Policy_Cheat_Sheet.html)
* [フロントエンドチェックリスト（日本語訳） | Qiita](https://qiita.com/miya0001/items/8fff46c201bf9eaeba4a#%E3%82%BB%E3%82%AD%E3%83%A5%E3%83%AA%E3%83%86%E3%82%A3)
* [Content Security Policy (CSP) Quick Reference Guide](https://content-security-policy.com/)
* [Contents Security Policy（CSP）のお勉強 | Qiita](https://qiita.com/o_Ozzzzk/items/c505b7dd20707eb4ee08)
* [スクリプトインジェクション入門 | Qiita](https://qiita.com/tomochan154/items/a93c56536c78d1faff0f#%E3%81%AF%E3%81%98%E3%82%81%E3%81%AB)
* [なんとなく CORS がわかる...はもう終わりにする。 | Qiita](https://qiita.com/att55/items/2154a8aad8bf1409db2b)
* [How To Secure Node.js Applications with a Content Security Policy | DigitalOcean](https://www.digitalocean.com/community/tutorials/how-to-secure-node-js-applications-with-a-content-security-policy)