Content-Security-Policy と nonce の話
Content-Security-Policy の nonce を利用すると、XSS の脅威をかなり軽減できます。
そこで、Web Application Framework ではデフォルトで対応したほうがよいのではないか、という旨を @hasegawayosuke さんから教えて頂いたので、実装について考えてみました。
とりあえず CSP の nonce はどういうものなのかを考慮するために、コード例を探していたのですが、実際に動くサンプルというものが nonce 関連のもので見当たりませんでした。
そこで、実際に動くサンプルを用意しました。
https://github.com/tokuhirom/csp-nonce-sample
以下は Sinatra で書かれたサンプルコードです。
require 'sinatra'
require 'securerandom'
get '/' do
@nonce = SecureRandom.base64()
headers 'Content-Security-Policy' => "script-src 'nonce-#{@nonce}' 'unsafe-inline'"
erb :index
end
テンプレートファイルは以下のようになります。
<!doctype html>
<html>
<body>
<h1>CSP の nonce のサンプルです。</h1>
<script type="text/javascript" nonce="<%= @nonce %>">
alert("これは信用されてる");
</script>
<script type="text/javascript">
alert("こんにちは!こんにちは!");
</script>
↑これは、攻撃者が送り込んだメッセージだと思ってください。これは実際にはサポートされているブラウザでは実行されません。
</body>
</html>
"Content-Security-Policy: unsafe-inline; script-src 'nonce-${nonce}'" 形式で書いたら、${nonce}
の部分を script タグの属性にいれないと実行されなくなります。
nonce の値は、リクエストごとにランダムな文字列が生成されるので、クライアント側からは想像もつかず、攻撃することが困難になります。
これはもう、どんどん nonce 使って行った方がいいのでしょうかね!
Web application framework でのサポートについて
Web Application Framework でこの機能をがっつりサポートするのはどうなのでしょうか?
現実的には HTML をパースして nonce を埋め込むアプローチも考えられますが、それでは攻撃者が送ってきた script タグに nonce をつけてしまう可能性もあって効果が薄れます。
理想的には、やはり手作業で一つ一つ nonce 値を埋めていって上げるのがよいでしょう。
Web Application Framework では csp header の送出とテンプレートエンジンへの nonce 値の受け渡しだけを担当し、HTML の書き換えはユーザーに任せたほうがよいように思います。
(追記) unsafe-inline をつけよう
nonceだけだとCSP 1.0には対応してるけどnonceに対応してないSafariとかでサイトが表示できなくなるので、フォールバックのためにunsafe-inlineも付けた方が良いかも。 / “Content-Securit…” http://t.co/C8agAjQyDz
— teppeis (@teppeis) September 25, 2014
@teppeis unsafe-inlineつけたらCSPの意味なくなっちゃうし、かといってUAみてヘッダ変えるとかも綺麗じゃないし、どうするのがいいんですかねー。
— 縺ッ縺帙′繧上h縺 縺吶¢ (@hasegawayosuke) September 26, 2014
@hasegawayosuke nonceとunsafe-inlineが両方あった場合、nonceに対応しているChrome/FFではunsafe-inlineは禁止されるので、「対応ブラウザの被害を緩和する」という目的は達成できるかなと思います。
— teppeis (@teppeis) September 26, 2014
ということなので、unsafe-inline もつけたほうがよいようです。
(追記) nonce つけても XSS される場合。
<script nonce="...">document.write(location.hashref)</script>
のような場合、ダメとのこと。