CSPとは
Content Security Policy の略。
CSPを用いることにより、ブラウザが読み込み可能なリソース(JavaScript, CSS, Imgなど)をホワイトリストで制限することができます。
XSSといった脆弱性に対する攻撃を抑止することができます。
ホワイトリスト:許可リストのこと。逆にブラックリストとは禁止リストのこと。
XSS:Webアプリケーションにスクリプトを保存させ、該当するページを閲覧するたびに不正なスクリプトが実行されること。
CSP が必要になったきっかけ
別ドメインの Webサイトに iframe として埋め込む際に、CSPの frame-ancestors というディレクティブで、埋め込み先のドメインを指定する必要がありました。
frame-ancestorsを指定しリリースしたもののコンソールに以下のエラーが返されていました。
Refused to load the script 'https://www.googletagmanager.com/gtm.js?id=GTM-XXXXX...
because it violates the following Content Security Policy directive:
Google タグマネージャーのドキュメントにあるとおり、CSPを利用すると、GTMのスクリプトに対してもホワイトリストに加える必要があるようでした。
今回は、GTMのCSP対応した時に行った実装について困った部分も含め、説明していきたいと思います。
CSPの利用&実装方法
CSPは HTTPのレスポンスヘッダーに加えることで利用可能です。
HTTP レスポンスヘッダーを以下のように設定します。
Content-Security-Policy: <policy>
の部分に、制限するリソースの種類 (ディレクティブ) と、読み込み元 (ソース)のペアで指定します。
例えば、https://test.example.com/posts/1 にアクセスした時のレスポンスヘッダーのCSPの指定を以下のようにすると…
Content-Security-Policy: default-src 'self';
デフォルトのリソースの読み込み先 (default-src)を同じドメイン(test.example.com) & 同一ポートが配布しているものに制限することができます。(同オリジンとも言う。オリジンについてはこちらの説明 をどうぞ)
この時、もしアクセスしたページの画像が外部ドメイン(Ex: https://test.hello.com/img.jpg )から読み込んだ場合、CSPに違反するため読み込まれません!
default-src は、ディレクティブに指定されなかったファイルの種類の読み込み元指定するものです。img-src を追加で指定した場合、画像の読み込み制限はdefault-srcではなく、img-src で設定したものが優先されます。
様々なディレクティブやソースの指定方法がありますので、詳しく知りたい方はリンクをどうぞ。
では、CSPの簡単な概要と指定方法を説明したところで、本題のGTMのCSP対応を説明しましょう。
みなさんがお使いのGTMは、以下のようなスクリプトをheadタグのどこかに仕込んでいると思います。
<!-- Google Tag Manager -->
<script>(function(w,d,s,l,i)
...
...
...
})(window,document,'script','dataLayer','GTM-XXXXXX');</script>
<!-- End Google Tag Manager -->
ドキュメントには、CSPが有効なページでこのスクリプトを実行可能にする方法2つ挙げられています。
1. nonce を使う (Google 推奨)
2. script-src: unsafe-inline を指定する。(非推奨、というかCSPのメリットを享受してない。)
まず、1 を説明する前に 2を説明しましょう。
2. script-src: unsafe-inline を含めたCSPを指定する。
script-src: 'unsafe-inline' https://www.googletagmanager.com
img-src: www.googletagmanager.com
これは、「JavaScript のソースについて、インラインのものを許可する」というものです。
そもそも冒頭でも話した通り、CSPには、攻撃者によって仕込まれたインラインスクリプトやインラインスタイルの実行を阻止できる利点があります。
unsafe-inline を指定してしまうと、この利点を全く享受することができません。最終手段として下さい。
ですが、script-src に、unsafe-inline を指定しない場合、GTM だけでなく他のインラインスクリプトまでも実行が禁じられてしまいます。
ここで、登場するのがGoogleが推奨する nonce を使った方法です。
方法を説明する前に、そもそもnonceとは何かを説明します。
nonceとは (読み方:ノンス)
nonce とは、使い捨て乱数データ、ワンタイムパスワード◆通信などでセキュリティー保護のため、そのたびに生成される補助的変数。(英辞郎)
インラインのJavascipt に nonce 属性に、ランダムな文字列を指定し、その値をCSPヘッダに指定することで、スクリプトを実行できるようにするというものです。
例えば、CSPヘッダに
Content-Security-Policy: script-src 'nonce-XXX'
と指定します。
インラインのスクリプトタグのnonce属性に指定した nonce を与えます。
<script nonce="nonce-XXX">
console.log('許可');
</script>
<script>
console.log('違反');
</script>
nonce 属性を与えられていないインラインスクリプトは実行されないのに対し、nonce属性が与えられたインラインスクリプトは実行されるようになります。
GTM の script タグに nonce 属性を付与 + nonce 対応の GTMタグ を与えればGTMも実行できるというわけです。
では次に、具体的にどのように実装するか Rails の実装例を挙げながら説明します。
CSPの実装方法 by Rails
Rails には CSPを設定するためのDSLが提供されています。
config/initializers/content_security_policy.rb を設定することでレスポンスヘッダーに自動的にCSP が設定されます。
# config/initializers/content_security_policy.rb
Rails.application.config.content_security_policy do |policy|
policy.default_src :self, :https
policy.font_src :self, :https, :data
policy.img_src :self, :https, :data
policy.object_src :none
policy.script_src :self, :https
policy.style_src :self, :https
end
# nonceを利用する場合、以下の設定をすることで nonce の自動生成を有効にできます。
# If you are using UJS then enable automatic nonce generation
Rails.application.config.content_security_policy_nonce_generator = -> request { SecureRandom.base64(16) }
# 特定のディレクティブ(sciprtとかimg)に対して、nonce属性を有効にすることができます。
# script-src: 'nonce-XXX' img-src: 'nonce-YYY' とかってできる
# Set the nonce only to specific directives
Rails.application.config.content_security_policy_nonce_directives = %w(script-src)
config/initializers/content_security_policy.rb に設定した場合、全てのコントローラでCSPが有効になり大体既存のプロジェクトは動作しなくなるでしょう。
なので、最初はコントローラやアクションを絞って適用するのが良いです。
コントローラごとに設定するには、以下のようにして下さい。
class TestController
content_security_policy only: %i[index, show] do |policy|
policy.script_src :self
end
end
only でアクションを絞れます。
最後に、Viewのスクリプトにnonce属性を付与します。
<%= csp_meta_tag %><%= javascript_include_tag 'application', nonce: true %>
scriptタグの場合は、
<script nonce="<%= content_security_policy_nonce %>">
...
</script>
hamlの場合は、
= javascript_tag nonce: true do
CSPの実装におけるデメリット&注意点
デメリット
CSP にはデメリットがあります。それはホワイトリストであるが故に、メンテナンスが大変であること、リストアップが大変なことであります。
注意点
Railsで上記の実装をし、いざchromeのデベロッパーツールでスクリプトタグを確認すると、
<script nonce>
...
</script>
のように描画されます。nonce=’xxxxxxx’ のようにnonce 属性の値が出ません。
これは、ノンスを隠蔽することにより、攻撃者がノンスデータを流出させることを防ぐためです。