愛と勇気と缶ビール

ふしぎとぼくらはなにをしたらよいか

Content Security Policy (CSP) についてちょっと調べたことのメモ

http://blog.monoweb.info/article/2012031523.html

しばらく前に↑のblogで記事になった、Content Security Policy(以下CSP)についてのメモ。

ちなみにCSP自体の仕様については、

などを参照。下のは仕様というよりmozillaにおける実装かな。

CSPでできること

ざっくりとだけCSPで可能なことを示すと

  • デフォルト機能
    • href="javascript:foo()"のようなJavaScript URIの無効化
    • onclick="foo()"のようなinline Event Handlerの無効化
    • inline scriptの無効化
    • inline styleの無効化
    • eval("[code]"), setTimeout("[code]", 0), setInterval("[code"], 1000), new Function("[code]")のような文字列evalの無効化
  • リソースに対する制限
    • frame
    • img
    • script
    • font
    • style
    • media
    • object
    • xhr, connect (XMLHttpRequest, WebSocket, Server-Sent Events)
    • 他にもあるかも
  • Policy違反が行われた場合のReport機能
    • policy-uriディレクティブで指定したURIにJSONで違反内容がPOSTされる
    • ブロックはしないがReportだけするモードにもできる(X-Content-Security-Report-Only)
  • Policyのファイル読み込み
    • Response Headerに入れずに、サーバ側に置いたファイルにpolicyを書くこともできる
  • data URIのブロックとかもできる
  • sandbox?

のような感じ。

ブラウザベンダの実装

Firefoxでは"X-Content-Security-Policy", WebKit系のブラウザでは"X-WebKit-CSP"というレスポンスヘッダを返せばよい。僕が調べた範囲では、上記に挙げたような機能はほぼ期待通りにうごく。

例によって挙動というか実装が違う部分があるので、その辺についてちょっと解説。ちなみにブラウザの挙動は Firefox => 11.0, Chrome => 17.0.963.83 にて調べたもので、ソースを参照している部分については本日(2012/03/25)に mozilla => hg, WebKit => svn から引っ張ってきたものなのでけっこうズレがあることをご了承ください。

xhrとWebSocketを制御するためのディレクティブ

どちらのブラウザも、xhrによるリソースアクセスを制御するディレクティブとWebSocketのそれには同じものを使う。ちなみに、Server-Sent EventsのEventSourceについてもこれが適用される。これらを制御するためのディレクティブが、mozillaでは"xhr-src"になっていて、WebKitでは"connect-src"になっている。W3Cの現時点でのdraftに準拠している、という意味ではWebKitの"connect-src"が正しい。mozillaのは昔のdraftに準拠している感じかな。

default-srcを指定しなかった場合の挙動の違い

mozillaではdefault-srcを指定しなかった場合は"default-src *"を指定した場合と同じ挙動になる。

コンストラクタでとりあえず"default-src *"をぶっ込んでしまって、そこからcsp_refinePolicyで追加していく感じなんだと思う。

mozilla-central/content/base/src/contentSecurityPolicy.js:

function ContentSecurityPolicy() {
  CSPdebug("CSP CREATED");
  this._isInitialized = false;
  this._reportOnlyMode = false;
  this._policy = CSPRep.fromString("default-src *");

  // default options "wide open" since this policy will be intersected soon
  this._policy._allowInlineScripts = true;
  this._policy._allowEval = true;

  this._request = "";
  this._docRequest = null;
  CSPdebug("CSP POLICY INITED TO 'default-src *'");
}


WebKitではこういうことをしていないので、default-srcとscript-srcの両方を指定しなかった場合、inline scriptやevalに対するブロックは動かず素通しになる。

WebKit/Source/WebCore/page/ContentSecurityPolicy.cpp:

bool ContentSecurityPolicy::allowEval() const
{
    DEFINE_STATIC_LOCAL(String, consoleMessage, ("Refused to evaluate script because of Content-Security-Policy.\n"));
    return checkEvalAndReportViolation(operativeDirective(m_scriptSrc.get()), consoleMessage);
}

CSPDirective* ContentSecurityPolicy::operativeDirective(CSPDirective* directive) const
{
    return directive ? directive : m_defaultSrc.get();
}

bool ContentSecurityPolicy::checkEvalAndReportViolation(CSPDirective* directive, const String& consoleMessage) const
{
    if (checkEval(directive))
        return true;
    reportViolation(directive->text(), consoleMessage);
    return denyIfEnforcingPolicy();
}

要は、script-src -> default-src の順に調べて、どっちも無かったら通す、というロジックになっている。

reportの送り方

mozillaはちゃんとapplication/jsonで送ってくるが、WebKitはなぜかapplication/x-www-form-urlencodedで送ってくる

もろもろ

ほか細かい点も含めて、全体としてはWebKitの方がW3C draftに追随している感じ。reportが謎。

結論

以上の点を踏まえて現時点でCSPを使うなら、

  • default-srcは自前で指定する
  • xhr/WebSocket/Server-Sent Eventsの制御については、xhr-srcとconnect-srcの両方を指定する
  • reportを受け取る場合は、application/jsonとapplication/x-www-form-urlencodedの両方を受け入れられるようにしておく

という風にするのがよいと思われる。

追記

使っているブラウザでCSP featureが有効かどうかは下のページで見れます

http://people.mozilla.org/~bsterne/content-security-policy/demo.cgi