質問をすることでしか得られない、回答やアドバイスがある。

15分調べてもわからないことは、質問しよう!

ただいまの
回答率

90.49%

  • PHP

    24449questions

    PHPは、Webサイト構築に特化して開発されたプログラミング言語です。大きな特徴のひとつは、HTMLに直接プログラムを埋め込むことができるという点です。PHPを用いることで、HTMLを動的コンテンツとして出力できます。HTMLがそのままブラウザに表示されるのに対し、PHPプログラムはサーバ側で実行された結果がブラウザに表示されるため、PHPスクリプトは「サーバサイドスクリプト」と呼ばれています。

  • CSRF

    56questions

    クロスサイトリクエストフォージェリ (Cross site request forgeries、CSRF)は、 外部Webページから、HTTPリクエストによって、 Webサイトの機能の一部が実行されてしまうWWWにおける攻撃手法です。

PHP CSRF対策について

解決済

回答 2

投稿 編集

  • 評価
  • クリップ 0
  • VIEW 772

yyyyyk

score 7

初めまして。
現在PHPを勉強していて、クイズサイトを作成しています。
問題の編集画面にて、CSRFの対策を取ろうと思っているのですがうまくいきません。

<?php

session_start();

//変数宣言
$num = $_GET['num'];
$msg = '';

//セッションがセットされていない場合の処理
if(!isset($_SESSION['NAME'])) {

    header("Location: Login.php");
    exit();

}

//管理者権限以外がページにアクセスした場合の処理
if($_SESSION['kengen'] != 'kanri') {

    header("Location: main.php");
    exit();

}

//GETで受け取った値が不正だった場合の処理
if(!preg_match("/^[0-9]+$/", $num)) {

    header("Location: error.php");
    exit();    

}

        //GETで受け取ったNumのデータを引っ張る
        try{

                //データベースから問題を引っ張る
                $DB = new PDO('mysql:host=localhost;dbname=question;charset=utf8','root','');
                $stmt = $DB->query("SELECT * FROM question WHERE num=".$num);
                $row = $stmt->fetch(PDO::FETCH_ASSOC);

            } catch(PDOException $e) {

                $err_msg = 'データベースエラー';

            }

    //更新機能
    if(isset($_POST['update'])) {

        //token発行
        $token = sha1(uniqid(mt_rand(), true));
        $_SESSION['token'] = $token;

        //CSRF対策
        if($_SESSION['token'] != $_POST['token']) {

            header("Location: error.php");
            exit();

        }

        $title = $_POST['title'];
        $content = $_POST['content'];
        $question1 = $_POST['question1'];
        $question2 = $_POST['question2'];
        $question3 = $_POST['question3'];
        $answer = $_POST['answer'];


            if(!empty($title)) {

                try{

                    $DB = new PDO('mysql:host=localhost;dbname=question;charset=utf8','root','');
                    $stmt = $DB->prepare("UPDATE question set title = :title WHERE num =".$num);
                    $stmt->bindParam(':title', $title, PDO::PARAM_STR);
                    $stmt->execute();

                } catch(PDOException $e) {

                    $err_msg = 'データベースエラー';

                }

            }

            if(!empty($content)) {

                try{

                    $DB = new PDO('mysql:host=localhost;dbname=question;charset=utf8','root','');
                    $stmt = $DB->prepare("UPDATE question set content = :content WHERE num =".$num);
                    $stmt->bindParam(':content', $content, PDO::PARAM_STR);
                    $stmt->execute();

                } catch(PDOException $e) {

                    $err_msg = 'データベースエラー';

                }

            }

            if(!empty($question1)) {

                try{

                    $DB = new PDO('mysql:host=localhost;dbname=question;charset=utf8','root','');
                    $stmt = $DB->prepare("UPDATE question set question1 = :question1 WHERE num =".$num);
                    $stmt->bindParam(':question1', $question1, PDO::PARAM_STR);
                    $stmt->execute();

                } catch(PDOException $e) {

                    $err_msg = 'データベースエラー';

                }

            }

            if(!empty($question2)) {

                try{

                    $DB = new PDO('mysql:host=localhost;dbname=question;charset=utf8','root','');
                    $stmt = $DB->prepare("UPDATE question set question2 = :question2 WHERE num =".$num);
                    $stmt->bindParam(':question2', $question2, PDO::PARAM_STR);
                    $stmt->execute();

                } catch(PDOException $e) {

                    $err_msg = 'データベースエラー';

                }

            }            

            if(!empty($question3)) {

                try{

                    $DB = new PDO('mysql:host=localhost;dbname=question;charset=utf8','root','');
                    $stmt = $DB->prepare("UPDATE question set question3 = :question3 WHERE num =".$num);
                    $stmt->bindParam(':question3', $question3, PDO::PARAM_STR);
                    $stmt->execute();

                } catch(PDOException $e) {

                    $err_msg = 'データベースエラー';

                }

            }

            if(!empty($answer)) {

                try{

                    $DB = new PDO('mysql:host=localhost;dbname=question;charset=utf8','root','');
                    $stmt = $DB->prepare("UPDATE question set answer = :answer WHERE num =".$num);
                    $stmt->bindParam(':answer', $answer, PDO::PARAM_STR);
                    $stmt->execute();

                } catch(PDOException $e) {

                    $err_msg = 'データベースエラー';

                }

            }        


            $msg = '更新しました';

        }


        try{

                //データベースから問題を引っ張る
                $DB = new PDO('mysql:host=localhost;dbname=question;charset=utf8','root','');
                $stmt = $DB->query("SELECT * FROM question WHERE num=".$num);
                $row = $stmt->fetch(PDO::FETCH_ASSOC);

            } catch(PDOException $e) {

                $err_msg = 'データベースエラー';

            }






?>



<!DOCTYPE html>
<html>
<head>
    <title>問題編集</title>
</head>
<body>
    <h3>問題編集</h3>
    <p><?php echo $msg; ?></p>
    <?php var_dump($_SESSION['token']); ?>
    <form method="POST" action="">
    タイトル:<input type="text" name="title" value="" placeholder="<?php echo htmlspecialchars($row['title'], ENT_QUOTES); ?>">
    <br>
    クイズ内容:<input type="text" name="content" value="" placeholder="<?php echo htmlspecialchars($row['content'], ENT_QUOTES); ?>">
    <br>
    質問1:<input type="text" name="question1" value="" placeholder="<?php echo htmlspecialchars($row['question1'], ENT_QUOTES); ?>">
    <br>
    質問2:<input type="text" name="question2" value="" placeholder="<?php echo htmlspecialchars($row['question2'], ENT_QUOTES); ?>">
    <br>
    質問3:<input type="text" name="question3" value="" placeholder="<?php echo htmlspecialchars($row['question3'], ENT_QUOTES); ?>">
    <br>
    回答:<input type="text" name="answer" value="" placeholder="<?php echo htmlspecialchars($row['answer'], ENT_QUOTES); ?>">
    <br>
    <input type="hidden" name="token" value="<?php echo $_SESSION['token']; ?>">
    <input type="submit" name="update" value="変更">
    </form>
    <a href="main.php">戻る</a>
</body>
</html>

問題箇所としては、//更新処理 の頭です。
現状、token自体は合っていると思うのですが変更ボタンを押下するとエラーページに遷移してしまいます。
宜しければご教示願います。

  • 気になる質問をクリップする

    クリップした質問は、後からいつでもマイページで確認できます。

    またクリップした質問に回答があった際、通知やメールを受け取ることができます。

    クリップを取り消します

  • 良い質問の評価を上げる

    以下のような質問は評価を上げましょう

    • 質問内容が明確
    • 自分も答えを知りたい
    • 質問者以外のユーザにも役立つ

    評価が高い質問は、TOPページの「注目」タブのフィードに表示されやすくなります。

    質問の評価を上げたことを取り消します

  • 評価を下げられる数の上限に達しました

    評価を下げることができません

    • 1日5回まで評価を下げられます
    • 1日に1ユーザに対して2回まで評価を下げられます

    質問の評価を下げる

    teratailでは下記のような質問を「具体的に困っていることがない質問」、「サイトポリシーに違反する質問」と定義し、推奨していません。

    • プログラミングに関係のない質問
    • やってほしいことだけを記載した丸投げの質問
    • 問題・課題が含まれていない質問
    • 意図的に内容が抹消された質問
    • 広告と受け取られるような投稿

    評価が下がると、TOPページの「アクティブ」「注目」タブのフィードに表示されにくくなります。

    質問の評価を下げたことを取り消します

    この機能は開放されていません

    評価を下げる条件を満たしてません

    評価を下げる理由を選択してください

    詳細な説明はこちら

    上記に当てはまらず、質問内容が明確になっていない質問には「情報の追加・修正依頼」機能からコメントをしてください。

    質問の評価を下げる機能の利用条件

    この機能を利用するためには、以下の事項を行う必要があります。

回答 2

checkベストアンサー

+3

//token発行
$token = sha1(uniqid(mt_rand(), true));
$_SESSION['token'] = $token;
//CSRF対策
if($_SESSION['token'] != $_POST['token']) {

token の発行と token の検証の順番を考えてみてください。

あと、試してないけど、SQL インジェクションが可能かと。

投稿

  • 回答の評価を上げる

    以下のような回答は評価を上げましょう

    • 正しい回答
    • わかりやすい回答
    • ためになる回答

    評価が高い回答ほどページの上位に表示されます。

  • 回答の評価を下げる

    下記のような回答は推奨されていません。

    • 間違っている回答
    • 質問の回答になっていない投稿
    • スパムや攻撃的な表現を用いた投稿

    評価を下げる際はその理由を明確に伝え、適切な回答に修正してもらいましょう。

  • 2018/02/04 12:22 編集

    SQL インジェクションの指摘はおかしいですね^^;
    if(!preg_match("/^[0-9]+$/", $num))
    で見てましたね。

    なんか抜け道がありそうな気がしますが、私の知ってる範囲では大丈夫そうです。失礼しました。
    *ただ、prepare でバインドしたほうが良いと思います。

    あと、どこまで指摘するか微妙ですが、軽く補足しておきます。
    *以下のコメントは、新しめの php を使っている前提です。(7系からかなぁ。。。)

    ・token に関して
    暗号論的にセキュアな関数を使用することが推奨されます。
    最近の php だと、random_bytes() や random_int() 等

    ・GET や POST が空の時のフィルタ
    空だと warning が出るので、入力値の確認をしたほうが良いです。

    ・$DB に関して
    何度も定義してますが、使い回すのがきれいな気がします。

    ・token の比較方法
    hash_equals() を使用したほうが良さげ。
    https://blog.tokumaru.org/2017/04/teratailcsrf.html

    ざっと見たところですが、参考まで。

    キャンセル

  • 2018/02/04 19:54

    ありがとうございます。参考にさせて頂きます。

    キャンセル

  • 2018/02/04 20:19 編集

    世の中には、「CSRF対策は、固定トークンでよくね?」って議論もよくあって、私も(要件によっては)それでイイ派なので、それを実装する時の参考記事を貼っておきます。
    https://qiita.com/mpyw/items/8f8989f8575159ce95fc

    キャンセル

  • 2018/02/04 21:56

    こちらも参考にさせて頂きます。

    キャンセル

+1

セッションのトークンを上書きした後でトークンチェックをやっちゃってますね。
トークンはポストで送られているし、セッション内のトークンもちゃんと残っているのに、チェックの直前で上書きしちゃダメですよ。
トークンの発行はチェック処理のIF文の後でないと。

投稿

  • 回答の評価を上げる

    以下のような回答は評価を上げましょう

    • 正しい回答
    • わかりやすい回答
    • ためになる回答

    評価が高い回答ほどページの上位に表示されます。

  • 回答の評価を下げる

    下記のような回答は推奨されていません。

    • 間違っている回答
    • 質問の回答になっていない投稿
    • スパムや攻撃的な表現を用いた投稿

    評価を下げる際はその理由を明確に伝え、適切な回答に修正してもらいましょう。

  • 2018/02/04 19:54

    ありがとうございます、もう一度見直してみます。

    キャンセル

同じタグがついた質問を見る

  • PHP

    24449questions

    PHPは、Webサイト構築に特化して開発されたプログラミング言語です。大きな特徴のひとつは、HTMLに直接プログラムを埋め込むことができるという点です。PHPを用いることで、HTMLを動的コンテンツとして出力できます。HTMLがそのままブラウザに表示されるのに対し、PHPプログラムはサーバ側で実行された結果がブラウザに表示されるため、PHPスクリプトは「サーバサイドスクリプト」と呼ばれています。

  • CSRF

    56questions

    クロスサイトリクエストフォージェリ (Cross site request forgeries、CSRF)は、 外部Webページから、HTTPリクエストによって、 Webサイトの機能の一部が実行されてしまうWWWにおける攻撃手法です。