- 2006-06-09 (金)
JavaScript を使った CSRF 対策の方法として、以前 Kazuho@Cybozu Labs で紹介されました。
CSRF 対策 w. JavaScriptここでさらに一歩進めて、既存の FORM 要素に onsubmit 属性、および hiddenフィールドを直接追加せずに、JavaScript ファイルを1つインクルードすることにより、既存アプリケーションの書き換えをさらに少なくする方法を紹介したいと思います。CSSXSS に対して脆弱でない CSRF 対策とはどのようなものか、という議論が続いているようですが、JavaScript を用いてよいのであれば、簡単な対策手法が存在すると思います。
以下の JavaScript ファイルをHTMLの最後でインクルードする方法です。
fight_csrf.js
sessid_name = "";
scripts = document.getElementsByTagName("script");
len = scripts.length;
for (i = 0 ; i < len ; i ++) {
if (scripts[i].src.indexOf("fight_csrf.js") != -1) {
a = scripts[i].src.split("#");
if (a.length > 1) sessid_name = a[1];
break;
}
}
data = document.cookie + ";";
start = data.indexOf(sessid_name + "=");
end = data.indexOf(";", start);
value = data.substring(start + sessid_name.length + 1, end);
forms = document.forms;
len = forms.length;
for (var i = 0 ; i < len ; i ++) {
var form = forms[i];
if (form.method == "post") {
var sessid = document.createElement("input");
sessid.type = "hidden";
sessid.name = sessid_name;
sessid.value = value;
form.appendChild(sessid);
}
}
HTML の最後でインクルードする記述は、以下のような感じです。
<form method="post" ...>SESSID の部分を、Cookie に保存されているセッションIDのCookie名にしてください。
...
</form>
...
<script type="text/javascript" src="fight_csrf.js#SESSID"></script>
</body>
</html>
JavaScript 内で行っていることは、
- インクルード元の script タグの src 属性の値の # 以降の値から、セッションIDのCookie名を取得する
- Cookie からセッションIDを取得する
- ドキュメント上の全てのPOSTフォームに name 属性がセッション名となる hidden フィールドを追加し、value 属性にセッションIDを代入する
- POSTされた際には、セッションIDがサーバー側に送信される。
たいていのアプリの場合は、フッタとかは共通のテンプレートを使用していると思うので、そのテンプレートに上記の JavaScript ファイルのインクルード文を挿入すれば、クライアント側の対応はこれだけで済ませられます。
後はサーバー側で送信されたセッションIDをチェックしればOKです。PHPを例にすると、以下のような感じです。
<?php
session_start();
if (count($_POST)) {
// counter to CSRF
$sessid_name = sessin_name();
if (! isset($_POST[$sessid_name]) || session_id() != $_POST[$sessid_name]) {
trigger_error('Invalid request.', E_USER_ERROR);
}
...
}
...
?>
なお、CSRF対策にセッションIDを送信する方法については、高木浩光@自宅の日記に詳しく書かれてありますので、そちらを参照して頂ければと思います。