前回:やられアプリ BadTodo - 10.1 CSRF(クロスサイト・リクエスト・フォージェリ) - demandosigno
安全なウェブサイトの作り方 - 1.6 CSRF(根本的解決)
処理を実行するページをPOSTメソッドでアクセスするようにし、その「hiddenパラメータ」に秘密情報が挿入されるよう、前のページを自動生成して、実行ページではその値が正しい場合のみ処理を実行する。
ここでは具体的な例として、「入力画面 → 確認画面 → 登録処理」のようなページ遷移を取り上げて説明します。
まず、利用者の入力内容を確認画面として出力する際、合わせて秘密情報を「hidden パラメータ」に出力するようにします。この秘密情報は、セッション管理に使用しているセッションIDを用いる方法の他、セッションIDとは別のもうひとつのID(第2セッションID)をログイン時に生成して用いる方法等が考えられます。
生成するIDは暗号論的擬似乱数生成器を用いて、第三者に予測困難なように生成する必要があります。次に確認画面から登録処理のリクエストを受けた際は、リクエスト内容に含まれる「hiddenパラメータ」の値と、秘密情報とを比較し、一致しない場合は登録処理を行わないようにします。このような実装であれば、攻撃者が「hiddenパラメータ」に出力された秘密情報を入手できなければ、攻撃は成立しません。
なお、このリクエストは、POSTメソッドで行うようにします。これは、GET メソッドで行った場合、外部サイトに送信されるRefererに秘密情報が含まれてしまうためです。
トークン(秘密情報)の生成
PHPには暗号論的疑似乱数生成器が複数用意されています。
BadTodoでは badapp.php
にてopenssl_random_pseudo_bytes()
で生成されています。
<?php public function get_token() { $token = $this->get(TOKENNAME); if (empty($token)) { $token = bin2hex(openssl_random_pseudo_bytes(TOKENLEN)); $this->set(TOKENNAME, $token); } return $token; }
トークンのチェック
前回確認したように、BadTodo上の更新リクエストにはCSRF用対策トークンが付与されていますが、トークンのチェックが漏れているリクエストがあります。今回は前回見つかったメールアドレスの変更リクエストを例として修正します。
changemaildo.php
の先頭付近に追記。
<?php require_once('./common.php'); $app->require_loggedin(); // ▼▼ 追記ここから $token = filter_input(INPUT_POST, TOKENNAME); if ($token != $app->get(TOKENNAME)) { error_exit('正規の画面から使用ください'); } // ▲▲ 追記ここまで $id = $app->get_id();
確認
罠サイトからリクエストを送信しても、(ソースは前回を参照)
チェックが入りエラーとなります。
Refererのチェック・CookieのSameSite属性
後日