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

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

ただいまの
回答率

90.84%

  • PHP

    18674questions

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

  • MySQL

    5368questions

    MySQL(マイエスキューエル)は、TCX DataKonsultAB社などが開発するRDBMS(リレーショナルデータベースの管理システム)です。世界で最も人気の高いシステムで、オープンソースで開発されています。MySQLデータベースサーバは、高速性と信頼性があり、Linux、UNIX、Windowsなどの複数のプラットフォームで動作することができます。

  • セキュリティー

    421questions

    このタグは、コンピューターシステムの安全性やデータの機密性に関連したトピックの為に使われます。

  • リダイレクト

    107questions

    プログラムの入力元や出力先を通常とは別の場所に転送させることをリダイレクトと呼びます。

[php]CSRF対策している場合、リダイレクトはNGでしょうか?

受付中

回答 3

投稿 編集

  • 評価
  • クリップ 2
  • VIEW 771

mariko705

score 2

初めて投稿させて頂きます。
初心者なので、初歩的な質問で申し訳ございません。

ユーザーが氏名を最大50個まで入力し、確認ボタンを押すと、内容確認画面(入力値一覧)に遷移する処理を書いています。

会員式のシステムなので、今までログインいていることのみをチェックしておりましたが、
今回初めてCRSF対策でトークンを発行して、チェックが必要なページに対して入れています。

そこで今回迷っているのが、氏名が既に登録されているかどうかのチェックをDBに対して行うのですが、現状以下の様な流れとなっています。

1.氏名を入力
2.「確認」ボタンを押下(自分に対して、トークンとhidden値「check」をPOST)
3.if (isset($_POST['check']) { でifの中に入り、その中でDB既存チェック
4.エラーならdie('エラー');
5.OKなら確認画面へリダイレクト
といった感じです。

この時、リダイレクト先へトークンを渡せない為に、確認画面に対して氏名をPOSTされると外部から氏名を登録されてしまいます。
やはりこの場合は、確認画面でチェックして、エラーだと「エラーメッセージ+入力画面に戻る」のみ表示させるべきなのでしょうか?
別画面に遷移⇒戻る よりも、そのページ内でチェックして弾いてあげるほうが使いやすいかと思い悩んでおります。

説明が下手ですみません。
以下はソースです。

【input.php】
<?php 
    // セッション開始
    session_start();

    <!-- チェッククラスの読み込み -->
    include_once('check.php'); 

    // ログイン状態チェック + CSRF対策
    if (    ( !isset($_SESSION["NAME"]) ) || 
        ( !CsrfValidator::funcTokenValidate(filter_input(INPUT_POST, 'token')))  ) {  ←トークンのチェックです
        // ログイン画面へリダイレクト
        header("Location: login.php");
    }

    if ( isset($_POST['check']) ) {
        チェックフラグがセットされている場合

        $UserName = 入力された氏名を配列として格納();

        //------------------
        // 氏名既存チェック
        //------------------
        $pdo = new PDO(DSN, ユーザー名, パスワード, array(PDO::ATTR_ERRMODE=>PDO::ERRMODE_EXCEPTION));

        $stmt = $pdo->prepare("SELECT NAME FROM 氏名テーブル WHERE NAME = ?");
        foreach ( $UserName as $name ) {
            $stmt->execute(array($name));

            //チェック
            if ( $row = $stmt->fetch(PDO::FETCH_ASSOC) ) {
                // 既に登録された氏名の場合
                die("{$row['NAME']}は既に登録されています。");
            }
        }

        // 確認フォームへ
        header("Location: confirm.php");  // この時、トークンを渡せない
    }
    ?>

<!doctype html>
<html>
<head>
<meta charset="utf-8">
<title>入力画面</title>
</head>

<body>
    <!---------------- 
     入力フォーム
    ----------------->
    <div class="form_wrapper cont">
        <form action="menber_add_conf.php" method="post" name="frmAdd" onSubmit="return 入力チェック()">
            <table class="formTable_v">
                <tbody>
                    <tr>
                        <th>
                            氏名
                        </th>
                    </tr>
                    <!-- この下に入力領域を50個用意 -->
                            ・・・
                            ・・・
                            ・・・
                            ・・・
                </tbody>
            </table>
            <!-- トークンのセット -->
            <?=CsrfValidator::funcSetToken()?> <!-- このセットが、リダイレクトではできないですよね・・? -->
                        <!-- チェックフラグのセット -->
            <input type="hidden" name="check" value="check">

            <button type="submit" id="submit">確認画面へ</button>
        </form>
    </div>
</body>
</html>

一応連携しておきますが、トークンチェックに関するクラスは以下の通りです。

【check.php】
    class CsrfValidator {

        // ハッシュ用のアルゴリズムをセット(SHA256)
        const HASH_ALGO = 'sha256';

        //=======================================
        // トークン生成処理
        //=======================================
        private static function Generate()
        {
            if (session_status() === PHP_SESSION_NONE) {
                throw new \BadMethodCallException('Session is not active.');
            }
            return hash(self::HASH_ALGO, session_id());
        }

        //=======================================
        // トークンチェック処理
        //=======================================
        public static function Validate($token, $throw = false)
        {
            $success = self::funcTokenGenerate() === $token;
            if (!$success && $throw) {
                throw new \RuntimeException('CSRF validation failed.', 400);
            }
            return $success;
        }

        //=======================================
        // トークンセット処理
        //=======================================
        public static function SetToken() {
            return '<input type="hidden" name="token" value="' . self::funcTokenGenerate() . '">';    
        }
    }

わかりにくくて申し訳ございませんが、ご指摘の程宜しくお願いいたします。

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

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

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

    クリップを取り消します

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

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

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

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

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

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

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

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

    質問の評価を下げる

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

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

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

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

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

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

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

    詳細な説明はこちら

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

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

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

回答 3

+7

画面遷移の確認ですが、以下のようになっているのでしょうか?

          (1)                        (2)              (3)             (4)
入力画面 ------> 氏名の重複チェック -------> 確認画面 -------> 登録処理 ------> 登録しました表示
         POST                    リダイレクト         POST            リダイレクト

現在は、(1)のトークン確認はしているが、(2)ではトークン確認をしていないということですね。
結論から言えば、(2)でのトークン確認は必要ありません。そして、実は、(1)のトークン確認も必須ではありません。
本当にトークン確認が必要なのは(3)のみです。ここでトークンを確認しないと、「氏名をPOSTされると外部から氏名を登録されてしまいます」。
ついでに言えば、重複確認は登録処理でも(登録処理でこそ)必要ですが、やっていますか?そうしないと、同時に(1)→(2)の遷移を通ると、その時点では氏名は登録されていないので、その後(3)で重複した登録がなされます。DBの一意制約があれば大丈夫で、そうするべきですが。

投稿

  • 回答の評価を上げる

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

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

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

  • 回答の評価を下げる

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

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

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

  • 2018/01/09 11:49

    画面遷移のフロー、とてもわかりやすいです。
    まさしくその通りで、説明の仕方の参考にもなります・・。ありがとうございます。

    >(2)でのトークン確認は必要ありません。そして、実は、(1)のトークン確認も必須ではありません。
    確かに、冷静に考えるとそうですね・・。
    (1)、(2)では表示やチェックのみで、登録など発生しません。

    目先のリダイレクトの疑問にとらわれて根本的なミスに気づいておりませんでした。

    >ついでに言えば、重複確認は登録処理でも(登録処理でこそ)必要ですが、やっていますか?
    ご指摘ありがとうございます。
    上記に関しては、登録処理の直前に共に行っています。

    ―――――――――――――――――――――――――――――――――――

    トークンチェックのタイミングとチェックさえきっちり行っていれば、
    画面遷移的には、上記の画面遷移で問題ありませんでしょうか?

    キャンセル

  • 2018/01/24 09:01

    画面遷移は特に問題ありませんが、(2)でリダイレクトするのは冗長な気もします。問題があるとまでは言えませんが

    キャンセル

+2

直接的な回答にはなっていません^^;

フローを確認していないのですが、コードをざっと眺めた時に気になったことがあります。
このコード、ネットにあるコードを継ぎ接ぎして作ってませんか?
filter_input と $_POST が混在していたり、new PDO で、charset がなかったり、重複程度で die で出力し表示を崩してしまっていたり、いろいろチグハグです。。。

個人情報を取り扱うようなので、堅牢なコードがかける前提を整えたほうが良いです。フレームワークの適用や、適切なライブラリの選定を行ってみてはいかがでしょうか?

氏名の重複チェックが少しイレギュラーに見えますが、多分入力値の確認の一部に組み込めば、フレームワークやライブラリの用意する通常のフローで処理できると思います。

個人情報を扱うサイトをフルスクラッチで制作するのは、かなりしんどいと思います。再検討されることをオススメします。

投稿

  • 回答の評価を上げる

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

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

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

  • 回答の評価を下げる

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

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

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

  • 2018/01/09 12:04

    大変わかりやすくご回答いただきまして、ありがとうございます!

    >ネットにあるコードを継ぎ接ぎ
    ソースまでしっかりチェックして頂きましてありがとうございます・・。
    その通りです^^;

    たとえば
    >filter_input と $_POST が混在していたり
    など、なぜこの参考サイトは「filter_input」を使用しているのか・・・などをしっかりできていないまま引用したので、ここを$_POSTで統一させて良いのか、ここを変更するとこのサンプルは動かなくなるのか・・などで迷っているうちに「触らぬ神にたたりなし」で放っておいてしまっている箇所が結構あります・・。
    現状成功している動作が崩れるのを恐れての事ですが、そもそもよくわからないで引用している時点でいつか想定外の処理で止まることは時間の問題ですよね・・。

    すでにこの低レベルなスクラッチでいろいろ作ってしまっているのですが、、
    一点幸いなのはこうやって作っているうちに一つ一つの意味を理解し始めているので、チグハグな箇所についてはそろそろ修正(チグハグを統一)していけそうです。

    >フレームワークの適用や、適切なライブラリの選定を行ってみてはいかがでしょうか?
    >氏名の重複チェックが少しイレギュラーに見えますが、多分入力値の確認の一部に組み込めば、フレームワークやライブラリの用意する通常のフローで処理できると思います。
    やはりそうですか。
    個人で趣味+勉強で作っているので、とりあえず勉強も兼ねてスクラッチで作ってますが、
    一通りやりたいことが実装できれば、今度はフレームワーク使用Verに改造する勉強をしてみようと考えております。

    ちなみにte2ji様のご経験上、私の現状(スキルやシステム内容)にオススメのライブラリやフレームワークはございますでしょうか?
    ※フレームワーク開発の経験がなく、本を買って勉強し始めるレベルだと思います・・。
    なるべく易しいものがいいです・・^^;

    キャンセル

  • 2018/01/09 12:28

    私自身は、コードレベルでのセキュリティを担保する自信がないので、個人情報を扱う案件は手を出してません。
    セキュリティ設計の時点で、個人情報を取り扱わないように設計しています。

    自身のスキルに合格点が与えられるようになるまでは、個人情報の取扱をやめるか、キチンと識者にコードレビューしてもららわないと危険です。

    また、フレームワークですが、私自身は素の php に近い感覚で書けるため、CodeIgniter を使用してきましたが、会員管理システムを作るなら別のフレームワークを使用すると思うので、オススメを紹介できません。
    実務で会員管理を行っている方からアドバイスを貰うほうが良いです。

    キャンセル

+2

ockeghem さんの回答と同じですが、解説を追加して書いてみます。
まず、すべてのセキュリティ対策に共通のことですが、対象となる脅威について具体的に考えて見る必要があります。

脅威

質問者様のサイトでは、CSRF攻撃によってユーザの意図しない氏名が登録されてしまう。

方法

以下の手順が成立すると攻撃が成功する

  1. ユーザが質問者様のサイトにログインする(ブラウザに Cookie がセットされる)
  2. 悪意のあるサイトの運営者がユーザに攻撃ページを開かせる
  3. 攻撃ページに隠しの form を埋め込みスクリプトで submit する→このフォームは氏名の登録を行うPOSTと同じ内容をPOSTであり、Cookieも付与されているので、質問者様のサイトがリクエストを受け入れてしまい、氏名が登録されてしまう

防御方法

氏名の登録を実行する form には隠し input タグでトークンを付与し、トークンが付与されていないものや不正なものはリクエストを受け入れないようにする

リダイレクトなどで複数の手順がある場合の対処方法

CSRF攻撃では単発のリクエストを発行することはできても、その結果を参照したり操作したりすることはできません。これはブラウザの仕様です。したがって、

2.「確認」ボタンを押下(自分に対して、トークンとhidden値「check」をPOST)

と同様のPOSTを悪意のあるページから発行することはできても、返ってきた確認画面の form をスクリプトなどから submit することはできません。
ですので、攻撃するのであれば、最後の確認画面からのPOSTのほうを真似して攻撃ページに埋め込むことになります。
CSRF攻撃では、悪意のあるページからリクエストの結果を参照したり操作したりできないので、一般的に参照しかしないリクエストは防御の必要はありません。
更新リクエストとなる最後の確認画面からのPOSTのみ防御すればよいということになります。

投稿

  • 回答の評価を上げる

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

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

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

  • 回答の評価を下げる

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

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

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

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

  • ただいまの回答率 90.84%
  • 質問をまとめることで、思考を整理して素早く解決
  • テンプレート機能で、簡単に質問をまとめられる

関連した質問

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

  • PHP

    18674questions

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

  • MySQL

    5368questions

    MySQL(マイエスキューエル)は、TCX DataKonsultAB社などが開発するRDBMS(リレーショナルデータベースの管理システム)です。世界で最も人気の高いシステムで、オープンソースで開発されています。MySQLデータベースサーバは、高速性と信頼性があり、Linux、UNIX、Windowsなどの複数のプラットフォームで動作することができます。

  • セキュリティー

    421questions

    このタグは、コンピューターシステムの安全性やデータの機密性に関連したトピックの為に使われます。

  • リダイレクト

    107questions

    プログラムの入力元や出力先を通常とは別の場所に転送させることをリダイレクトと呼びます。