🎄teratailクリスマスプレゼントキャンペーン2024🎄』開催中!

\teratail特別グッズやAmazonギフトカード最大2,000円分が当たる!/

詳細はこちら
PHP

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

Q&A

解決済

2回答

3573閲覧

$_SESSION['token']と$_POST['token']が一致しない。 PHP CSRF対策

Yuto921

総合スコア11

PHP

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

0グッド

0クリップ

投稿2019/09/08 00:10

前提・実現したいこと

[PHP]
CSRF対策

セッションを使って、CSRF対策をしたい。

POSTを押すと、セットされた$_SESSION['token']が新しくなり、$_POST['token']と一致しなくなってしまいます。
$_SESSION['token'] === $_POST['token']を一致させたい。

PHPで簡易的なログインシステムを作っています。
セッション機能を使って、サインアップが押された時に、事前に仕込んであったtokenとPOSTされたtokenを比較する実装をしていて、以下のエラーメッセージが発生しました。

(やりたいことは、読み込み時に、セットしたtokenがPOSTされたtokenと等しかったらOK,等しくなかったらエラーを返すというような処理をしたい。)

発生している問題・エラーメッセージ

Invalid Token!

sign up 前 (サインアップボタンを押す前)

php

1SESSION:token: 66b33407b289f53ad8934224ed071922 2POST:token:

sign up 後 (サインアップボタンを押した後)

php

1SESSION:token: c56356defa3a206e97c3278243e490b1 2POST:token: 66b33407b289f53ad8934224ed071922 3Invalid Token!

該当のソースコード

↓ Controller.php

php

1<?php 2require_once(__DIR__ . '/Controller/Signup.php'); 3 4class Controller 5{ 6 // エラー情報を格納する変数 7 // private $_errors; 8 9 public function __construct() 10 { 11 // セッションを使ってCSRF対策 12 // session_start(); 13 $_SESSION['token'] = bin2hex(openssl_random_pseudo_bytes(16)); 14 echo 'SESSION:token: ' . $_SESSION['token']; 15 echo '<br>'; 16 echo 'POST:token: ' . $_POST['token']; 17 echo '<br>'; 18 } 19 protected function isLoggedIn() 20 { 21 // ログイン判定 22 // ログイン時にセッションにmeというキーで情報を保持->判定 23 return isset($_SESSION['me']) && !empty($_SESSION['me']); 24 } 25}

↓ Signup.php

php

1<?php 2require_once(__DIR__ . '/../Controller.php'); 3require_once(__DIR__ . '/../Exception/InvalidEmail.php'); 4require_once(__DIR__ . '/../Exception/InvalidPassword.php'); 5 6class Signup extends Controller 7{ 8 public function run() 9 { 10 // ログインしていたら、ホームに飛ばす 11 // フォームがもしポストされたら、postProcess() 12 13 14 // ログインしていたら、ホームに飛ばす 15 if ($this->isLoggedIn()) { 16 // login URLは定数管理 17 header('Location: index.php'); 18 exit; 19 } 20 21 // フォームがもしポストされたら 22 if ($_SERVER['REQUEST_METHOD'] == 'POST') { 23 $this->postProcess(); 24 } 25 } 26 27 protected function postProcess() 28 { 29 // データの検証 30 // ユーザー作成 31 // 登録処理 32 33 // validate 34 // Controllerにエラーオブジェクトを持たせて、値をセット/取得できるようにする 35 try { 36 $this->_validate(); 37 } catch (\InvalidEmail $e) { 38 echo $e->getMessage(); 39 exit; 40 41 // emailのキーでエラーメッセージを渡す 42 // $this->setErrors('email', $e->getMessage()); 43 44 } catch (\InvalidPassword $e) { 45 echo $e->getMessage(); 46 exit; 47 48 // passwordのキーでエラーメッセージを渡す 49 // $this->setErrors('password', $e->getMessage()); 50 } 51 52 // echo 'Success'; 53 // exit; 54 55 // if ($this->hasError()) { 56 // return; 57 // } else { 58 // ユーザー作成 59 // 登録処理 60 // } 61 62 63 } 64 65 private function _validate() 66 { 67 //セッションの検証 68 if ($_POST['token'] !== $_SESSION['token']) { 69 echo "Invalid Token!"; 70 exit; 71 } 72 73 // filter_var関数を使って、OKじゃなかったら例外を返す 74 if(!filter_var($_POST['email'], FILTER_VALIDATE_EMAIL)) { 75 throw new InvalidEmail(); 76 } 77 78 // preg_matchの正規表現で調べる 79 if(!preg_match('/\A[a-zA-Z0-9]+\z/', $_POST['password'])) { 80 throw new InvalidPassword(); 81 } 82 } 83 84}

↓ signup.php

php

1<?php 2 3// 新規登録 4 5require_once(__DIR__ . '/../config/config.php'); 6require_once(__DIR__ . '/../lib/Controller/Signup.php'); 7require_once(__DIR__ . '/../lib/Controller.php'); 8require_once(__DIR__ . '/../lib/functions.php'); 9 10 11// このビューに表示するデータをControllerから引っ張ってくる 12// Controllerのインスタンスを作る 13$app = new Signup(); 14 15 16// html部分にデータを表示する 17$app->run(); 18 19?> 20 21<!DOCTYPE html> 22<html lang="ja"> 23<head> 24 <meta charset="UTF-8"> 25 <meta name="viewport" content="width=device-width, initial-scale=1.0"> 26 <meta http-equiv="X-UA-Compatible" content="ie=edge"> 27 <title>Sign Up</title> 28 <link rel="stylesheet" href="styles.css"> 29</head> 30<body> 31 <div id="container"> 32 <form action="" method="POST" id="signup"> 33 <p> 34 <input type="text" name="email" placeholder="email"> 35 </p> 36 <p> 37 <input type="password" name="password" placeholder="password"> 38 </p> 39 <div class="btn" onclick="document.getElementById('signup').submit();">Sign Up</div> 40 <p class="fs12"><a href="login.php">Log In</a></p> 41 <input type="hidden" name="token" value="<?php echo h($_SESSION['token']); ?>"> 42 </form> 43 </div> 44 45 46</body> 47</html>

試したこと

echoなどを使って、$_SESSION['token']と$_POST['token']を確認してみたら、
$_SESSION['token']がサインアップボタンを押すたびに新しくトークンが作成されているため、  $_SESSION['token']=$_POST['token'] が永遠と一致せずにエラーになってしまうことがわかりました。

しかし、なぜ$_SESSION['token']がポストするたびに変更されてしまうのかわからないです。

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

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

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

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

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

guest

回答2

0

$_SESSION['token'] の変化を追ってみると良いです。
適当に echo でもデバッグでもしてみてください。

$_SESSION['token']への代入を正しい箇所で行えば意図した挙動を得ることができます。

投稿2019/09/08 00:15

退会済みユーザー

退会済みユーザー

総合スコア0

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

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

Yuto921

2019/09/08 00:24

ありがとうございます。 $_SESSION['token']の位置は、全体の共通処理をするController.phpに書いており、実際の画面表示であるsingup.phpの方で、Singup()クラスをインスタンス化した時に、セットしています。echo などで確認したところ、singupのボタンを押した時に、$_SESSION['token']が変化していることが確認できました。 $_SESSION['token']への代入箇所は共通処理のController.phpのままにしたいので、どこかでsessionを削除するなどの処理を書いた方がいいのでしょうか?
guest

0

自己解決

echoなどでみてみると、signupボタンを押すたびに新しくsessionがセットされてしまうとのことだったので、

sessionをセットする際に、すでにセットされていたらsessionをセットしないようにisset関数で$_SESSION['token']の有無をif文にしました。

php

1 if (!isset($_SESSION['token'])) { 2 $_SESSION['token'] = bin2hex(openssl_random_pseudo_bytes(16)); 3 }

これで、singup時に、$_SESSION['token']==$_POST['token']が等しくなったので解決です。

投稿2019/09/08 00:42

Yuto921

総合スコア11

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

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

退会済みユーザー

退会済みユーザー

2019/09/08 02:05

その仕組だと、ほぼ固定になりますが理解して記述していますか?
Yuto921

2019/09/08 03:13

先ほど、確認してみました。確かに、リロードしてもセットされたトークンが変わってないことに気づきました。これだと、CSRF対策としてやはり不十分ですよね。。。。先ほどの$_SESSION['token']への代入箇所を正しい箇所で行うには、どのあたりに記述するべきでしょうか?
退会済みユーザー

退会済みユーザー

2019/09/08 07:39

> $_SESSION['token']への代入箇所を正しい箇所で行うには、どのあたりに記述するべきでしょうか? 回答に書いたとおり、$_SESSION['token'] の変化を追ってみると良いです。自身のコードの挙動が理解できない限りセキュアなコードにはなりません。 > CSRF対策としてやはり不十分ですよね。。。。 まぁ実はそれほど問題のある状況でも無いです。session id が固定されるわけではないので、token を固定することは直接脆弱性には繋がりません。 ただ、私が書くのであれば、session id をハッシュ化した値を token として使用するかなぁ。
guest

あなたの回答

tips

太字

斜体

打ち消し線

見出し

引用テキストの挿入

コードの挿入

リンクの挿入

リストの挿入

番号リストの挿入

表の挿入

水平線の挿入

プレビュー

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

ただいまの回答率
85.36%

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

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

質問する

関連した質問