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

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

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

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

CSRF

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

Q&A

解決済

2回答

1110閲覧

CSRF対策について(PHP)

gsuisk

総合スコア72

PHP

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

CSRF

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

0グッド

1クリップ

投稿2017/09/15 04:40

CSRF対策でトークンの設定と確認をどこで行えばよいのかわかりません。

input.htmlで、名前と性別とコメントを入力し送信すると、insert.phpへと飛び、入力チェック等を行った後にDBと接続して入力データをレコードに追加します。

input.html

<!DOCTYPE html> <html lang="ja"> <head> <meta charset="utf-8"> <title>入力画面</title> </head> <body> <form action="insert.php" method="post"> お名前: <input type="text", name="name" required> <br> 性別: <select name="sex" required> <option value="男">男</option> <option value="女">女</option> </select> <br> コメント: <textarea name="comment" cols="25" rows="4" maxlength="100" required></textarea> <br> <input type="submit" value="送信"> </form> </body> </html>

insert.phpは以下です。checkEncodingとescapeはユーザー定義関数です。

<?php $back = "input.html"; // 文字エンコードの検証 if (!checkEncoding($_POST)){ header("Location:{$back}"); exit(); } $errors = []; if (($_POST["name"]==="")||($_POST["sex"]==="")||($_POST["comment"]==="")){ $errors[] = "空です。"; }else { $name = escape($_POST["name"]); $sex = escape($_POST["sex"]); $comment = escape($_POST["comment"]); } if (count($errors) > 0){ echo "<ol>"; foreach ($errors as $value) { echo "<li>", $value , "</li>"; } echo "</ol>"; echo "<hr>"; echo "<a href=", $back, ">戻る</a>"; exit(); } $user = 'user'; $password = 'password'; $dbName = 'db'; $host = 'localhost'; $dsn = "mysql:host={$host};dbname={$dbName};charset=utf8"; ?> <!DOCTYPE html> <html lang="ja"> <head> <meta charset="utf-8"> <title>レコード追加</title> </head> <body> <?php try { $pdo = new PDO($dsn, $user, $password); $pdo->setAttribute(PDO::ATTR_EMULATE_PREPARES, false); $pdo->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION); $sql = "INSERT INTO member (name, sex, comment) VALUES (:name, :sex, :comment)"; $stm = $pdo->prepare($sql); $stm->bindValue(':name', $name, PDO::PARAM_STR); $stm->bindValue(':sex', $sex, PDO::PARAM_STR); $stm->bindValue(':comment', $comment, PDO::PARAM_STR); $stm->execute(); echo "投稿ありがとうございました。"; } catch (Exception $e) { echo $e->getMessage(); } ?> </body> </html>

この場合だとどこにsetToken()とcheckToken()を入れればよいでしょうか?

トークンの関数作ってみました。(もしこれが安全でなければそれも教えてください。)

function setToken(){ $bytes=openssl_random_pseudo_bytes(16); $token=bin2hex($bytes); $_SESSION['token']=$token; } function checkToken(){ if(empty($_SESSION['token']) || ($_SESSION['token']) != $_POST['token']){ echo "不正なPOSTが行われました"; exit(); } }

投稿前にsetToken()し、投稿後にcheckToken()すると聞いたのですが、ページ遷移している必要はありますか?
「投稿ありがとうございました」と表示する画面と、DBにレコードを追加する処理は別のファイルに書いたほうが良いのでしょう?

ご回答いただけたら幸いです。

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

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

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

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

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

guest

回答2

0

ベストアンサー

投稿前にsetToken()し、投稿後にcheckToken()する

ちょっと表現が違うように思います。(特に後者)
「投稿後」だとDB登録も全て終わった後だという風に捉えられます
正しくは
「入力画面生成時にsetToken()で生成したトークンをフォーム内に埋め込み、
入力内容をPOSTした際の入力チェックの冒頭でcheckToken()する」です。

setToken()

入力フォームにhiddenで置いておくのがよくあるやり方なので、
入力画面のhiddenに対して利用すると良いです。

php

1<?php 2setToken(); 3?> 4 5<form ~~> 6~~~ 7<input type="hidden" name="token" value="<?php echo $_SESSION['token'] ?>"> 8~~~ 9</form>

今は入力画面がhtmlになっているのでphpにする必要があります。

checkToken()

他の入力チェックより前に実行します。

php

1checkToken(); 2 3$errors = []; 4if (($_POST["name"]==="")||($_POST["sex"]==="")||($_POST["comment"]==="")){ 5~~

checkEncoding()が$_POSTに対してどのような処理をしているか分かりませんが、
checkEncoding()より前でも良いかもしれません。

投稿2017/09/15 05:06

m.ts10806

総合スコア80850

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

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

gsuisk

2017/09/15 06:17

ありがとうございます! ちなみにcheckEncoding()は以下です。checkToken()は前でも後でもいいですよね!? function checkEncoding(array $data){ $result = true; foreach ($data as $key => $value) { if (is_array($value)){ $value = implode("", $value); } if (!mb_check_encoding($value)){ $result = false; break; } } return $result; }
m.ts10806

2017/09/15 06:27

んーそもそも必要そうではない処理のように思います。 あったとしても、不正なPOSTを捕捉する方が重要度としては高いので、 先にcheckToken()した方が良いと思います。
gsuisk

2017/09/15 06:41

わかりました!ありがとうございます。 ちなみにCSRF対策のテストは、この場合どうすればできますか? 調べたら、「サイトに設置したフォームのPOSTリクエストの送信先のURLにフォーム以外から、必要なパラメータに任意の値が設定されたPOSTリクエストを送る」 とあるのですがよくわかりません...
m.ts10806

2017/09/15 09:59 編集

input.phpとは別ファイルでフォームを作り、actionにinsert.phpを設定してsubmitします。 トークンが存在しない別セッションの方が望ましいので、正常系をテストする確認ブラウザとは別の種類のブラウザでテストすると良いでしょう(同じブラウザの別タブとかだと同セッションなので) あとはajaxでinsert.phpに対してpost送信してみるのもいいですね。これも別フォームで。 あとは簡易テストツールを作っている方がいるようですので、それを利用してもいいかもしれません。 https://blog.shimabox.net/2013/10/25/csrf_test_tool/
gsuisk

2017/09/15 11:41

別のファイルからactionで飛ばしてみたところ、正常に動作(ブロック)されました。 ありがとうございます。
guest

0

今回の場合、入れるところがありません。

input.htmlを表示するタイミングでトークンを作成します。

なので、input.phpにして、トークン作成と、hiddenタグ(token)に作成したトークンをセットしてください。

投稿2017/09/15 04:58

momon-ga

総合スコア4820

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

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

gsuisk

2017/09/15 06:19

input.phpの <!DOCTYPE html> の上に <?php session_start(); setToken(); ?> と書いて、フォーム内に <input type="hidden" name="token" value="<?php echo $_SESSION['token'] ?>"> と書く。insert.phpの冒頭では session_start(); checkToken(); と書く。これで大丈夫ですよね?
guest

あなたの回答

tips

太字

斜体

打ち消し線

見出し

引用テキストの挿入

コードの挿入

リンクの挿入

リストの挿入

番号リストの挿入

表の挿入

水平線の挿入

プレビュー

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

ただいまの回答率
85.48%

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

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

質問する

関連した質問