2
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

クロスサイトリクエストフォージェリ

Last updated at Posted at 2024-05-16

はじめに

クロスサイトリクエストフォージェリについてまとめる。

クロスサイトリクエストフォージェリとは

サイトに攻撃用のスクリプトを仕込むことでアクセスしてきたユーザーに意図しない操作を強制すること。

具体的な被害例として、「あるサービスに勝手に登録させられてしまう」、「掲示板やブログに勝手に書き込みされてしまう」「ショッピングサイトで自動的に物品を購入されてしまう」のような被害を受ける可能性がある。

以下のようなコードで書かれたサイトを運営していたとして、

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版
著者名:山田祥寛
出版社名:翔泳社

2
1
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
2
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?