初めて投稿させて頂きます。
初心者なので、初歩的な質問で申し訳ございません。
ユーザーが氏名を最大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ページの「アクティブ」「注目」タブのフィードに表示されにくくなります。
質問の評価を下げたことを取り消します
この機能は開放されていません
評価を下げる条件を満たしてません
質問の評価を下げる機能の利用条件
この機能を利用するためには、以下の事項を行う必要があります。
- 質問回答など一定の行動
-
メールアドレスの認証
メールアドレスの認証
-
質問評価に関するヘルプページの閲覧
質問評価に関するヘルプページの閲覧
+8
画面遷移の確認ですが、以下のようになっているのでしょうか?
(1) (2) (3) (4)
入力画面 ------> 氏名の重複チェック -------> 確認画面 -------> 登録処理 ------> 登録しました表示
POST リダイレクト POST リダイレクト
現在は、(1)のトークン確認はしているが、(2)ではトークン確認をしていないということですね。
結論から言えば、(2)でのトークン確認は必要ありません。そして、実は、(1)のトークン確認も必須ではありません。
本当にトークン確認が必要なのは(3)のみです。ここでトークンを確認しないと、「氏名をPOSTされると外部から氏名を登録されてしまいます」。
ついでに言えば、重複確認は登録処理でも(登録処理でこそ)必要ですが、やっていますか?そうしないと、同時に(1)→(2)の遷移を通ると、その時点では氏名は登録されていないので、その後(3)で重複した登録がなされます。DBの一意制約があれば大丈夫で、そうするべきですが。
投稿
-
回答の評価を上げる
以下のような回答は評価を上げましょう
- 正しい回答
- わかりやすい回答
- ためになる回答
評価が高い回答ほどページの上位に表示されます。
-
回答の評価を下げる
下記のような回答は推奨されていません。
- 間違っている回答
- 質問の回答になっていない投稿
- スパムや攻撃的な表現を用いた投稿
評価を下げる際はその理由を明確に伝え、適切な回答に修正してもらいましょう。
+2
直接的な回答にはなっていません^^;
フローを確認していないのですが、コードをざっと眺めた時に気になったことがあります。
このコード、ネットにあるコードを継ぎ接ぎして作ってませんか?
filter_input と $_POST が混在していたり、new PDO で、charset がなかったり、重複程度で die で出力し表示を崩してしまっていたり、いろいろチグハグです。。。
個人情報を取り扱うようなので、堅牢なコードがかける前提を整えたほうが良いです。フレームワークの適用や、適切なライブラリの選定を行ってみてはいかがでしょうか?
氏名の重複チェックが少しイレギュラーに見えますが、多分入力値の確認の一部に組み込めば、フレームワークやライブラリの用意する通常のフローで処理できると思います。
個人情報を扱うサイトをフルスクラッチで制作するのは、かなりしんどいと思います。再検討されることをオススメします。
投稿
-
回答の評価を上げる
以下のような回答は評価を上げましょう
- 正しい回答
- わかりやすい回答
- ためになる回答
評価が高い回答ほどページの上位に表示されます。
-
回答の評価を下げる
下記のような回答は推奨されていません。
- 間違っている回答
- 質問の回答になっていない投稿
- スパムや攻撃的な表現を用いた投稿
評価を下げる際はその理由を明確に伝え、適切な回答に修正してもらいましょう。
+2
ockeghem さんの回答と同じですが、解説を追加して書いてみます。
まず、すべてのセキュリティ対策に共通のことですが、対象となる脅威について具体的に考えて見る必要があります。
脅威
質問者様のサイトでは、CSRF攻撃によってユーザの意図しない氏名が登録されてしまう。
方法
以下の手順が成立すると攻撃が成功する
- ユーザが質問者様のサイトにログインする(ブラウザに Cookie がセットされる)
- 悪意のあるサイトの運営者がユーザに攻撃ページを開かせる
- 攻撃ページに隠しの form を埋め込みスクリプトで submit する→このフォームは氏名の登録を行うPOSTと同じ内容をPOSTであり、Cookieも付与されているので、質問者様のサイトがリクエストを受け入れてしまい、氏名が登録されてしまう
防御方法
氏名の登録を実行する form には隠し input タグでトークンを付与し、トークンが付与されていないものや不正なものはリクエストを受け入れないようにする
リダイレクトなどで複数の手順がある場合の対処方法
CSRF攻撃では単発のリクエストを発行することはできても、その結果を参照したり操作したりすることはできません。これはブラウザの仕様です。したがって、
2.「確認」ボタンを押下(自分に対して、トークンとhidden値「check」をPOST)
と同様のPOSTを悪意のあるページから発行することはできても、返ってきた確認画面の form をスクリプトなどから submit することはできません。
ですので、攻撃するのであれば、最後の確認画面からのPOSTのほうを真似して攻撃ページに埋め込むことになります。
CSRF攻撃では、悪意のあるページからリクエストの結果を参照したり操作したりできないので、一般的に参照しかしないリクエストは防御の必要はありません。
更新リクエストとなる最後の確認画面からのPOSTのみ防御すればよいということになります。
投稿
-
回答の評価を上げる
以下のような回答は評価を上げましょう
- 正しい回答
- わかりやすい回答
- ためになる回答
評価が高い回答ほどページの上位に表示されます。
-
回答の評価を下げる
下記のような回答は推奨されていません。
- 間違っている回答
- 質問の回答になっていない投稿
- スパムや攻撃的な表現を用いた投稿
評価を下げる際はその理由を明確に伝え、適切な回答に修正してもらいましょう。
15分調べてもわからないことは、teratailで質問しよう!
- ただいまの回答率 88.11%
- 質問をまとめることで、思考を整理して素早く解決
- テンプレート機能で、簡単に質問をまとめられる
2018/01/09 11:49
まさしくその通りで、説明の仕方の参考にもなります・・。ありがとうございます。
>(2)でのトークン確認は必要ありません。そして、実は、(1)のトークン確認も必須ではありません。
確かに、冷静に考えるとそうですね・・。
(1)、(2)では表示やチェックのみで、登録など発生しません。
目先のリダイレクトの疑問にとらわれて根本的なミスに気づいておりませんでした。
>ついでに言えば、重複確認は登録処理でも(登録処理でこそ)必要ですが、やっていますか?
ご指摘ありがとうございます。
上記に関しては、登録処理の直前に共に行っています。
―――――――――――――――――――――――――――――――――――
トークンチェックのタイミングとチェックさえきっちり行っていれば、
画面遷移的には、上記の画面遷移で問題ありませんでしょうか?
2018/01/24 09:01