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

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

新規登録して質問してみよう
ただいま回答率
85.48%
POST

POSTはHTTPプロトコルのリクエストメソッドです。ファイルをアップロードしたときや入力フォームが送信されたときなど、クライアントがデータをサーバに送る際に利用されます。

PHP

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

HTML

HTMLとは、ウェブ上の文書を記述・作成するためのマークアップ言語のことです。文章の中に記述することで、文書の論理構造などを設定することができます。ハイパーリンクを設定できるハイパーテキストであり、画像・リスト・表などのデータファイルをリンクする情報に結びつけて情報を整理します。現在あるネットワーク上のほとんどのウェブページはHTMLで作成されています。

Q&A

解決済

3回答

15302閲覧

PHP タブブラウザでの二重POST対策について

退会済みユーザー

退会済みユーザー

総合スコア0

POST

POSTはHTTPプロトコルのリクエストメソッドです。ファイルをアップロードしたときや入力フォームが送信されたときなど、クライアントがデータをサーバに送る際に利用されます。

PHP

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

HTML

HTMLとは、ウェブ上の文書を記述・作成するためのマークアップ言語のことです。文章の中に記述することで、文書の論理構造などを設定することができます。ハイパーリンクを設定できるハイパーテキストであり、画像・リスト・表などのデータファイルをリンクする情報に結びつけて情報を整理します。現在あるネットワーク上のほとんどのウェブページはHTMLで作成されています。

0グッド

3クリップ

投稿2017/05/18 02:51

以下のような流れで、PHPにてPOST処理の二重送信対策を行っています。

1.画面表示時に、ランダムな文字列でトークンを発行。
2.発行したトークンをセッション変数に格納。
3.フォームのhidden要素に発行したトークンをセット。
4.POSTされた時に、「POSTされたhidden要素のトークン」と、「セッションに格納されているトークン」を比較。
5.両者が一致していた場合は、処理を行う。

具体的なコードは、以下の通りです。

(page.php?id=xxxx)

PHP

1<?php 2//POSTされたhidden要素のトークンを取得 3$post_token = isset($_POST['token']) ? $_POST['token'] : ''; 4 5//セッションに格納されたトークンを取得 6$session_token = isset($_SESSION['token']) ? $_SESSION['token'] : ''; 7 8//セッションに格納されたトークンを破棄 9unset($_SESSION['token']); 10 11//POSTされたhidden要素のトークンとセッションに格納されているトークンを比較 12if($post_token != '' && $session_token == $post_token){ 13 //トークン一致 14 //(ここで登録処理) 15}else{ 16 //トークン無し or トークン不一致 17 //(処理キャンセル) 18} 19 20//二重送信対策用トークンを発行 21$token = rtrim(base64_encode(openssl_random_pseudo_bytes(32)),'='); 22 23//トークンをセッションに格納 24$_SESSION['token'] = $token; 25 26?> 27 28<!DOCTYPE html> 29<html lang="ja"> 30<head>xxxx記事詳細</head> 31<body> 32 33<form method="POST"> 34<input type="text" name="comment"> 35<input type="hidden" name="token" value="<?php echo $token;?>"> 36<input type="submit" value="送信"> 37</form> 38 39</body> 40</html>

以上のコードで、同画面上では正常に二重送信対策が出来たのですが、タブブラウザで複数タブを開いた時に問題がありました。

例えば、ブログの「記事一覧ページ」にアクセスし、最初に「各記事ページ(page.php?id=xxx)」を複数タブで開いておいて、それぞれのタブで記事にコメントしたい、というケースがあると思います。

理想は、タブ毎に二重送信をチェックにしたいのですが、上記コードだと新しく記事詳細ページを開く度に、セッションに格納するトークンが更新されてしまいます。

そうすると、最後に開いたタブ以外のタブでは、hidden要素にセットしたトークンとセッションに格納したトークンが相違してしまう事になり、二重送信とみなされてしまいます。

対策として、以下の二つを考えました。

1.$_SESSION[$page_id][$token]のような形で、ページ毎にトークンを格納する。
(懸念点)ページを開くたびにトークンが追加され、セッション変数が肥大化してしまう。

2.POST先を別画面にし、別画面での処理後に元の画面にリダイレクトさせる。
別画面ではトークンチェックは行わず、POST判定、リファラー判定のみを行う。

(懸念点)ページリロード対策にはなるが、根本的な二重POST対策にはなっていない。

※javascript側で、submitされた時にsubmitボタンをDisable化し、二重リクエスト自体は送られないようにはしております。

他にベストな対策方法がありましたら、教えていただけませんでしょうか。

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

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

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

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

guest

回答3

0

使ったことないけど、メモリンクに入ってました。

より高度なCSRF対策 – URI個別にバリデーションする方法

これ、応用するとイケると思います。

** 追記 **
他には、ユーザ毎の固定 token でもイケルと思う。

投稿2017/05/18 02:57

編集2017/05/18 03:01
退会済みユーザー

退会済みユーザー

総合スコア0

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

退会済みユーザー

退会済みユーザー

2017/05/18 03:09

ご回答ありがとうございます! いただいたURLを拝見しましたが、非常にためになる情報でした。 トークンの有効範囲がURIに限られるので、セキュリティも高められるかなと思いました。 一度熟読して試してみたいと思います。
guest

0

ベストアンサー

がると申します。

何となく、拝見していて「CSRF対策」の応用でいけるのかなぁ、と思いました。
個人的に、CSRF対策は「ある程度のタブを許容する」作りにする事が多いので、以下のような手法がありますが、如何でしょうか?
少し長いコードで恐縮ですが、コメントなどを手掛かりにしていただければ、と思います。

入力Page(tokenを作るところあたり:HTMLへの埋め込みは適宜)

PHP

1 // CSRF用のトークンの作成と設定 2 $csrf_token = ''; 3 try { 4 // XXX random_bytesはPHP7以降の関数だが、PHP5.2以降で使えるユーザランド実装( https://github.com/paragonie/random_compat )が存在する 5 if(function_exists('random_bytes')) { 6 $csrf_token = hash('sha512', random_bytes(128)); 7 } else if (is_readable('/dev/urandom')) { 8 $csrf_token = hash('sha512', file_get_contents('/dev/urandom', false, NULL, 0, 128), false); 9 } else if(function_exists('openssl_random_pseudo_bytes')) { 10 $csrf_token = hash('sha512', openssl_random_pseudo_bytes(128)); 11 } 12 } catch (Exception $e) { 13 ; // XXX 後でまとめてエラーチェックするので一端ここでは未処理 14 } 15 if ('' === $csrf_token) { 16 echo 'CSRFトークンが作成できないので終了します'; 17 exit; // XXX 18 } 19 // CSRFトークンは5個まで(で後で追加するので、ここでは4個以下に) 20 while (5 <= count(@$_SESSION[$type]['csrf_token'])) { 21 array_shift($_SESSION[$type]['csrf_token']); 22 } 23 // セッションに格納 24 $_SESSION[$type]['csrf_token'][$csrf_token] = time(); 25 26 // XXX この後、$csrf_tokenを、適宜、htmlのinput:hiddenに埋め込む

処理Page

PHP

1// CSRFチェック 2// tokenの存在確認(check exist) 3$posted_token = (string)@$_POST['csrf_token']; 4if (false === isset($_SESSION['csrf_token'][$posted_token])) { 5 // tokenが無いんでエラー 6 // XXX 適宜エラー処理。ここでは一端「問答無用でexit」 7 exit; 8} else { 9 // tokenの寿命確認(check life) 10 $ttl = $_SESSION['csrf_token'][$posted_token]; 11 if (time() >= $ttl + 60) { 12 // token作成から60秒以上経過しているのでNG 13 $error_detail['error_csrf_timeover'] = true; 14 } 15 // いずれにしてもtokenは1回しか使えないので、消す 16 unset($_SESSION['csrf_token'][$posted_token]); 17}

上述ですと
・タブは5つまで有効
・タブをひらいてから1分以内だけ有効
という縛りなので。
必要に応じて、CSRFトークンの所持数や時間チェックを緩くしてやる事で、ある程度、意図に近い事が出来そうに思いますが如何でしょうか?

以上、何かの参考にでもなれば幸いです。

投稿2017/05/18 14:35

gallu

総合スコア506

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

0

そうすると、最後に開いたタブ以外のタブでは、hidden要素にセットしたトークンとセッションに格納したトークンが相違してしまう事になり、二重送信とみなされてしまいます。

これで仕様でいいのではないかと思います。
逆に、タブ開かないと成り立たないような構造にしているのでしょうか?

投稿2017/05/18 02:56

yoorwm

総合スコア1305

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

退会済みユーザー

退会済みユーザー

2017/05/18 03:03

ご回答ありがとうございます。 システムの要件にもよりますが、ユーザーの使いやすさを考えた時に、例えば以下のようなケースが発生すると思います。 ・カートシステムで、商品詳細ページを複数開いておいて、それぞれのタブでカートに入れる ・SNSシステムで、メッセージ送受信ページを相手ごとに開いて、それぞれのタブでメッセージを送りたい ・グルメサイトで、店舗ページを複数タブで開いて、それぞれのページでお気に入り登録したい 以上のようなケースですと、タブ毎に二重送信対策をした方が良いと思い、質問させていただきました。
guest

あなたの回答

tips

太字

斜体

打ち消し線

見出し

引用テキストの挿入

コードの挿入

リンクの挿入

リストの挿入

番号リストの挿入

表の挿入

水平線の挿入

プレビュー

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

ただいまの回答率
85.48%

質問をまとめることで
思考を整理して素早く解決

テンプレート機能で
簡単に質問をまとめる

質問する

関連した質問