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

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

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

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

CSRF

クロスサイトリクエストフォージェリ (Cross site request forgeries、CSRF)は、 外部Webページから、HTTPリクエストによって、 Webサイトの機能の一部が実行されてしまうWWWにおける攻撃手法です。

Q&A

解決済

2回答

589閲覧

PHP CSRF対策について

yyyyyk

総合スコア13

PHP

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

CSRF

クロスサイトリクエストフォージェリ (Cross site request forgeries、CSRF)は、 外部Webページから、HTTPリクエストによって、 Webサイトの機能の一部が実行されてしまうWWWにおける攻撃手法です。

1グッド

0クリップ

投稿2018/02/03 17:39

編集2018/02/03 17:45

初めまして。
現在PHPを勉強していて、クイズサイトを作成しています。
問題の編集画面にて、CSRFの対策を取ろうと思っているのですがうまくいきません。

PHP

1<?php 2 3session_start(); 4 5//変数宣言 6$num = $_GET['num']; 7$msg = ''; 8 9//セッションがセットされていない場合の処理 10if(!isset($_SESSION['NAME'])) { 11 12 header("Location: Login.php"); 13 exit(); 14 15} 16 17//管理者権限以外がページにアクセスした場合の処理 18if($_SESSION['kengen'] != 'kanri') { 19 20 header("Location: main.php"); 21 exit(); 22 23} 24 25//GETで受け取った値が不正だった場合の処理 26if(!preg_match("/^[0-9]+$/", $num)) { 27 28 header("Location: error.php"); 29 exit(); 30 31} 32 33 //GETで受け取ったNumのデータを引っ張る 34 try{ 35 36 //データベースから問題を引っ張る 37 $DB = new PDO('mysql:host=localhost;dbname=question;charset=utf8','root',''); 38 $stmt = $DB->query("SELECT * FROM question WHERE num=".$num); 39 $row = $stmt->fetch(PDO::FETCH_ASSOC); 40 41 } catch(PDOException $e) { 42 43 $err_msg = 'データベースエラー'; 44 45 } 46 47 //更新機能 48 if(isset($_POST['update'])) { 49 50 //token発行 51 $token = sha1(uniqid(mt_rand(), true)); 52 $_SESSION['token'] = $token; 53 54 //CSRF対策 55 if($_SESSION['token'] != $_POST['token']) { 56 57 header("Location: error.php"); 58 exit(); 59 60 } 61 62 $title = $_POST['title']; 63 $content = $_POST['content']; 64 $question1 = $_POST['question1']; 65 $question2 = $_POST['question2']; 66 $question3 = $_POST['question3']; 67 $answer = $_POST['answer']; 68 69 70 if(!empty($title)) { 71 72 try{ 73 74 $DB = new PDO('mysql:host=localhost;dbname=question;charset=utf8','root',''); 75 $stmt = $DB->prepare("UPDATE question set title = :title WHERE num =".$num); 76 $stmt->bindParam(':title', $title, PDO::PARAM_STR); 77 $stmt->execute(); 78 79 } catch(PDOException $e) { 80 81 $err_msg = 'データベースエラー'; 82 83 } 84 85 } 86 87 if(!empty($content)) { 88 89 try{ 90 91 $DB = new PDO('mysql:host=localhost;dbname=question;charset=utf8','root',''); 92 $stmt = $DB->prepare("UPDATE question set content = :content WHERE num =".$num); 93 $stmt->bindParam(':content', $content, PDO::PARAM_STR); 94 $stmt->execute(); 95 96 } catch(PDOException $e) { 97 98 $err_msg = 'データベースエラー'; 99 100 } 101 102 } 103 104 if(!empty($question1)) { 105 106 try{ 107 108 $DB = new PDO('mysql:host=localhost;dbname=question;charset=utf8','root',''); 109 $stmt = $DB->prepare("UPDATE question set question1 = :question1 WHERE num =".$num); 110 $stmt->bindParam(':question1', $question1, PDO::PARAM_STR); 111 $stmt->execute(); 112 113 } catch(PDOException $e) { 114 115 $err_msg = 'データベースエラー'; 116 117 } 118 119 } 120 121 if(!empty($question2)) { 122 123 try{ 124 125 $DB = new PDO('mysql:host=localhost;dbname=question;charset=utf8','root',''); 126 $stmt = $DB->prepare("UPDATE question set question2 = :question2 WHERE num =".$num); 127 $stmt->bindParam(':question2', $question2, PDO::PARAM_STR); 128 $stmt->execute(); 129 130 } catch(PDOException $e) { 131 132 $err_msg = 'データベースエラー'; 133 134 } 135 136 } 137 138 if(!empty($question3)) { 139 140 try{ 141 142 $DB = new PDO('mysql:host=localhost;dbname=question;charset=utf8','root',''); 143 $stmt = $DB->prepare("UPDATE question set question3 = :question3 WHERE num =".$num); 144 $stmt->bindParam(':question3', $question3, PDO::PARAM_STR); 145 $stmt->execute(); 146 147 } catch(PDOException $e) { 148 149 $err_msg = 'データベースエラー'; 150 151 } 152 153 } 154 155 if(!empty($answer)) { 156 157 try{ 158 159 $DB = new PDO('mysql:host=localhost;dbname=question;charset=utf8','root',''); 160 $stmt = $DB->prepare("UPDATE question set answer = :answer WHERE num =".$num); 161 $stmt->bindParam(':answer', $answer, PDO::PARAM_STR); 162 $stmt->execute(); 163 164 } catch(PDOException $e) { 165 166 $err_msg = 'データベースエラー'; 167 168 } 169 170 } 171 172 173 $msg = '更新しました'; 174 175 } 176 177 178 try{ 179 180 //データベースから問題を引っ張る 181 $DB = new PDO('mysql:host=localhost;dbname=question;charset=utf8','root',''); 182 $stmt = $DB->query("SELECT * FROM question WHERE num=".$num); 183 $row = $stmt->fetch(PDO::FETCH_ASSOC); 184 185 } catch(PDOException $e) { 186 187 $err_msg = 'データベースエラー'; 188 189 } 190 191 192 193 194 195 196?> 197 198 199 200<!DOCTYPE html> 201<html> 202<head> 203 <title>問題編集</title> 204</head> 205<body> 206 <h3>問題編集</h3> 207 <p><?php echo $msg; ?></p> 208 <?php var_dump($_SESSION['token']); ?> 209 <form method="POST" action=""> 210 タイトル:<input type="text" name="title" value="" placeholder="<?php echo htmlspecialchars($row['title'], ENT_QUOTES); ?>"> 211 <br> 212 クイズ内容:<input type="text" name="content" value="" placeholder="<?php echo htmlspecialchars($row['content'], ENT_QUOTES); ?>"> 213 <br> 214 質問1:<input type="text" name="question1" value="" placeholder="<?php echo htmlspecialchars($row['question1'], ENT_QUOTES); ?>"> 215 <br> 216 質問2:<input type="text" name="question2" value="" placeholder="<?php echo htmlspecialchars($row['question2'], ENT_QUOTES); ?>"> 217 <br> 218 質問3:<input type="text" name="question3" value="" placeholder="<?php echo htmlspecialchars($row['question3'], ENT_QUOTES); ?>"> 219 <br> 220 回答:<input type="text" name="answer" value="" placeholder="<?php echo htmlspecialchars($row['answer'], ENT_QUOTES); ?>"> 221 <br> 222 <input type="hidden" name="token" value="<?php echo $_SESSION['token']; ?>"> 223 <input type="submit" name="update" value="変更"> 224 </form> 225 <a href="main.php">戻る</a> 226</body> 227</html>

問題箇所としては、//更新処理 の頭です。
現状、token自体は合っていると思うのですが変更ボタンを押下するとエラーページに遷移してしまいます。
宜しければご教示願います。

SatoshiMashino👍を押しています

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

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

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

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

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

guest

回答2

0

ベストアンサー

//token発行

$token = sha1(uniqid(mt_rand(), true));
$_SESSION['token'] = $token;
//CSRF対策
if($_SESSION['token'] != $_POST['token']) {

token の発行と token の検証の順番を考えてみてください。

あと、試してないけど、SQL インジェクションが可能かと。

投稿2018/02/04 00:34

退会済みユーザー

退会済みユーザー

総合スコア0

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

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

退会済みユーザー

退会済みユーザー

2018/02/04 03:30 編集

SQL インジェクションの指摘はおかしいですね^^; if(!preg_match("/^[0-9]+$/", $num)) で見てましたね。 なんか抜け道がありそうな気がしますが、私の知ってる範囲では大丈夫そうです。失礼しました。 *ただ、prepare でバインドしたほうが良いと思います。 あと、どこまで指摘するか微妙ですが、軽く補足しておきます。 *以下のコメントは、新しめの php を使っている前提です。(7系からかなぁ。。。) ・token に関して 暗号論的にセキュアな関数を使用することが推奨されます。 最近の php だと、random_bytes() や random_int() 等 ・GET や POST が空の時のフィルタ 空だと warning が出るので、入力値の確認をしたほうが良いです。 ・$DB に関して 何度も定義してますが、使い回すのがきれいな気がします。 ・token の比較方法 hash_equals() を使用したほうが良さげ。 https://blog.tokumaru.org/2017/04/teratailcsrf.html ざっと見たところですが、参考まで。
yyyyyk

2018/02/04 10:54

ありがとうございます。参考にさせて頂きます。
退会済みユーザー

退会済みユーザー

2018/02/04 11:20 編集

世の中には、「CSRF対策は、固定トークンでよくね?」って議論もよくあって、私も(要件によっては)それでイイ派なので、それを実装する時の参考記事を貼っておきます。 https://qiita.com/mpyw/items/8f8989f8575159ce95fc
yyyyyk

2018/02/04 12:56

こちらも参考にさせて頂きます。
guest

0

セッションのトークンを上書きした後でトークンチェックをやっちゃってますね。
トークンはポストで送られているし、セッション内のトークンもちゃんと残っているのに、チェックの直前で上書きしちゃダメですよ。
トークンの発行はチェック処理のIF文の後でないと。

投稿2018/02/04 01:56

SatoshiMashino

総合スコア210

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

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

yyyyyk

2018/02/04 10:54

ありがとうございます、もう一度見直してみます。
guest

あなたの回答

tips

太字

斜体

打ち消し線

見出し

引用テキストの挿入

コードの挿入

リンクの挿入

リストの挿入

番号リストの挿入

表の挿入

水平線の挿入

プレビュー

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

ただいまの回答率
85.50%

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

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

質問する

関連した質問