目次

Netlifyのレスポンスヘッダーをカスタマイズする


Netlifyで作成したサイトのレスポンスヘッダーをカスタマイズして、セキュリティ設定を整えた

1.はじめに

連日のように、Google Search Consoleからスタイルが崩れている、MFIでないといった指摘をもらい、修正。 正直なところ、予想していたよりも修正作業が楽。これもひとえにMarkdownで修正作業が完結するからかもしれない。

修正がひと段落したので、サイトの設定を色々といじってみようと思ったところ、Netlifyの公式ドキュメントでこのようなものを見つけた。

Custom headers

Define custom headers sent in response to site requests using a _headers file or a netlify.toml file.

つまり、header情報をカスタマイズできる模様。備忘録を兼ねて何がどうなるかをまとめる。

2. 設定方法

公式ドキュメントによると2種類の方法が用意されている。

  • _headersというテキストファイルをpublich配下に置く
  • netlify.toml配下に[[header]]区を作成する。

今回はnetlify.toml方式を利用する。

2.1 設定内容

最終的に、こちらの内容を参考することとした。

GitHub - sudobinbash/sudobinbash-site

Contribute to sudobinbash/sudobinbash-site development by creating an account on GitHub.

最終的な設定値は以下となった。

2021/09/01 追記
  • X-Content-Type-Options = "nosniff" を追加
1
2
3
4
5
6
7
8
9
[[headers]]
  for = "/*"
  [headers.values]
    X-Content-Type-Options = "nosniff"
    X-Frame-Options = "DENY"
    X-XSS-Protection = "1; mode=block"
    Content-Security-Policy = "form-action https:"
    Referrer-Policy = "strict-origin-when-cross-origin"
    Feature-Policy = "geolocation 'none'; midi 'none'; sync-xhr 'none'; microphone 'none'; camera 'none'; magnetometer 'none'; gyroscope 'none';fullscreen 'none'; payment 'none'"
2021/09/01 追記

参考にした情報が古く、2021/09/01 時点でFeature-PolicyはPermissions-Policyに改名していた。

1
2
3
4
5
6
7
8
9
[[headers]]
  for = "/*"
  [headers.values]
    X-Content-Type-Options = "nosniff"
    X-Frame-Options = "DENY"
    X-XSS-Protection = "1; mode=block"
    Content-Security-Policy = "form-action https:"
    Referrer-Policy = "strict-origin-when-cross-origin"
    Permissions-Policy = "geolocation=(),midi=(),sync-xhr=(),microphone=(),camera=(),magnetometer=(),gyroscope=(),fullscreen=(),payment=(),"

が正解となる

2.2 [[headers]]について

for – 対象となるパスを指定する。今回は /*のため、公開ディレクトリ全てが対象となる。

2.3 [headers.values]について

レスポンスヘッダーに設定される値。

2.3.1 X-Frame-Options について

X-Frame-Optionsとは、そのページが外部からiframe等で埋め込みを許容するかどうかを設定する

解説については、ヌーラボのこのページの解説がわかりやすかった

以下のように、Twitterの埋め込みは色々なサイトであるが、埋め込みツイートからフォローはできない。必ずTwitterのページ(アプリ)上で行う必要がある。これはフォローボタンに被さるように埋め込まれるブラクラ防止のためとなる。なるほど。

このサイト狙われることはないと思うが、DENY設定を入れておく。

2.3.2 X-XSS-Protection について

ブラウザにXSSフィルター機能という、ユーザーのブラウザで悪意のあるコードを実行しようとする可能性のあるクロスサイトスクリプティング(XSS)攻撃のように見えるパターンについて、Webサイトのソースコードをスキャンする機能。

また、その有効化/無効化をブラウザに設定するためのヘッダー。

しかしながら、2021/08/31 現在、ほとんどのブラウザで利用されなくなっている。サポートされているのはIE8 (笑)と、Safari (Mac , iOS)。

MDN Web Docsにも記載されているが、

引用
  • Chrome は XSS Auditor を削除しました
  • Firefox は対応しておらず、 X-XSS-Protection を今後も実装しません
  • Edge は XSS filter を廃止しました

とのことで、代わりに、Content-Security-Policy を使用し unsafe-inline を許可しないことをお勧めとのこと。

なお、設定値としては

X-XSS-Protection

1; mode=block

XSS フィルタリングを有効化します。攻撃を検知すると、ページをサニタイジングするよりも、ページのレンダリングを停止します。

設定を入れておかない理由にはならないので、1; mode=blockを設定することにする。

2.3.3  Content-Security-Policy について

前述のX-XSS-Protectionに代わる設定。metaタグでも指定可能。 ホワイトリスト形式で設定した事柄について許可を行う、とのこと。

例えばContent-Security-Policy = "form-action https:"だと、formの送信先をhttpsにかぎっている(ドメインは関係ない)設定。

今のところformを利用する想定はないこと、またホワイトリスト形式であることから大枠で設定を追加したらCSSが取得できなくなるなどえらい目にあった。今後調整したい。

2.3.4 Referrer-Policy について

通常、ブラウザが各ページにアクセスする場合、元のページの情報をReffererとしてヘッダに格納している。 この格納ルールについて指定するヘッダー。

strict-origin-when-cross-originを設定すると、

https://developer.mozilla.org/ja/docs/Web/HTTP/Headers/Referrer-Policy より
同じオリジン間でリクエストを行う際はオリジン、パス、クエリ文字列を送信し、オリジン間リクエストを行う際にプロトコルのセキュリティレベルが同じ場合 (HTTPS→HTTPS) はオリジンを送信し、安全性の劣る送信先 (HTTPS→HTTP) にはヘッダーを送信しません。

となる。引用元:Refferer-Policy

2.3.5 Strict-Transport-Security について

これもMDNの解説がわかりやすかった。

https://developer.mozilla.org/ja/docs/Web/HTTP/Headers/Referrer-Policy より

あなたが、空港で無料の Wi-Fi アクセスポイントにログインしてウェブの利用を開始し、オンラインバンキングサービスで残高の確認や取引を行ったとします。しかし不運にも、あなたが使用したアクセスポイントはハッカーのノートパソコンであり、そのハッカーはあなたの HTTP リクエストを傍受して、本物の銀行のサイトではなく偽のサイトへリダイレクトしたとします。こうなると、あなたの個人情報はハッカーにさらされてしまいます。

Strict Transport Security はこの問題を解決します。いったん銀行のウェブサイトへ HTTPS でアクセスすれば、そして銀行のウェブサイトが Strict Transport Security を利用していれば、ブラウザーは自動的に HTTPS のみを用いるよう理解して、ハッカーによるこの種の中間者攻撃の実行を防ぎます。

要はhttpでアクセスした際、一度でもhttpsで本物のサイトに接続していれば(リンクなどからページ遷移した際に)httpで接続を試みたとしても、一定時間はhttpsで(クライアントであるブラウザ側が)自動でリダイレクトをしてくれる、ということ。

これはNetlifyがデフォルトで設定(31536000=365日)していた。

2.3.6 Feature-Policy について

2021/09/01 追記 Permissions-Policyへの改名について
参考にした情報が古く、2021/09/01 時点でFeature-PolicyはPermissions-Policyに改名している。 設定値は、そのままに、Permissions-Policyとして設定する必要がある。

仮に iframeでformを利用した際、その中に埋め込めるファイル(拡張子)をどれだけ許可するか。

今のところ利用する想定はないのでざっくり許可しないでおく。

ちなみに、

  • vibrate –非推奨。端末のバイブレーションAPIをキックする
  • geolocation – Geolocation インターフェイスを使用することを許可するかどうか
  • midi - midiを利用できるか
  • sync-xhr – XMLHTTPリクエストでの同期を許可するか
  • microphone – マイクの利用を許可するか
  • camera – カメラを許可するか
  • magnetometer / gyrocscope – 端末の方向の収集を許可するか
  • fullscreen – フルスクリーン化を許可するか
  • payment – payment request API を許可するか

らしい。おおよそ名前の通り何をしようとするAPIかはわかるので、とりあえず不許可

notifications, push, speaker はMDN Web Docsのリファレンスに記載がなかったので省くことにした。vibrateは非推奨(どのブラウザも対応していない)ため、同様に省いた。あと参考にしたサイトには2つ設定されていた

2.3.7 X-Content-Type-Options について

色々と調べていてこちらのブログで知ったヘッダー

MIME スニッフィングを無効化して、Content-Type で指定したタイプを強制的に使用させる。

https://developer.mozilla.org/ja/docs/Web/HTTP/Headers/X-Content-Type-Options より
このヘッダーは、コンテンツのスニッフィングにより、実行不可能な MIME タイプを実行可能な MIME タイプに変換してしまうという事故をウェブマスターが抑止するための方法として、マイクロソフトが IE 8 で導入したものです。それ以来、他のブラウザーは MIME スニッフィングのアルゴリズムがそれほど積極的ではなくても、このヘッダーを導入してきました。

この動作を抑止するにはnosniffをパラメータにレスポンスヘッダを付ける。これは IE8以降から有効。

確かにIEでWebページを開くと変な挙動するサイトが昔からあったよなぁ・・・

3. 設定前のレスポンスヘッダー

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
age: 9
cache-control: public, max-age=0, must-revalidate
content-encoding: br
content-length: 5857
content-type: text/html; charset=UTF-8
date: Tue, 31 Aug 2021 13:00:09 GMT
etag: "c911776adc4fc152f3fe48076e76ae91-ssl-df"
server: Netlify
strict-transport-security: max-age=31536000
vary: Accept-Encoding
x-nf-request-id: 多分NetlifyのDebug用のRequestID

4. 設定後のレスポンスヘッダー

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
age: 0
cache-control: public, max-age=0, must-revalidate
content-encoding: br
content-security-policy: form-action https:
content-type: text/html; charset=UTF-8
date: Tue, 31 Aug 2021 13:59:36 GMT
etag: "19ae075ddb7e6975ffaa65517196b66c-ssl-df"
feature-policy: geolocation 'none'; midi 'none'; sync-xhr 'none'; microphone 'none'; camera 'none'; magnetometer 'none'; gyroscope 'none';fullscreen 'none'; payment 'none'
referrer-policy: strict-origin-when-cross-origin
server: Netlify
strict-transport-security: max-age=31536000
vary: Accept-Encoding
x-frame-options: DENY
x-nf-request-id: 多分NetlifyのDebug用のRequestID
x-xss-protection: 1; mode=block
x-content-type-options: nosniff