はじめに
問題は、OWASPから出ているCross-Site Request Forgery (CSRF) Prevention Cheat Sheet(JPCERT/CCによる邦訳「クロスサイトリクエストフォージェリ (CSRF) の防止策に関するチートシート」)にツッコミを入れてもらおうというものです。具体的には、このチートシート(カンニングペーパーの意)のDouble Submit Cookie(Cookie の二重送信)の箇所です。以下、JPCERT/CC訳で該当箇所を引用します。Cookie の二重送信要は、ログイン時に乱数でトークンを生成しておいて、それをクッキーに保存しておく。入力フォームではクッキーの値をhiddenパラメータ等に入れて、POSTパラメータとクッキーとで同じトークン値を「二重に」送信し、受け取り側で両者が一致していることを確認するというものですね。上記にもあるように、サーバー側で状態を保持する必要がなく、RESTとの相性が良いということもあり、最近人気が出始めているようです。アプリケーションフレームワークでは、Django、CodeIgniter、FuelPHP等で採用されています。
Cookie の二重送信は、Cookie およびリクエストパラメーターの双方でランダムな値を送信し、サーバー側で Cookie の値とリクエストの値が等しいかどうか検証する手法です。
ユーザーがサイトにログイン するとき、サイトは暗号強度の高い疑似ランダム値を生成し、その値を Cookie としてユーザーのマシンに、セッション ID とは別に送ります 。どんな形であれ、サイトはこの値を保存しておく必要はありません。次にサイトは、機密に関わる送信にはすべてこのランダム値が非表示のフォーム値 (または他のリクエストパラメーター) および Cookie の値として含まれていることを確認します。同一生成元ポリシーにより、攻撃者はサーバーから送信されるどんなデータも読み取ることができません。また、Cookie の値を変更することもできません。攻撃者は、任意の値を悪意のある CSRF リクエストに添付して送信できますが、Cookie に保存されている値は、変更することも、読み取ることもできません。Cookie の値と、リクエストパラメーターまたはフォームの値は同じにする必要があるので、攻撃者はランダムの CSRF 値を推測できない限り、フォームを正常に送信できません。
Direct Web Remoting (DWR) の Java ライブラリバージョン 2.0 には、CSRF 対策として、透過的に Cookieの二重送信を行う機能が組み込まれています。
上記の説明は技術的な間違いがあるので、それを指摘してもらおう。天下のOWASPのドキュメントにいちゃもんをつけるのだから、上級問題にふさわしい…そう思い、記事執筆の準備としてチートシートの原文(英語)を久しぶりに確認しました。
…あれ?
…大幅に改定されている
…Double Submit Cookieの位置づけも変わっている
ということで、なんと「解答」が本家のサイトに掲載されているというまぬけな状況になってしまいました。改定は今年の10月に行われたようです(改訂履歴)。
まぁ、チートシートでカンニングができるという、まことにチートシートにふさわしい状況になってしまったのですが、問題自身は面白いので、初期とか上級とか抜きにして出題したいと思います。
参考実装
読者の便宜のために参考実装を以下に示します。仕様は初級編等と同じですので、画面遷移例は初級編の記事を参照してください。以下はテスト用に「ログインしたことにする」スクリプト(mypage.php)。
以下はメールアドレス変更フォーム(chgmailform.php)です。<?php // mypage.php ログインしたことにする確認用のスクリプト session_start(); if (empty($_SESSION['id'])) { $_SESSION['id'] = 'alice'; // ユーザIDは alice 固定 $_SESSION['mail'] = 'alice@example.com'; // メールアドレス初期値 } ?><body> ログイン中(id:<?php echo htmlspecialchars($_SESSION['id'], ENT_QUOTES, 'UTF-8'); ?>)<br> メールアドレス:<?php echo htmlspecialchars($_SESSION['mail'], ENT_QUOTES, 'UTF-8'); ?><br> <a href="chgmailform.php">メールアドレス変更</a><br> </body>
以下はメールアドレス変更プログラム(chgmail.php)。POSTパラメータとクッキーのトークンを確認の後、メールアドレスを変更(実際にはセッション変数のみ変更)します。<?php // chgmailform.php メールアドレス変更フォーム session_start(); if (empty($_SESSION['id'])) { die('ログインしてください'); } $token = filter_input(INPUT_COOKIE, 'token'); if (empty($token)) { $token = bin2hex(random_bytes(24)); // CSRFトークンの生成 setcookie('token', $token); // CSRFトークンをクッキーに保存 } ?><body> <form action="chgmail.php" method="POST"> メールアドレス<input name="mail"><BR> <input type=submit value="メールアドレス変更"> <input type="hidden" name="token" value="<?php echo htmlspecialchars($token, ENT_COMPAT, 'UTF-8'); ?>"> </form> </body>
メールアドレス変更時のPOSTリクエスト例を以下に示します。<?php // chgmail.php メールアドレス変更実行 session_start(); if (empty($_SESSION['id'])) { die('ログインしてください'); } $id = $_SESSION['id']; $c_token = filter_input(INPUT_COOKIE, 'token'); $p_token = filter_input(INPUT_POST, 'token'); if (! hash_equals($c_token, $p_token)) { die('正規の画面からご使用ください'); } $mail = filter_input(INPUT_POST, 'mail'); $_SESSION['mail'] = $mail; // メールアドレスの変更 ?> <body> <?php echo htmlspecialchars($id, ENT_COMPAT, 'UTF-8'); ?>さんのメールアドレスを<?php echo htmlspecialchars($mail, ENT_COMPAT, 'UTF-8'); ?>に変更しました<br> <a href="mypage.php">マイページ</a> </body>
クッキーとPOSTパラメータで、同じトークンが二重に送信されていることがわかります。POST http://example.jp/chgmail.php HTTP/1.1 Host: example.jp Content-Length: 79 Cache-Control: max-age=0 Origin: http://example.jp Upgrade-Insecure-Requests: 1 Content-Type: application/x-www-form-urlencoded User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/70.0.3538.102 Safari/537.36 Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8 Referer: http://example.jp/chgmailform.php Accept-Encoding: gzip, deflate Accept-Language: ja,en-US;q=0.9,en;q=0.8 Cookie: PHPSESSID=onvi06g301m1o2cb1i0lgm9neg; token=fe24151956678c544bef14258407e12032ada4216f36480b Connection: close mail=alice%40wasbook.org&token=fe24151956678c544bef14258407e12032ada4216f36480b
(2018/11/16追記)
Herokuにデモ環境を用意しました
https://csrf-vul.herokuapp.com/mypage.php
設問
チートシート旧版の翻訳であるJPCERT/CC訳(前述の引用部分)を元に以下の設問に答えよ。- 引用部分の解説には技術的な間違いがある。それを指摘せよ
- クッキーの二重送信でCSRF保護できないシナリオを複数指摘せよ。OWASP原文の改定で指摘されていないシナリオを指摘すると加点となる
解答は週明けに公開予定です。
0 件のコメント:
コメントを投稿