My tech diary

ソフトウェアエンジニアをやっています。技術的内容を中心に調べたことを書いていきます。

HTTP CSP について

Content-Security-Policy (CSP)

CSP は Cross Site Scripting (XSS) や data injection 攻撃を防ぐための HTTP の仕様です。

CSP を有効にするには、以下のいずれかを実施します。

  • HTTP header で Content-Security-Policy を返す

    http Content-Security-Policy: default-src 'self'; img-src *; media-src media1.com media2.com; script-src userscripts.example.com

  • meta タグで Content-Security-Policy を埋め込む

    html <meta http-equiv="Content-Security-Policy" content="default-src 'self'; img-src https://*; child-src 'none';">

CSP directives

CSP の policy は、上記のように directive に続けて、空白区切りで値リストの記述を ; 区切りで複数記載していきます。 directive は Content の種類によって、以下の種類があります。

  • default-src
  • child-src
  • connect-src
  • font-src
  • frame-src
  • img-src
  • manifest-src
  • media-src
  • object-src
  • prefetch-src
  • script-src
  • style-src
  • worker-src

default-src は特殊な directive で、対象のコンテンツの directive が定義されていなかった場合に、この値が使われます。

CSP による制約

CSP が有効な場合、 HTML 本体以外のリソースをロード・実行しようとしたときに制約が生じます。

例えば、 img-src https://*; のように設定すれば、 HTTPS 以外の画像の読み込みを制限することができます。

script-src unsafe-inline

script-src'unsafe-inline' を指定することで、 inline scripts と inline event handlers の実行を許可できますが、モダンブラウザではこのオプションは推奨されません。

代わりに nonce もしくは hash を用います。

script-src nonce

nonce は inline scripts と inline event handlers に対して、 whitelist 形式で CSP の許可を与える方法です。

まず実行を許可したい inline script の nonce 属性に、 BASE64 エンコードしたテキストを埋め込みます。

<script nonce="2726c7f26c">
  var inline = 1;
</script>

上記スクリプトの実行を許可するには、 CSP の script-src の policy に、以下のように 'nonce-BASE64_VALUE' の値を埋め込みます。

Content-Security-Policy: script-src 'nonce-2726c7f26c'

nonce はランダムな値を使えば OK ですから、生成は簡単です。たとえば、 PHP だと以下のように生成できます。

> echo base64_encode(random_bytes(20));
fUz9xyElzZh7x4XwbKkt2OdAhPs=

script-src hash

inline scripts の実行を許可する方法は、他にもあります。 スクリプト本体 (空白・改行を含む / <script>タグは除く) 全体のハッシュ値 (sha256 or sha384 or sha512) を求めて、それに対して whitelist 形式で許可するやり方です。

例えば、以下の inline script の実行を許可したいとします。

<script>var inline = 1;</script>

まず、このスクリプト本体のハッシュ値 (BASE64 エンコードしたもの) を求めます。今回は Ruby でやってみます。

> require 'digest'
> Digest::SHA256.base64digest 'var inline = 1;'
=> "B2yPHKaXnvFWtRChIbabYmUBFZdVfKKXHbWtWidDVF8="

そして、 CSP の script-src の policy で以下のように、ハッシュアルゴリズム名に続けて、生成したハッシュを埋め込めば OK です。

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

References