はじめに
クロスサイトリクエストフォージェリについてまとめる。
クロスサイトリクエストフォージェリとは
サイトに攻撃用のスクリプトを仕込むことでアクセスしてきたユーザーに意図しない操作を強制すること。
具体的な被害例として、「あるサービスに勝手に登録させられてしまう」、「掲示板やブログに勝手に書き込みされてしまう」「ショッピングサイトで自動的に物品を購入されてしまう」のような被害を受ける可能性がある。
例
以下のようなコードで書かれたサイトを運営していたとして、
insert_form.php
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>データの登録</title>
</head>
<body>
<form action="insert.php" method="POST">
<div>
<label for="isbn">ISBNコード:</label>
<input id="isbn" name="isbn" type="text" siza="25" maxlength="20">
</div>
<div>
<label for="title">書名:</label>
<input id="title" name="title" type="text" siza="35" maxlength="150">
</div>
<div>
<label for="price">価格:</label>
<input id="price" name="price" type="text" siza="5" maxlength="5">円
</div>
<div>
<label for="publish">出版社:</label>
<input id="publish" name="publish" type="text" siza="15" maxlength="30">
</div>
<div>
<label for="published">刊行日:</label>
<input id="published" name="published" type="text" siza="15" maxlength="10">
</div>
<div>
<input type="submit" value="登録">
</div>
</form>
</body>
</html>
insert_process.php
<?php
require_once __DIR__ . '../DbManager.php';
try {
$db = getDb();
$stt = $db->prepare('INSERT INTO book(isbn, title, price, publish, published)
VALUES(:isbn, :title, :price, :publish, :published)');
$stt->bindValue(':isbn', '$_POST[isbn]');
$stt->bindValue(':title', '$_POST[title]');
$stt->bindValue(':price', '$_POST[price]');
$stt->bindValue(':publish', '$_POST[publish]');
$stt->bindValue(':published', '$_POST[published]');
$stt->execute();
header('Location: http://'.$_SERVER['HTTP_HOST'].dirname($_SERVER['PHP_SELF']).'/insert_form.php');
} catch (PDOException $e) {
die("エラーメッセージ:{$e->getMessage()}");
}
DbManager.php
<?php
function getDb(): PDO
{
$dsn = 'mysql:dbname=selfphp; host=127.0.0.1; charset=utf8';
$usr = 'selusr';
$passwd = 'selfpass';
$db = new PDO($dsn, $usr, $passwd);
return $db;
}
第三者が以下のような攻撃者のページにアクセスすると、
untrue.html
ようこそ、いらっしゃいました!
<iframe src="auto_submit.html" width="1" height="1"></iframe>
auto_submit.html
<html>
<body onload="document.fm.submit();">
<form name="fm" method="POST" action="http://localhost/insert_process.php">
<input type="hidden" name="isbn" value="978-X-7X80-2401-X" />
<input type="hidden" name="title" value="Bad Book" />
<input type="hidden" name="price" value="9999" />
<input type="hidden" name="publish" value="Bad Publish" />
<input type="hidden" name="published" value="2016-12-31" />
</form>
</body>
</html>
insert_process.php
に意図しないデータを送信してしまう。
(インラインフレームの大きさ的にユーザーは何が起こっているのか気づかない)
対策
ワンタイムトークン方式を使う。
ランダムな文字列(トークン)を入力フォーム(insert_form.php
)に隠しフィールドとして埋め込み、セッションにも保存する。
insert_form.php
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>データの登録</title>
</head>
<body>
<form action="insert.php" method="POST">
<div>
<label for="isbn">ISBNコード:</label>
<input id="isbn" name="isbn" type="text" siza="25" maxlength="20">
</div>
<div>
<label for="title">書名:</label>
<input id="title" name="title" type="text" siza="35" maxlength="150">
</div>
<div>
<label for="price">価格:</label>
<input id="price" name="price" type="text" siza="5" maxlength="5">円
</div>
<div>
<label for="publish">出版社:</label>
<input id="publish" name="publish" type="text" siza="15" maxlength="30">
</div>
<div>
<label for="published">刊行日:</label>
<input id="published" name="published" type="text" siza="15" maxlength="10">
</div>
<div>
<input type="submit" value="登録">
</div>
<!-- 追記 ここから-->
<?php
session_start();
$token = uniqid(bin2hex(random_bytes(13)), true);
$_SESSION['token'] = $token;
?>
<input type="hidden" name="token" value="<?php print $token; ?>">
<!-- 追記 ここまで-->
</form>
</body>
</html>
処理側(insert_process.php
)ではフォームからトークンが送信されているか、送信されていたらセッションに保存された値とポストデータが等しいかチェックする。
insert_process.php
<?php
require_once __DIR__ . '../DbManager.php';
// 追記 ここから
session_start();
if (!isset($_POST['token']) || $_POST['token'] !== $_SESSION['token']) {
die('不正なアクセスが行われました');
}
// 追記 ここまで
try {
$db = getDb();
$stt = $db->prepare('INSERT INTO book(isbn, title, price, publish, published)
VALUES(:isbn, :title, :price, :publish, :published)');
$stt->bindValue(':isbn', '$_POST[isbn]');
$stt->bindValue(':title', '$_POST[title]');
$stt->bindValue(':price', '$_POST[price]');
$stt->bindValue(':publish', '$_POST[publish]');
$stt->bindValue(':published', '$_POST[published]');
$stt->execute();
header('Location: http://' . $_SERVER['HTTP_HOST'] . dirname($_SERVER['PHP_SELF']) . '/insert_form.php');
} catch (PDOException $e) {
die("エラーメッセージ:{$e->getMessage()}");
}
参照
書籍名:独習PHP 第4版
著者名:山田祥寛
出版社名:翔泳社