WEBページをコンテントセキュリティポリシー（CSP）対応させる

自社のWEBサービスをCSP対応させたかった話です。

## そもそもCSPって何？

WEBページの潜在的なXSSなどに対する脆弱性を緩和するための仕組みです。**CSPに対応させる**というのはCSPの仕組みをもつブラウザの機能を利用するために、レスポンスヘッダーを追記することを指します。具体的にはヘッダーにWEBページが必要とするリソースを列挙する作業になります。そうするとブラウザはそれらのリソース以外を読み込みません。

簡単に言えば、**うちのページはここに挙げた場所からしかリソースを持って来ないつもりだし、もし違うところから持って来てたらそいつら全部遮断してしまって構わんよ**とブラウザに宣言することです。

こうすればXSSなどの意図しないスクリプトの実行を防げるわけですね。同時に自分で自分の首を絞める行為でもあります。

## PHPで実装してみる

PHPプログラムの場合、CSPの書き方は以下の三つがあります。

1. PHPのheader関数で送信する
2. WEBサーバー（apache, nginx）の設定ファイルに書く
3. htmlヘッダーのmetaタグに書く（後述のreport_uriが設定できない）

1で実装する場合、以下の`content-security-policy: ...`の部分をheader関数に入れて

```php
header("Content-Security-Policy: ...");
```

とやるだけです。

## CSPヘッダーの構造

```
content-security-policy: default-src <source>; script-src <source> <source>; ...
```

上記のように`content-security-policy`ヘッダーの後にポリシーディレクティブをセミコロン区切りで書き連ねます。これにより、ポリシーディレクティブに記述のないリソースはシャットアウトされてしまいますが、替わりに`content-security-policy-report-only`ヘッダーを使えば、そのエラー内容を特定のURLにPOSTするだけで、読み込みは行ってくれます。いきなり適用するのではなく、実験的にCSPを導入するのに便利な機能です。

### Fetchディレクティブ
上の例でいう`default-src`とか`script-src`の部分です。リソースの種類ごとにソースを指定します。例えば

```
default-src 'self'; script-src 'self' https://www.google-analytics.com
```

と書けば、同じURLであるか`https://www.google-analytics.com`にあるjavascriptはロードし、かつそれ以外のリソース（スタイルシートやフォント、画像など）は同じURLにあるもののみロードする、という意味になります。このように`default-src`に書いたリソースは**全てのディレクティブに適用されるのではなく、特に記述のないディレクティブに対し有効である**ことに注意が必要です。ちなみによくある以下のソースを読み込むにはCSPはここまで書かないといけません（全部`default-src`に入れてもいいですが）。

- Google Analytics
- Google Adsense
- Google Tag Manager
- Facebook のウィジェット
- FontAwesome

```
content-security-policy-report-only:
default-src 'self';
script-src 'self' 'unsafe-inline' 'unsafe-eval' https://connect.facebook.net https://www.google-analytics.com https://www.googletagmanager.com https://stats.g.doubleclick.net blob: data:;
style-src 'self' 'unsafe-inline' https://fonts.googleapis.com https://use.fontawesome.com https://netdna.bootstrapcdn.com use.fontawesome.com;
img-src * data:;
font-src 'self' https://fonts.gstatic.com https://use.fontawesome.com https://netdna.bootstrapcdn.com data:;
frame-src 'self' https://www.facebook.com https://staticxx.facebook.com https://www.googletagmanager.com;
connect-src 'self' https://www.google-analytics.com https://stats.g.doubleclick.net https://api.ipify.org;
report-uri <report先URL>
```

見やすくするために改行しています。report-onlyで実装して見てエラーが出たら修正の繰り返しです。slackにエラー通知を飛ばしたのですが、通知欄が凄まじいことになるのでオススメしません。

以下、このソースに関する説明（弁明）になります。

### `report-uri`について
`content-security-policy:`の代わりに`content-security-policy-report-only:`ヘッダーを使うとき、エラーレポートを飛ばす先を指定します。以下のような形式のデータが飛んできます。

```
{
	"csp-report":{
		"blocked-uri":<ブロックされたソース>,
		"document-uri":<送信元URL>,
		"original-policy":<設定したCSP>,
		"referrer":<リファラー>,
		"violated-directive":<ブロックの原因となったFetchディレクティブ>
	}
}
```

これをPHPサーバーで受けたかったら

```php
$payload = file_get_contents('php://input');
```

で変数に代入できるので、あとは煮るなり焼くなりslackAPIで通知飛ばすなり好きにできます。

### ワイルドカード`*`について
`img-src * data:;`という記述、不思議ですがこれはワイルドカードが`blob:`、`data:`、`filesystem:`といったスキーマのURIを含まないからです。

### dataスキーマについて
上の例である`data:`の部分です。これは`data:`形式のURIを受け付ける記述で、Mozillaのドキュメントでは

> コンテンツのソースとして `data:` の URI を使うことができるようにします。これは安全ではありません。攻撃者は任意の data: URI を挿入することもできます。使用は控え、スクリプトには絶対に使用しないでください。

とありますが、これを入れないと大概うまく動きません。仕方ないよね。

### `script-src`に`'unsafe-inline'`入れていいのか問題
ダメです。`'unsafe-inline'`と書けばスクリプトなどのインライン記述を許すわけですが、これはインラインスクリプトによる攻撃などを防ぐというCSPの目的の一つを崩壊させています。なんのためのCSPやねんと。でも実際の運用ではなかなか厳しんですよと。じゃあ入れざるを得ないんよと。

### 【追加】Mixed Content 警告の防止
HTTPS のページに HTTP で読み込むリソースがあったら出る、あの警告です。これの防止策は二つあって

 - httpと書かれていてもhttpsでアクセスしに行く `upgrade-insecure-requests`
 - httpのリソースはそもそも読み込まない `block-all-mixed-content`

これらのうちどちらかを CSP ヘッダーに記述します。前者の方が良さげ。

## 感想
以上、散々な結果ですがやはり実際の運用を考えると、完全にセキュアな書き方は難しいです。今後CSP対応が必須になって来たときに、もう少しやりやすい感じになって欲しいですね。

あとなぜか頻繁に`webviewprogressproxy://`をブロックした旨の通知が来るので気になっているのですが、このスキーマはwebviewで用いられるようで、基本的にブロックしてしまっていいそうです。ソースは[Dropboxの記事](https://blogs.dropbox.com/tech/2015/09/on-csp-reporting-and-filtering/)。

## 参考
[Content-Security-Policy - HTTP | MDN](https://developer.mozilla.org/ja/docs/Web/HTTP/Headers/Content-Security-Policy)
[[CSP] On Reporting and Filtering | Dropbox Tech Blog](https://blogs.dropbox.com/tech/2015/09/on-csp-reporting-and-filtering/)