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

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

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

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

Q&A

解決済

2回答

1259閲覧

PHPでのトークンの仕込むことによって、CSRF攻撃を防御することについて

Maruco2321

総合スコア118

PHP

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

0グッド

0クリップ

投稿2021/10/26 21:51

編集2021/10/28 08:05

###質問内容
macのVScodeで$_SESSION["token"]を利用した簡単な掲示板をドットインストールの動画をお手本に作っています。
テーマが
「トークンの仕込むことによって、CSRF攻撃を防御する」
とのことなのですが、上手くいきません。

<編集部分>
初期画面はこのようになっており
イメージ説明
Postボタンでレスlt。php画面に行くとこのようになり
イメージ説明
Go backボタンで文字が保存されるといった感じになっています。
イメージ説明

//////////

コードを何度も見比べたのですが、間違っている場所がわかりません。
フォルダ、ファイルは下のような感じです。
イメージ説明

自分で試してみたところ
"../app/functions.php"ファイルを少しいじってみると
if分の条件となっている場所について、
empty($_SESSION["token"]) || $_SESSION["token"] !== filter_input(INPUT_POST, "token")
の場合はどんな値でも、全て
validateToken()のexit("Invalid post request")によるInvalid post requestが下のように表示されてしまいますが
イメージ説明
条件部分を empty($_SESSION["token"])のみに変えてみると
上手く機能することから
$_SESSION["token"] !== filter_input(INPUT_POST, "token")
の部分が間違っているのだと思います。
さらに"../app/index.php"をブラウザにあげた初期画面について検証してみると
イメージ説明
のドラッグ部分にあるように
編集前:value
編集後:index.phpの5行目辺り、createToken()

が上手く機能してることも考えると条件部分の
filter_input(INPUT_POST, "token")が上手く前のindex.phpファイルからデータを所得していない?のかと思います
ただそうだとしても間違っている箇所は分かりませんでした。よろしくお願いします。
下に自分のコードとドットインストールのコード比較機能画面を載せておきたいと思います。

###自分のコード
INDEX.PHP

PHP

1 <?php 2 require("../app/functions.php"); 3 createToken(); 4 define("FILENAME","../app/messages.txt"); 5 6 if($_SERVER["REQUEST_METHOD"] === "POST"){ 7 validateToken(); 8 $message = trim(filter_input(INPUT_POST, "message")); 9 $message = $message !== "" ? $message : "..."; 10 $fp = fopen(FILENAME, "a"); 11 fwrite($fp,$message."\n" ); 12 fclose($fp); 13 14 header("Location: http://localhost:8080/result.php"); 15 exit; 16} 17 $messages = file(FILENAME, FILE_IGNORE_NEW_LINES); 18 include("../app/_parts/_header.php"); 19?> 20 21<ul> 22 <?php foreach($messages as $message): ?> 23 <li><?= h($message); ?> </li> 24 <?php endforeach; ?> 25</ul> 26 27<form action="" method="post"> 28 <input type="text" name="message"> 29 <button>Post</button> 30 <input type="hidden" name="token" value="<?= h($_SESSION["token"]); ?> "> 31</form> 32 33 34<?php 35 36include("../app/_parts/_footer.php"); 37コード

functions.php

PHP

1<?php 2 3 4 function h($str){ 5 return htmlspecialchars($str, ENT_QUOTES,"UTF-8"); 6 } 7 8 function createToken(){ 9 if(!isset($_SESSION["token"])){ 10 $_SESSION["token"] = bin2hex(random_bytes(32)); 11 } 12 } 13 14 function validateToken(){ 15 if( 16 empty($_SESSION["token"]) || $_SESSION["token"] !== filter_input(INPUT_POST, "token") 17 ){ 18 exit("Invalid post request"); 19 } 20 } 21 22 session_start(); 23コード

PHP

1<?php 2 require("../app/functions.php"); 3 4 5 include("../app/_parts/_header.php"); 6?> 7<p>Message added!</p> 8<p><a href="index.php">Go back</a></p> 9 10<?php 11 12include("../app/_parts/_footer.php"); 13コード

###dottoinstallとのコード比較写真
イメージ説明
下はresult.php
イメージ説明
イメージ説明

###解決方法、追記
あまり慣れない作業なので時間結構かかりましたが、分かりました笑
結論から言うと
<input type="hidden" name="token" value="<?= h($_SESSION["token"]); ?> ">
の?> ">の部分を?>"> (空白をなくす)ことで解決しました。つまり
<input type="hidden" name="token" value="<?= h($_SESSION["token"]); ?> ">
<input type="hidden" name="token" value="<?= h($_SESSION["token"]); ?>">(最後のみ変化)
をすることで上手く機能します。
あまり良くない記述ですが
value="<?= h($_SESSION["token"]); ?>"
のようにダブルクォーテーションを連続で使ったとしても、一様機能的には問題ありませんでした
尚、今回の場合はドットインストールの比較対象と自分の間違いコードを入れたり外したり、繋いだりすることでデバック(?)をやっていきました

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

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

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

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

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

guest

回答2

0

ベストアンサー

勘やお手本との比較だけでデバッグするのには限界があるので
PHP デバッグ 方法
あたりで検索して、デバッグ方法を知り、デバッグしてみるのが良いかと思いますよ。
出来れば
VSCode xdebug ブレークポイントで調べて環境を構築し、PHPを途中で止めてその時点での変数の状態を確認出来るようにすることをお勧めします。

また、お手本との比較で

  • シングルクォートとダブルクォートが違っていたり(両者には機能上の差があります)
  • ファイルの先頭にコメント(スクリーンショット用に付け加えたものだと思いますが、動作させる場合には問題になります。)

などの差異があり、第三者からみてお手本と同じとは判断できない状態です。

原因解明の流れ

今回のケースだと、まずはvalidate()の判定部分の直前で$_POST$_SESSIONの中身を確認し、それぞれ想定通りの内容になっているか確認してみて下さい。(var_dump()でのデバッグはセッションやcookieの動作に影響が出るので、あまりお勧めはしませんが、手軽に確認することが可能です)
どちらかが想定通りになっていないはずなので、それぞれの値を作成している処理のところに遡って確認という感じで確認を進めると良いかと思います。

PHP

1 function validateToken(){ 2 var_dump($_SESSION); 3 var_dump($_POST); 4 5 if( 6 empty($_SESSION["token"]) || $_SESSION["token"] !== filter_input(INPUT_POST, "token") 7 ){ 8 exit("Invalid post request"); 9 } 10 }

また、前述の通り、完全にお手本通りのファイルになっていないので、

  1. 全ファイルをお手本からコピーして動作確認
  2. 1ファイルだけ書き換えて動作確認
  3. 問題が無ければ1に戻って次のファイルを書き換えて動作確認

という感じで、問題が発生しているファイルを特定するのも、今回のケースでは有効に思います。

投稿2021/10/27 07:53

tanat

総合スコア18727

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

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

Maruco2321

2021/10/27 23:36

回答有難うございます。上記のデバックの方法などのいずれかで解決することはとりあえずできそうなので試してみたいと思います。
tanat

2021/10/28 02:29

見通しが立って良かったです。 解決したフィードバックを頂けると嬉しいです
Maruco2321

2021/10/28 08:03

あまり慣れない作業なので時間結構かかりましたが、分かりました笑 結論から言うと <input type="hidden" name="token" value="<?= h($_SESSION["token"]); ?> "> の?> ">の部分を?>"> (空白をなくす)ことで解決しました。つまり <input type="hidden" name="token" value="<?= h($_SESSION["token"]); ?> "> <input type="hidden" name="token" value="<?= h($_SESSION["token"]); ?>">(最後から約3文字目のみ変化) をすることで上手く機能します。 あまり良くない記述ですが value="<?= h($_SESSION["token"]); ?>" のようにダブルクォーテーションを連続で使ったとしても、一様機能的には問題ありませんでした デバックの方はもう少し時間をかけて物にしていきたいと思います。有難うございました????‍♂️
tanat

2021/10/28 08:18 編集

フィードバックありがとうございます。 確かに、スペースが入っていると値が変わりますね。 原因が分かってよかったです。 ダブルクォートについては、value="<?= h($_SESSION["token"]); ?>"のようなケースでは特に問題無いかなと思います。(<?= で区切られているので問題が起きません) シングルクォートとダブルクォートの挙動の違いは https://www.php.net/manual/ja/language.types.string.php https://teratail.com/questions/127209 あたりが参考になるかと思います。
guest

0

ちょっと質問の意図がわかりかねますが
$_SESSION["token"]と$_POST["token"]を比較するということは
初期状態では$_SESSION["token"]が空でしょうからマッチすることはないと思いますが・・・
別ページであらかじめセッションのトークンを埋め込んでおくのでしょうか?

sample

PHP

1<?PHP 2session_start(); 3$_SESSION["token"]="hoge";//他のページから来てもOK 4if(!empty($_SESSION["token"]) and $_SESSION["token"] === filter_input(INPUT_POST, "token")){ 5print "ok"; 6}; 7?> 8<form method="post"> 9<input type="text" name="token" value="<?=htmlspecialchars($_SESSION["token"])?>"> 10<input type="submit" value="send"> 11</form>

投稿2021/10/27 06:24

編集2021/10/27 06:58
yambejp

総合スコア116724

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

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

Maruco2321

2021/10/27 06:48

質問有難うございます。index.phpの3行目辺りのcreateToken()のアクションによってfunctions.phpのアクションを呼び出し、初期画面(index.php)を呼び出した際に自動で$_SSESION["token"]が作られるようにしています。それが機能しているため上の3枚目の写真でvalueが16進数の文字で書かれているといった感じになっています。
yambejp

2021/10/27 06:57

つまりワンタイムパスワードがセッションに保持されるとともにformのパラメータにも渡るということでよろしいですか? であれば、セッションとpostの比較で行けると思いますが・・・
Maruco2321

2021/10/27 07:07

CSRF攻撃を防御することはできるのですが例えば「たまご」とポストで送ったとしても現状 Invalid post request の文字が表示されてしまいます。
guest

あなたの回答

tips

太字

斜体

打ち消し線

見出し

引用テキストの挿入

コードの挿入

リンクの挿入

リストの挿入

番号リストの挿入

表の挿入

水平線の挿入

プレビュー

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

ただいまの回答率
85.35%

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

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

質問する

関連した質問