monoの開発ブログ

Content Security PolicyでXSSを断ち切る

XSSやクリックジャッキングなどの攻撃を軽減するContent Seucrity Policy (CSP)を紹介します。Google Chromeの拡張機能でもそろそろ有効化されそうですし、学ぶにはよいタイミングなのではないでしょうか。

Content Security Policyでは、読み込み可能なリソースをホワイトリストで制限することで、悪意ある攻撃者によって予期しないリソースを読み込まされることを防ぎます。従来は、scriptタグを挿入することで外部のスクリプトを自由に実行できてしまうなんでもありの状態でしたが。Content Security Policyを利用すると細かなアクセス制御が可能になり、許可したスクリプトだけを実行できます。攻撃者が用意したスクリプトをできなくなれば、攻撃の幅は大きく狭まります。

Content Security PolicyはFirefox 4以降やChromium 13以降に実装されています。変更は追加のヘッダだけなので後方互換性が保たれており、非対応のブラウザでもポリシーによる保護が働かないだけで問題なく動作します。

できること

XSS対策

外部のスクリプトの実行を制限し、さらに後述するインラインJavaScriptの無視・evalの無効化を行うことによって、XSSが起きてもスクリプトを一切実行できないようにします。攻撃者が用意したスクリプトを実行できなければ、XSSは何ら意味を持たなくなるため、攻撃を根絶できます。

クリックジャッキング対策

クリックジャッキングでは透明なiframeにクリックさせたいページを読み込ませますが、Content Security Policyを利用するとiframe内に読み込めるページを制限できるので、クリックジャッキングを防ぐことができます。

制限されること

外部のリソースだけを制限しても十分とは言えません。Content Security Policyを利用した場合、外部リソースの制限に加えて以下の機能の利用も制限されます。既存のアプリケーションにContent Security Policyを導入する場合は修正が必要ですので注意してください。

インラインJavaScript

以下のようなインラインのJavaScriptは実行されません。

<script type="text/javascript">
  ...
</script>

JavaScriptはすべて独立したファイルとして用意する必要があります。

<script type="text/javascript" src="..."></script>

そのほか、以下のような記述も無視されます。

<a href="javascript:do_something_evil()">...</a>
<a onclick="another_evil_script()">...</a>

イベントを処理する場合はonxxxプロパティかaddEventListenerを利用する必要があります。

element.onclick = someFunction;
element.addEventListener('click', someFunction, false);

インラインJavaScriptが制限されることによって、実行できるJavaScriptはContent Security Policyによって制御される外部ファイルに限定されます。

文字列内のコード

JavaScriptには、evalなど文字列をコードとして評価する機能があります。これを利用して悪意のあるコードが挿入されることを防ぐため、evalは無効化されます。

eval('...');

また、文字列を渡すことでevalに準ずる働きをする機能もすべて無効化されます。

window.setInterval('...', 10000);
var f = new Function('...');

これらは以下のように書き換える必要があります。

window.setInterval(myFunc, 10000);
var f = function() { ... };

dataスキームURI

以下のようにJavaScriptを実行される恐れがあるため、デフォルトではdataスキームURIも無効化されています。

<a href="data:text/html,<script>omg_evil_script()</script>">...</a>

インラインスタイル

インラインのスタイル指定も無視されます。<style>要素、style属性のどちらも利用できません。<link>要素で指定してください。

<link rel="stylesheet" type="text/css" href="..." />

ただ、現在のところFirefoxではインラインスタイルは無効化されないようです。

ポリシーの書き方

詳しいことはMozillaの資料を読んでみてください。難しくはないですが、長くなりそうなので。

丸投げするだけではアレなので、このブログで試したやつをおいておきます。資料とあわせてご覧いただくとイメージしやすいのではないでしょうか。

ポリシーはFirefox向けにはX-Content-Security-Policyヘッダ、WebKit向けにはX-WebKit-CSPを使って記述します。

# Nginx
add_header X-Content-Security-Policy "...";
add_header X-WebKit-CSP "...";

# Apache
Header set X-Content-Security-Policy "..."
Header set X-WebKit-CSP "..."

それではどうぞ。ここでは見やすさのために改行しながら書いてますが、実際にはすべて1行で記述します。

Google Analytics

画像を許可しているのを不思議に思うかもしれませんが、実は内部ではダミーの画像に対してリクエストを投げることで情報を送信しています。

allow 'self';
script-src 'self' http://www.google-analytics.com;
img-src http://www.google-analytics.com

はてなスター

スターやボタンの画像はs.hatena.ne.jpにありますが、アイコンはst-hatena.comらしいです。

allow 'self';
script-src 'self' http://s.hatena.ne.jp;
img-src 'self' http://s.hatena.ne.jp http://www.st-hatena.com

Twitter

インラインスタイルがあるので微妙に崩れます。

allow 'self';
script-src 'self' http://platform.twitter.com;
frame-src 'self' http://platform.twitter.com

YouTube

allow 'self';
frame-src 'self' https://www.youtube-nocookie.com

Disqus

内部でevalを使っていたのであきらめました。

全部入り

allow 'self';
script-src 'self' http://www.google-analytics.com http://platform.twitter.com http://s.hatena.ne.jp;
img-src 'self' http://www.google-analytics.com http://s.hatena.ne.jp http://www.st-hatena.com;
frame-src 'self' http://platform.twitter.com https://www.youtube-nocookie.com

導入してみて

試してみるとわかりますが、かなりめんどくさいです。普段何気なく利用しているもののひとつひとつについて、中身を調べて適切にポリシーを記述する必要があります。あまりやりたい作業ではないですね。ただ、外部のスクリプトをぽんぽん貼っていた今までが安易すぎたとも考えられるのではないでしょうか。ホワイトリストにドメインを追加していく中で、本当にそのスクリプトが必要なのか、あるいは信頼できるのか考え直してみるのもいいかもしれません。

おわりに

断ち切る、などと釣りっぽいタイトルを付けましたが、本当にXSSを断ち切るといっていいほどの力を発揮してくれる可能性を持った機能と言えそうです。しかし、インラインJavaScriptやevalなど既存のコードに対して修正が必要な点もあり、特に外部サイトのスクリプトだと対応が困難なのは致命的です。時間が解決してくれればよいのですが、こればっかりは何とも言えません。

参考