質問するログイン新規登録
PHP

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

Q&A

解決済

1回答

862閲覧

CSRF対策でワンタイムトークンが上手くいかない

jacky-tom

総合スコア4

PHP

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

0グッド

0クリップ

投稿2023/06/12 07:49

0

0

実現したいこと

ワンタイムトークンを成功させたい

前提

管理者ユーザーシステムで、ユーザー登録する際にCSRF対策でワンタイムトークンの
チェックを試みています。
しかし、受け取り側でメッセージ「不正なリクエストです。処理を中断します..」
(失敗した際のメッセージ)が表示されます。

該当のソースコード

//送信元 <!doctype html> <html lang=ja> <head> <meta charset="utf-8"> <title>ユーザーメンテナンス(修正)</title> <meta name="description" content="ユーザーの修正画面"> <!--リセットcss--> <link rel="stylesheet" href="https://unpkg.com/ress/dist/ress.min.css"> <!--css--> <link href="css/ad_style.css" rel="stylesheet"> <!--レスポンシブ対応--> <meta name="viwport" content="width=device-width, initial-scale=1"> <!--Googleフォント--> <link href="https://fonts.googleapis.com/css2?family=Sawarabi+Gothic&display=swap" rel="stylesheet"> <link href="https://fonts.googleapis.com/css2?family=Shippori+Mincho:wght@500&display=swap" rel="stylesheet"> </head> <body> <h1>ユーザーメンテナンス(修正)</h1> <?php session_start(); function generateToken() { $bytes = openssl_random_pseudo_bytes(16); return bin2hex($bytes); } $token = generateToken(); $SESSION['token'] = $token; var_dump($SESSION['token']); try{ require_once("db_connect.php"); $pdo->setAttribute(PDO::ATTR_ERRMODE,PDO::ERRMODE_EXCEPTION); $stmt=$pdo->prepare('select * from user_table where id=?'); $stmt->execute([htmlspecialchars($_REQUEST['id'], ENT_QUOTES | ENT_HTML5, 'UTF-8')]); foreach($stmt->fetchAll() as $row) { echo '<form action="user_update_conf.php" method="post" class="usermente">'; echo '<input type="hidden" name="token" value="$token">'; echo '<input type="hidden" name="seq" value="', htmlspecialchars($row['id'], ENT_QUOTES | ENT_HTML5, 'UTF-8'), '" id = "seq" readonly=readonly>'; echo '<div id = "last_name">'; echo '<label for="sei">姓</label>'; echo '<input type="text" name="sei" value="', htmlspecialchars($row['last_name'], ENT_QUOTES | ENT_HTML5, 'UTF-8'), '" id = "sei">'; echo '</div>'; echo '<div>'; echo '<label for="mei">名</label>'; echo '<input type="text" name="mei" value="', htmlspecialchars($row['first_name'], ENT_QUOTES | ENT_HTML5, 'UTF-8'), '" id = "mei">'; echo '</div>'; echo '<div>'; echo '<label for="sei_kana">姓(カナ)</label>'; echo '<input type="text" name="sei_kana" value="', htmlspecialchars($row['last_name_kana'], ENT_QUOTES | ENT_HTML5, 'UTF-8'), '" id="sei_kana">'; echo '</div>'; echo '<div>'; echo '<label for="mei_kana">名(カナ)</label>'; echo '<input type="text" name="mei_kana" value="', htmlspecialchars($row['first_name_kana'], ENT_QUOTES | ENT_HTML5, 'UTF-8'), '" id="mei_kana">'; echo '</div>'; echo '<div>'; echo '<label for="user">ユーザー</label>'; echo '<input type="text" name="user" value="', htmlspecialchars($row['user'], ENT_QUOTES | ENT_HTML5, 'UTF-8'), '" id="user">'; echo '</div>'; echo '<div>'; echo '<label for="password">パスワード</label>'; echo '<input type="password" name="password" value="', htmlspecialchars($row['password'], ENT_QUOTES | ENT_HTML5, 'UTF-8'), '" id="password">'; echo '</div>'; echo '<div>'; echo '<label for="password2">パスワード(確認用)</label>'; echo '<input type="password" name="password2" value="', htmlspecialchars($row['password'], ENT_QUOTES | ENT_HTML5, 'UTF-8'), '" id="password2">'; echo '</div>'; } } catch(PDOException $e){echo "次がエラーの内容です。;" .$e->getMessage();} ?> <div class = "button_layout"> <a href="user_table.php" class="button_c">戻る</a> <input type="submit" class="button_b" name="button" id="button" value="確認"> </div> </form> <script> document.addEventListener('DOMContentLoaded', function() { document.getElementById('button').addEventListener('click', function(){ var error = document.getElementById('msg'); var sei = document.getElementById('sei'); var lastName = document.getElementById('last_name'); var seiValue = sei.value; console.log(error); console.log (seiValue); if(error === null&&seiValue === null || error === null&&seiValue === ""){ var msg = '入力必須項目です。'; var ptag = document.createElement('p'); ptag.id = 'msg'; var text = document.createTextNode(msg); ptag.appendChild(text); var lastName = document.getElementById('last_name'); lastName.appendChild(ptag); event.preventDefault(); } else if(error !== null&&seiValue === null || error !== null&&seiValue === ""){ lastName.removeChild(lastName.lastChild); var msg = '入力必須項目です。'; var ptag = document.createElement('p'); ptag.id = 'msg'; var text = document.createTextNode(msg); ptag.appendChild(text); var lastName = document.getElementById('last_name'); lastName.appendChild(ptag); event.preventDefault(); } },false) },false) </script> <noscript>JavaScriptが利用できません</noscript> </body> </html> //送信先 <?php session_start(); if (!isset($_SESSION['token']) || !isset($_POST['token']) || $_SESSION['token'] !== $_POST['token']) { die('不正なリクエストです。処理を中断します..'); } unset($_SESSION['token']); function escape($val) { return htmlspecialchars($val, ENT_QUOTES | ENT_HTML5, 'UTF-8'); } ?> <!doctype html> <html lang=ja> <head> <meta charset="utf-8"> <title>ユーザーメンテナンス(修正)</title> <meta name="description" content="ユーザーの修正確認画面"> <!--リセットcss--> <link rel="stylesheet" href="https://unpkg.com/ress/dist/ress.min.css"> <!--css--> <link href="css/ad_style.css" rel="stylesheet"> <!--レスポンシブ対応--> <meta name="viwport" content="width=device-width, initial-scale=1"> <!--Googleフォント--> <link href="https://fonts.googleapis.com/css2?family=Sawarabi+Gothic&display=swap" rel="stylesheet"> <link href="https://fonts.googleapis.com/css2?family=Shippori+Mincho:wght@500&display=swap" rel="stylesheet"> </head> <body> <h1>ユーザーメンテナンス(確認)</h1> <h2>下記でよろしいですか?</h2> <form action="user-update-commit.php" method="post" class="usermente_conf"> <input type="hidden" name="seq" value="<?php echo htmlspecialchars(escape($_POST['seq']),ENT_QUOTES,'UTF-8')?>" id = "seq" readonly=readonly> <div> <label for = "sei">姓</label> <input type="text" name="sei" value="<?php echo htmlspecialchars(escape($_POST['sei']),ENT_QUOTES,'UTF-8') ?>" id = "sei" readonly=readonly> </div> <div class=mei> <label for = "mei">名</label> <input type="text" name="mei" value="<?php echo htmlspecialchars(escape($_POST['mei']),ENT_QUOTES,'UTF-8') ?>" id = "mei" readonly=readonly> </div> <div> <label for = "sei_kana">姓(カナ)</label> <input type="text" name="sei_kana" value="<?php echo htmlspecialchars(escape($_POST['sei_kana']),ENT_QUOTES,'UTF-8') ?>" id="sei_kana" readonly=readonly> </div> <div> <label for = "mei_kana">名(カナ)</label> <input type="text" name="mei_kana" value="<?php echo htmlspecialchars(escape($_POST['mei_kana']),ENT_QUOTES,"UTF-8"); ?>" id="mei_kana" readonly=readonly> </div> <label for = "user">ユーザー</label> <input type="text" name="user" value="<?php echo htmlspecialchars(escape($_POST['user']),ENT_QUOTES,"UTF-8"); ?>" id="user" readonly=readonly> </div> <div> <label for = "password">パスワード</label> <input type="password" name="password" value="<?php echo htmlspecialchars(escape($_POST['password']),ENT_QUOTES,"UTF-8"); ?>" id="password" readonly=readonly> </div> <div> <label for = "password">パスワード(確認用)</label> <input type="password" name="password2" value="<?php echo htmlspecialchars(escape($_POST['password2']),ENT_QUOTES,"UTF-8"); ?>" id="password2" readonly=readonly> </div> <div class = "button_layout"> <button type="button" class="button_c" onclick="history.back()">戻る</button> <input class="button_b" type="submit" value="確認"> </div> </form> </body> </html>

試したこと

送信元でvar_dumpしたところ、$SESSION['token']は問題なさそうです。
$SESSION[’token’]が送信先に渡せてない気がします。
送信元のソースの
echo '<input type="hidden" name="token" value="$token">';
の記述が悪いのかもしれませんが、何が悪いのかもわからずです。

補足情報(FW/ツールのバージョンなど)

質問とは関係ありませんが、送信元ソースのjavascriptは作りかけです。

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

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

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

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

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

m.ts10806

2023/06/12 08:09

送信先でvar_dumpすれば"気がする"から抜けられるので、そちらを先に確認を。
jacky-tom

2023/06/12 08:39

コメントありがとうございます。 送信先でvar_dump($_SESSION)したら空でした。 var_dump($_SESSION['token'])だと「Warning: Undefined array key "token"」でした。 やっぱり送信先に渡ってないでしょうか。
m.ts10806

2023/06/12 20:11

https://www.php.net/manual/ja/function.session-start.php > クッキーに基づくセッションを使用している場合、ブラウザに何か出力を行う前に session_start() をコールする必要があります。 PHPタグ外は全て「出力」の扱いです。
guest

回答1

0

ベストアンサー

とりあえず気になる点
session_start();はあらゆる出力の前で宣言してください

//from.php

php

1<?php 2ob_flush(); 3session_start(); 4function generateToken() 5{ 6 $bytes = openssl_random_pseudo_bytes(16); 7 return bin2hex($bytes); 8} 9$token = generateToken(); 10$_SESSION['token'] = $token; 11var_dump($_SESSION['token']); 12?> 13<html> 14<body> 15・・・ 16<form action="to.php" method="post" class="usermente"> 17<input type="hidden" name="token" value="<?=$token?>"> 18<input type="submit" class="button_b" name="button" id="button" value="確認"> 19</form> 20・・・ 21</body> 22</html>

//to.php

PHP

1<?php 2ob_flush(); 3session_start(); 4print "session:".($_SESSION["token"]??null)."<br>"; 5print "post:".(filter_input(INPUT_POST,"token")??null)."<br>";

※調整版

投稿2023/06/12 08:14

編集2023/06/13 03:05
yambejp

総合スコア118024

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

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

jacky-tom

2023/06/12 08:58

回答ありがとうございます。 1点確認させてください。 あらゆる出力の前との事ですが、両方のソースでsession_start宣言してますが、それでは 不足しているという事でしょうか。 解釈違いだったら申し訳ありません。
yambejp

2023/06/12 10:36

> あらゆる出力の前 ソースに一部追記しておきましたが、あらゆる出力とはHTML文はもちろん、改行やスペースもNGです。 原則UTF8のBOMもNGなのでそれを前提にソースを書いてみてください
jacky-tom

2023/06/13 00:47

yambejpさん、m.ts10806さん ありがとうございます。 送信元ファイルで冒頭でsession_startを宣言するようにし、おまじないも記述しましたが、 エラーになります。 VAR_DUMPも「Warning: Undefined array key "token"」のままです。 他になにが考えられますか?
yambejp

2023/06/13 00:59

端的にいれば、質問者さんのtypoでしょうね $SESSION['token'] = $token; var_dump($SESSION['token']); 「$_SESSION」です
jacky-tom

2023/06/13 02:08

回答ありがとうございます。 $_SESSIONに書き換えたところ、var_dumpは「array(0) { }」でした。 やはり$tokenが渡せてないでしょうか。
yambejp

2023/06/13 03:11

質問者さんのソースを一部採用してfrom/toを書き換えました 質問者さんのソースに組み込まずに、そのままfrom.phpとto.phpというファイルに保存して テストしてみてください。 これでセッションデータが渡らないなら (1)セッションをPHP側で許可していない(phpinfo()で確認できる) (2)クッキーをブラザで制限している など可能性があります
jacky-tom

2023/06/13 03:45

わざわざテスト用のソースまでご用意いただき、大変恐縮です。 テストしてみたところ、セッションデータは渡りました。 (session、ポストがきちんと表示されました。) なので、 >(1)セッションをPHP側で許可していない(phpinfo()で確認できる) >(2)クッキーをブラザで制限している こちらではなく、ソースに問題があるのでしょうか?
yambejp

2023/06/13 03:59

そうですね。そうなると最初の指摘のとおり、 session_start()をソースのなるべく先頭の方で宣言した上で、 $_SESSIONへのデータ投入も同様に先頭の方で対応ください もともと質問者さんのtypoが1番の原因ですので、ほかでもボーンヘッドがあるかもしれませんね
jacky-tom

2023/06/13 09:33

送信元ファイルで 1.session_startを最初に記述 2.下記の通り修正 $SESSION⇒$_SESSION "$token"⇒"<?=$token?>" の2点を行いましたが、結果は同じでした。 他も確認しましたが、見つけられませんでした。
yambejp

2023/06/13 10:03 編集

たとえば echo '<input type="hidden" name="token" value="$token">'; これは全然ダメです。 hiddenのtokenの値には「$token」という文字列が挿入されているはずです。 post値はまず渡らないですね またformの始まりはループの中にあるので何度も<form・・・は貼ってあって ループ内で閉じることなく</form>が最後あるだけなのでHTMLとして成立していないと思います。 全体的に雑すぎる書き方なので今のソースに組み込もうとせずに、 一から書き直してみてください
jacky-tom

2023/06/16 05:06

回答ありがとうございます。 ソースを見直して解決しました。
guest

あなたの回答

tips

太字

斜体

打ち消し線

見出し

引用テキストの挿入

コードの挿入

リンクの挿入

リストの挿入

番号リストの挿入

表の挿入

水平線の挿入

プレビュー

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

ただいまの回答率
85.30%

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

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

質問する

関連した質問