CSRFとは
CSRF(Cross Site Request Forgery)とは、Webアプリのユーザー自身が、意図しない処理を実行するようにする脆弱性または攻撃手法のことです。
悪意のあるWebサイトにスクリプトや自動転送(HTTPリダイレクト)を仕込むことによって、ユーザーに別のWebサイト上で何らかの操作を意図せずに行わせる攻撃となっています。
CSRF対策をしていないサイトの例と攻撃方法
今回、会員サイトのパスワード変更画面を例にします
ログイン後にパスワード変更を行う場合を想定しております
(今回テーマから逸れてしまうので、ログイン画面とパスワード変更完了時のサニタイズ処理は省略しております)。
パスワード変更_入力画面
<?php
// ログイン確認
session_start();
if (!isset($_SESSION[‘id’])) {
header('Location: http://hoge.com/login.php');
}
?>
<h1>パスワード変更</h1>
<form action="http://hoge.com/password_change_complete.php" method="post">
<input type="hidden" name="password" value="password">
<input type="password" name="password">
<input type="submit" value="変更する">
</form>
パスワード変更_完了画面
<?php
session_start();
if (!isset($_SESSION[‘id’])) {
header('Location: http://hoge.com/login.php');
}
$id = $_SESSION[‘id’];
$password = filter_input(INPUT_POST, ‘password’);
?>
<h1>パスワード変更完了</h1>
<p><?php echo $id; ?>さんのパスワードを<?php echo $password; ?>に変更しました。</p>
図:id:hoge、変更後のパスワード:passwordで変更を行った場合
このようなCSRF対策のされていない画面でパスワードの変更を行うと、一定時間ブラウザにcookieの情報が保持されてしまいます。
cookieが保持されている状態で、以下のようなタグが入っている悪意のある別のWebページにアクセスしてしまうと、会員情報変更処理ページに自動でPOST送信されて、パスワードが変更されてしまいます。
CSRF攻撃(パスワードを勝手に変更)
// password_change.html
<body onload="document.forms[0].submit()">
<form action="http://hoge.com/password_change_complete.php" method="POST">
<input type="hidden" name="password" value="cracked">
</from>

図:id:hogeのcookie情報が残っている状態で上記のCSRF攻撃にあった場合
CSRFの対策
主な対策としては、データを送信するページにトークンを埋め込む方法がとられます。
これは、正規利用者の意図したリクエストかを確かめるために行います。
リクエスト送信フォーム内にhidden値でワンタイムのtokenを埋め込むことで、同一セッション内での一致の確認を行います。
対策に必要なページは、他のサイトから勝手に実行されてはいけないページです。
例えばECサイトでは、物品購入の確定ページや、パスワード変更など個人情報の編集確定画面などが挙げられます。
PHPの場合:inputタグでトークンの埋め込み(送信側)+トークンのチェック(受信側)
パスワード変更_入力画面
<?php
session_start();
if (!isset($_SESSION[‘id’])) {
header('Location: http://hoge.com/login.php');
}
if (empty($_SESSION['token'])) { // トークンが空の場合、生成する
$token = bin2hex(openssl_random_pseudo_bytes(24));
$_SESSION['token'] = $token;
} else { // トークンがあれば使用する
$token = $_SESSION['token'];
}
?>
<h1>パスワード変更画面</h1>
<form action="http://hoge.com/password_change_complete.php" method="post">
<input type="hidden" name="token" value="<?php echo htmlspecialchars($token, ENT_COMPAT, 'UTF-8'); ?>">
<input type="text" name="password">
<input type="submit" value="変更する">
</form>
パスワード変更_完了画面
<?php
session_start();
if (!isset($_SESSION[‘id’])) {
header('Location: http://hoge.com/login.php');
}
$token = filter_input(INPUT_POST, 'token');
if (empty($_SESSION['token']) || $token !== $_SESSION['token']) {
die('CSRF攻撃が発生 !!');
}
$id = $_SESSION[‘id’];
$password = filter_input(INPUT_POST, ‘password’);
?>
<h1>パスワード変更完了</h1>
<p><?php echo $id; ?>さんのパスワードを<?php echo $password; ?>に変更しました。</p>
CakePHPの場合:コントローラーにコンポーネントを追加する
CakePHPには共通のコントローラごとに共通の処理を支援する、「コンポーネント」という機能があります。
この中でトークンを埋め込み、CSRF対策をしてくれる機能が提供されています。
AppContorller.phpに以下の記述を行うことで、自動的にCSRF対策を行ってくれるようになるので、View側での記述は特に必要ありません。
<?php
class AppController extends Controller {
{
public $components = array('Security');
}
}
Laravel(Blade)の場合:viewファイルのformタグ内に「@csrf」と記述する
<h1>パスワード変更画面</h1>
<form action="{{ url('/password_change_complete') }}" method="post">
@csrf
// もしくは {{ csrf_field() }}
<input type="text" name="password">
<input type="submit" value="変更する">
</form>
LaravelではミドルウェアにデフォルトでCSRF対策が入っています(Formタグが存在するページでCSRF対策ができていないと、自動的に419ページにリダイレクトします)。
<?php
protected $middlewareGroups = [
'web' => [
\App\Http\Middleware\VerifyCsrfToken::class,
...
],
];
XSSとの違いについて
XSS(Cross Site Scripting):とは、Webアプリのユーザーが、Webページにアクセスすることで不正なスクリプトが実行されてしまう攻撃手法のことです。
文章で簡潔に表すと、かなり類似していますが、攻撃が実行される場所が異なります。
攻撃シナリオを示すと以下のようになります。
①ユーザーが罠サイト閲覧する
②仕掛けのあるHTMLの実行される
③②により、ユーザーから別のサイトに攻撃用のリクエストが送られる(CSRF)
④仕掛けを含むレスポンスが返ってくる(XSS)
CSRFでは、③のリクエストに対するサーバー側の処理を悪用しています。
不正なスクリプトが実行されるのは「 Webサーバー」です。
そのため、CSRF対策が必要となるページは、ログイン後に変更処理が絡んでくる箇所のみとなります。
対してXSSの場合は、③のリクエストに対して④が返ってきてそれがユーザーのWebブラウザ上で実行されます。不正なスクリプトが実行されるのは「被害にあうユーザーの Webブラウザ」となっています。
まとめ
PHP | CakePHP | Laravel |
---|---|---|
トークン埋め込み処理書く | コンポーネントの追加 | @csrf |
参考
- 体系的に学ぶ 安全なWebアプリケーションの作り方 第2版 徳丸 浩 (著)
- https://book.cakephp.org/3/ja/controllers/components/csrf.html
- https://readouble.com/laravel/7.x/ja/csrf.html