Content Security Policy の導入

原文: Implementing Content Security Policy on February 16, 2016 by April King, Stuart Colville

Mozilla のアドオンチームは、addons.mozilla.org (AMO) で Content  Security Policy (CSP) を有効にする作業を終えました。この記事では CSP 導入時の基礎とともに、CSP を AMO へ導入した際に遭遇した問題点も紹介します。

Content Security Policy について

Content Security Policy (CSP) とは、クロスサイトスクリプティング (XSS) といったコンテンツを差し込む攻撃に関して、これらの攻撃に対する耐性を高めるためのセキュリティスタンダードです。CSP では、ユーザエージェントがコンテンツを取得する際の読込元を Web サイトの管理者が指定・制限し、先ほどの攻撃を防ぐという目的を達成します。

ポリシーはサーバからのレスポンスヘッダに指定します。そのポリシーをユーザエージェントが受け取り、ポリシーへの違反を検知して能動的にブロックします。ただし、サポートしているユーザエージェントによってその動作は異なります。

CSP が必要な理由

CSP は防御策に追加するレイヤーであり、XSS といったコンテンツを差し込む攻撃からユーザを保護するのに役立ちます。CSP は銀の弾丸でありませんが、攻撃者がコンテンツを差し込んだり、データを窃取したりするのはかなり困難となります。

Web サイトを安全に構築するのは難しいものです。Web セキュリティのベストプラクティスを広く知っていても何かを見落とすことはあり、安全だった Web サイトにうっかりセキュリティホールを導入してしまうのは非常に簡単です。

CSP は能動的・受動的なコンテンツの読込元オリジンを制限することで動作します。そのうえ、インライン JavaScript の実行や eval() の利用など、能動的なコンテンツの特性を制限することも可能です。

CSP の導入

CSP を導入するには、Web サイトが利用するすべてのリソースについて、許可したいオリジンのリストを定義する必要があります。例として、スクリプトとスタイルシートと画像がローカルでホストされており、CDN から jQuery ライブラリを読み込むシンプルな Web サイトを考えると、ポリシーは次のようになります。

Content-Security-Policy:
    default-src 'self';
    script-src 'self' https://code.jquery.com;

上記の例では HTTP ヘッダに Content-Security-Policy を指定していますが、Content-Security-Policy-Report-Only ヘッダを指定することも可能です。このヘッダを指定すると、ユーザエージェントはエラーを報告しますが、能動的にブロックすることはしません。新しいポリシーを検証する際に有効にしておくと便利な機能です。

補足: script-src にも 'self' を明示的に指定する必要があります。すなわち、ディレクティブを定義しても default-src を継承することはありません。

default-src を常に定義することはとても重要です。定義されなかった場合、他のディレクティブですべてのリソースが許可されてしまう可能性があります。default-src 'self' を指定すると、画像の読込元として Web サイト自身のドメインも許可されます。

default-src は特別なディレクティブであり、読込元が設定されていない他のディレクティブのフォールバックに利用されます。しかし、以下のディレクティブは default-src を継承しないため注意が必要です。以下のディレクティブに値を設定しなかった場合、単に値がセットされていないか、ブラウザのデフォルト設定が使用されるものと解釈されます。

  • base-uri
  • form-action
  • frame-ancestors
  • plugin-types
  • report-uri
  • sandbox

default-srcself を指定しても、自分の管理するドメインを指すので常に安全です。とはいえ、デフォルト設定をより強固なものにしたい場合は、default-src 'none' を利用し、既知のリソースタイプをすべて書き下すことも可能です。上記の例に当てはめると、ポリシーは次のようになります。

Content-Security-Policy:
    default-src 'none';
    img-src 'self';
    script-src 'self' https://code.jquery.com;
    style-src 'self';

補足: Web サイト上で prefetch を使用している場合、default-src 'none' によって問題が生じる可能性があります。AMO へ CSP を導入する際、prefetch されたリソースのコンテンツタイプが Firefox で認識されず、default-src にフォールバックしてしまう事象が見つかりました。このとき、Web サイトに必要なオリジンが default-src でカバーされていなければ、prefetch されたリソースはブロックされてしまいます。この問題に関する詳細は Bug 1242902 で公開されています

インラインスクリプトの扱い

明示的な指定のないデフォルトの CSP では、インラインの JavaScript が許可されません。すなわち、この場合では以下を取り除いておく必要があります。

  • ページ内の <script> ブロック
  • HTML 内の DOM イベントハンドラ(onclick など)
  • javascript: 疑似スキーム

これらを許可する必要がある場合は、ディレクティブの値に nonce-sourcehash-source を用いることで安全に実現できます。これらを利用すると、指定したブロック内のスクリプト実行を許可することができます。script-src ディレクティブに 'unsafe-inline' を指定すればこの保護を外すことも可能ですが、Web サイトが XSS 攻撃に脆弱になるため、'unsafe-inline' は利用しないことを強く推奨します。

nonce-sourcehash-source に関する詳細は CSP for the web we have(参考: 日本語訳)をご覧ください。

eval() の扱い

CSP では動的なスクリプト実行もブロックされ、以下が該当します。

  • eval()
  • setTimeout / setInterval の第 1 引数に渡す文字列
  • new Function() コンストラクタ

動的実行が必要ならば 'unsafe-eval' を利用することもできます。しかし、繰り返しになりますが 'unsafe-eval' の利用は推奨できません。なぜなら、eval ブロック内に信頼できないコードを差し込むことは容易だからです。

AMO への導入時、多くのライブラリで eval や new Function といったコードを利用していることが分かりました。この点は CSP の導入時に最も修正に時間がかかる部分です。例えば Underscore の template で new Function が使われており、修正策としてプリコンパイルした template を使用するよう変更しました。

カスケーディングスタイルシート (CSS) の扱い

デフォルトの CSP において以下は許可されません。

  • <style> ブロック
  • HTML 内の style 属性

これは支障が出ることがあります。多くのライブラリでは、JavaScript と一緒にページに追加された HTML スニペット内に style 属性を用いています。また、HTML テンプレートに直接 style 属性が使われることもあります。

説明を加えておくと、CSS スタイルのプロパティを直接 JavaScript で更新した場合は問題になりません。例えば、JQuery の css() メソッドは、スタイルのプロパティを直接更新するので大丈夫です。ただし、JS で追加された HTML ブロックに style="background: red" といった style 属性を用いることはできません。

補足: Firefox のインスペクタでは、JavaScript で追加されたスタイルのプロパティと HTML の style 属性は見分けがつきにくく、ほぼ同じように表示されます。

先程触れたように、部分的にインライン CSS を有効にしたい場合は、ディレクティブの値に nonce-sourcehash-source を使用できます。

もしかすると「CSS に何のリスクがあるんだ?」と思うかもしれません。実際、CSS を上手く用いて Web サイトからデータを窃取する方法が何通りもあります。例えば、属性セレクタと背景画像を用いることで、CSRF トークンのような機密データにブルートフォース攻撃を仕掛けて窃取することが可能です。この手法に関する詳細や、CSS を用いたより高度な他の攻撃ベクタについては XSS (No, the other ‘S’) を参照してください。

style-src'unsafe-inline' を設定することは推奨できません。しかし、インラインスタイルの除去に必要な変更の数とリスクのバランスを考慮する必要があります。

レポート機能

JSON 形式による CSP 違反レポートを集める場所を指定するには、report-uri ディレクティブで設定すると良いでしょう。現在の CSP にはエラーレポートをまとめる機能はないため、単一のページに複数のエラーがあった場合には、指定したエンドポイントに複数のレポートが送信されます。接続数が多い状態で Web サイトを稼働させてしまうと、報告先のエンドポイントには大量のトラフィックが流れ込んでしまいます。

実際の違反によって送信されたレポートに加え、多くのアドオンやブラウザ拡張が CSP に違反することにも気づくかと思います。全体的にノイズが大きくなるため、受信データにフィルタリングを行うことを強くおすすめします。

テスト

一番最初のポリシーを作成したら、次にポリシーをテストし、見落としているオリジンを修正します。大きな Web サイトを運用する場合、リソースの送信元が多いことに驚くかもしれません。CSP を適用した Web サイトを report-only モードで稼働させると、コンテンツを能動的にブロックしてしまう前に、コンソールと CSP レポートで問題点を把握できます。

コンテンツが誤ってブロックされていないことを確認できたら、いよいよポリシーを適用する段階です。これ以降は、見落としがないか注意することと、新しい CSP の機能をサポートしたブラウザに追随していくのみです。

適用

要件を満たすポリシーを適切に構築した後は、CSP ディレクティブを送信するようにシステムを設定する段階です。使用する Web サーバによって設定項目はかなり異なりますが、おおよそ次のようになるはずです。

# CSP を Apache で有効にする場合
Header set Content-Security-Policy "default-src 'none'; img-src 'self';
    script-src 'self' https://code.jquery.com; style-src 'self'"
# CSP を nginx で有効にする場合
add_header Content-Security-Policy "default-src 'none'; img-src 'self';
    script-src 'self' https://code.jquery.com; style-src 'self'";

Web サーバの設定を変更する権限がない場合でも心配ありません!CSP は meta タグでも有効にすることができ、<head> 内における最初の meta タグに CSP を有効にする旨を書くだけで大丈夫です。

<!-- ページの HTML 内で CSP を有効にする場合 -->
<head>
    <meta http-equiv="Content-Security-Policy" content="default-src 'none'; img-src 'self';
          script-src 'self' https://code.jquery.com; style-src 'self'">
</head>

最後の適用作業

AMO はこれまでの運用期間が長く、かつ複雑度のかなり高い Web サイトであることを考えると、私たちが設計したポリシーの最終形に興味を持たれるかもしれません。実際には次のようになりました。

Content-Security-Policy:
    default-src 'self';
    connect-src 'self' https://sentry.prod.mozaws.net;
    font-src 'self' https://addons.cdn.mozilla.net;
    frame-src 'self' https://ic.paypal.com https://paypal.com
        https://www.google.com/recaptcha/ https://www.paypal.com;
    img-src 'self' data: blob: https://www.paypal.com https://ssl.google-analytics.com
        https://addons.cdn.mozilla.net https://static.addons.mozilla.net
        https://ssl.gstatic.com/ https://sentry.prod.mozaws.net;
    media-src https://videos.cdn.mozilla.net;
    object-src 'none';
    script-src 'self' https://addons.mozilla.org
        https://www.paypalobjects.com https://apis.google.com
        https://www.google.com/recaptcha/ https://www.gstatic.com/recaptcha/
        https://ssl.google-analytics.com https://addons.cdn.mozilla.net;
    style-src 'self' 'unsafe-inline' https://addons.cdn.mozilla.net;
    report-uri /__cspreport__

これはすごいですね!容易に想像できる通り、かなり多くのテスト によって AMO の利用するリソースが無数に見つかりました。

まとめ

その Web サイトが運用されてきた期間が長いほど、妥当な Content Security Policy を設定するのには多くの時間がかかります。しかしながら、CSP は 多層防御 を構成する追加のセキュリティレイヤーにできるため、時間をかけてでも導入するだけの価値があります。

もっと詳しく