WEBページをセキュアにするHTTPヘッダーを設定しよう

セキュリティに関するHTTPヘッダーとPHPにおけるその設定方法のまとめです。

## HSTS(**H**TTP **S**trict **T**ransport **S**ecurity)
次から接続するときはHTTPSで接続してくださいとお願いする仕組みです。HTTPSでの接続を強制したいときに用います。

```php
header('Strict-Transport-Security: max-age=31536000; includeSubDomains; preload');
```

### オプション
- `max-age` どのくらいの期間（単位は秒）HTTPSでの接続をお願いするか
- `includeSubDomains` このオプションをかくとサブドメインにも適用される
- `preload` プレロード設定（後述）を行ったら追記する

### プレロードについて
HTSTはHTTPSでの接続を強制したいときに用いるのですが、それでもなお**初回サイト訪問時**のアクセスはHTTPになる可能性があります。初回の接続もHTTPSに強制したい。そこで用いられるのがこのプレロードで、ブラウザが最初から「このサイトはHTTPSで繋いでくださいね」とユーザーに働きかけてくれます。登録は簡単で[こちら](https://hstspreload.org/)にドメインを入力するだけで、ユーザーがブラウザをアップデートした時点から有効になります（アップデート時にユーザーにプレロードリストがインストールされるため）。

## HPKP(HTTP公開鍵ピンニング：**H**TTP **P**ublic **K**ey **P**inning)
2回目以降の接続時に、以前の接続の時と同じサーバ証明書であるかを確認する仕組みです。公開鍵をピン留めすることで証明書の不正な変更を検知することができます。なお現在Chromeの最新版ではHPKPの対応は打ち切られています。

```php
header('Public-Key-Pins: pin-sha256="<サーバ証明書>"; pin-sha256="<中間認証局以上の証明書>"; max-age=5184000; includeSubDomains; report-uri="<report_uri>"');
```

### オプション
`Public-Key-Pins`ヘッダーと`Public-Key-Pins-Report-Only`ヘッダーがあり、前者は不正な証明書を検知した時点でアクセスを遮断し、後者は検知したら報告のみ行い遮断は行わない。

- `pin-sha256` Base64でエンコードされた証明書の公開鍵情報
- `max-age` ピン留めする期間（単位は秒）
- `includeSubDomains` このオプションをかくとサブドメインにも適用される
- `report-uri` 不正を報告する送信先

### `max-age`を設定するときの注意点
一度ピン留めされた公開鍵の情報は`max-age`の期間ユーザーに保存されるため、もし間違った公開鍵情報を登録するとその間アクセスが遮断されてしまいます。ですので、まずは`max-age`は小さい値（HPKPの特性上長い方が好ましいですが）で、あるいは`Public-Key-Pins-Report-Only`で試してみることをお勧めします。

### 公開鍵の登録について
上でも述べたように、公開鍵の情報が誤っていた場合は`max-age`で指定した期間サイトへのアクセスが遮断されます。多くのサーバ証明書の更新は一年サイクルで、この時に適切に変更を行わないとサイトにアクセスできなくなるわけです。ですから念のため、より更新サイクルの長い中間認証局以上の証明書を予備で書いておくことをお勧めします。

### AWSの無料証明書を使ってピンニングする
AWSの無料証明書発行サービスである Amazon Certificate Manager で取得した証明書を用いてHPKPを設定する方法について書きました。
[【AWS】無料のサーバ証明書を公開鍵ピンニングする](https://qiita.com/ruddy95/items/07735c1f25fba6a38fc2)

## CSP(**C**ontent **S**ecurity **P**olicy)
ページ内で読み込むリソースに制限を設ける仕組みです。読み込むリソースの出所を予めリストアップし、それ以外のロードを遮断することで、予期せぬスクリプトの実行などを防ぐことができます。

```php
header("Content-Security-Policy: default-src 'self'; script-src 'self' https://www.google-analytics.com　upgrade-insecure-requests　report-uri <report_uri>");
```

### オプション
`Content-Security-Policy`ヘッダーと`Content-Security-Policy-Report-Only`ヘッダーがあり、前者は違反したリソースを全て遮断、後者は指定したURIに違反報告のみ行い遮断は行わない。導入時はにReport-Onlyの方がいいかと思います。

- `Fetchディレクティブ` `script-src`など、リソースの種類ごとに出所を指定する。
- `report-uri` 違反報告の送信先
- `upgrade-insecure-requests` これをかくとhttpと書かれたリソースに対してもhttpsでアクセスしに行く
- `block-all-mixed-content` これをかくとhttpのリソースはそもそも読み込まない

### より詳しく
個々のページによって必要なリソースが変わってくるので、詳しくはこちらに書きました。
[WEBページをコンテントセキュリティポリシー（CSP）に対応させたかった話 - Qiita](https://qiita.com/ruddy95/items/e12498ab7c0eb56611a2)

## X-XSS-Protection
XSS攻撃を検知する仕組みです。

```php
header("X-XSS-Protection: 1; mode=block; report=<report_uri>");
```

### オプション
0でXSSフィルタリングを無効化、１で有効化します

- `mode=block` XSS攻撃を検知したらページの読み込みを止める
- `report` XSS攻撃を検知したときにその内容を送信するURI

## X-Content-Type-Options
ファイルの内容からファイルの種類を決定させるかどうか。以下のように書けばテキストで書かれた`script`タグや`style`タグが勝手に解釈されることはありません。

```php
header("X-Content-Type-Options: nosniff");
```

## X-Frame-Options
ページをiframeに読み込ませるかどうか。

```php
header("X-Frame-Options: DENY");
```

### オプション
- `DENY` 全てのサイトに対して読み込みを禁ずる
- `SAMEORIGIN` 同じオリジン内のみ読み込みを許可する
- `ALLOW-FROM <url>` 指定したurlからの読み込みを許可する

## ReportURI
上記の`report_uri`系のディレクティブに書くurlですが、いちいちレポートの受け口を用意するのは面倒かと思います。そこで[ReportURI](https://report-uri.com/)というサービスを用いると簡単にレポートを確認することができます。無料で月10,0000件まで見れるのでオススメです。

## 参考
[HTTP Strict Transport Security - Web セキュリティ | MDN](https://developer.mozilla.org/ja/docs/Web/Security/HTTP_Strict_Transport_Security)
[HTTP Public Key Pinning (HPKP) - Web セキュリティ | MDN](https://developer.mozilla.org/ja/docs/Web/Security/Public_Key_Pinning)
[Content-Security-Policy - HTTP | MDN](https://developer.mozilla.org/ja/docs/Web/HTTP/Headers/Content-Security-Policy)
[X-Frame-Options - HTTP | MDN](https://developer.mozilla.org/ja/docs/Web/HTTP/X-Frame-Options)