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

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

ただいまの
回答率

90.76%

  • PHP

    19188questions

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

  • CSRF

    43questions

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

CSRF対策について(PHP)

解決済

回答 2

投稿

  • 評価
  • クリップ 1
  • VIEW 318

gsuisk

score 61

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にレコードを追加する処理は別のファイルに書いたほうが良いのでしょう?

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

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

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

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

    クリップを取り消します

  • 良い質問の評価を上げる

    以下のような質問は評価を上げましょう

    • 質問内容が明確
    • 自分も答えを知りたい
    • 質問者以外のユーザにも役立つ

    評価が高い質問は、TOPページの「注目」タブのフィードに表示されやすくなります。

    質問の評価を上げたことを取り消します

  • 評価を下げられる数の上限に達しました

    評価を下げることができません

    • 1日5回まで評価を下げられます
    • 1日に1ユーザに対して2回まで評価を下げられます

    質問の評価を下げる

    teratailでは下記のような質問を「具体的に困っていることがない質問」、「サイトポリシーに違反する質問」と定義し、推奨していません。

    • プログラミングに関係のない質問
    • やってほしいことだけを記載した丸投げの質問
    • 問題・課題が含まれていない質問
    • 意図的に内容が抹消された質問
    • 広告と受け取られるような投稿

    評価が下がると、TOPページの「アクティブ」「注目」タブのフィードに表示されにくくなります。

    質問の評価を下げたことを取り消します

    この機能は開放されていません

    評価を下げる条件を満たしてません

    評価を下げる理由を選択してください

    詳細な説明はこちら

    上記に当てはまらず、質問内容が明確になっていない質問には「情報の追加・修正依頼」機能からコメントをしてください。

    質問の評価を下げる機能の利用条件

    この機能を利用するためには、以下の事項を行う必要があります。

回答 2

checkベストアンサー

+1

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

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

setToken()

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

<?php
setToken();
?>

<form ~~>
~~~
<input type="hidden" name="token" value="<?php echo $_SESSION['token'] ?>"> 
~~~
</form>


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

checkToken()

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

checkToken();

$errors = [];
if (($_POST["name"]==="")||($_POST["sex"]==="")||($_POST["comment"]==="")){
~~


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

投稿

  • 回答の評価を上げる

    以下のような回答は評価を上げましょう

    • 正しい回答
    • わかりやすい回答
    • ためになる回答

    評価が高い回答ほどページの上位に表示されます。

  • 回答の評価を下げる

    下記のような回答は推奨されていません。

    • 間違っている回答
    • 質問の回答になっていない投稿
    • スパムや攻撃的な表現を用いた投稿

    評価を下げる際はその理由を明確に伝え、適切な回答に修正してもらいましょう。

  • 2017/09/15 15: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;
    }

    キャンセル

  • 2017/09/15 15:27

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

    キャンセル

  • 2017/09/15 15:41

    わかりました!ありがとうございます。

    ちなみにCSRF対策のテストは、この場合どうすればできますか?

    調べたら、「サイトに設置したフォームのPOSTリクエストの送信先のURLにフォーム以外から、必要なパラメータに任意の値が設定されたPOSTリクエストを送る」

    とあるのですがよくわかりません...

    キャンセル

  • 2017/09/15 18:58 編集

    input.phpとは別ファイルでフォームを作り、actionにinsert.phpを設定してsubmitします。
    トークンが存在しない別セッションの方が望ましいので、正常系をテストする確認ブラウザとは別の種類のブラウザでテストすると良いでしょう(同じブラウザの別タブとかだと同セッションなので)
    あとはajaxでinsert.phpに対してpost送信してみるのもいいですね。これも別フォームで。

    あとは簡易テストツールを作っている方がいるようですので、それを利用してもいいかもしれません。
    https://blog.shimabox.net/2013/10/25/csrf_test_tool/

    キャンセル

  • 2017/09/15 20:41

    別のファイルからactionで飛ばしてみたところ、正常に動作(ブロック)されました。

    ありがとうございます。

    キャンセル

0

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

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

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

投稿

  • 回答の評価を上げる

    以下のような回答は評価を上げましょう

    • 正しい回答
    • わかりやすい回答
    • ためになる回答

    評価が高い回答ほどページの上位に表示されます。

  • 回答の評価を下げる

    下記のような回答は推奨されていません。

    • 間違っている回答
    • 質問の回答になっていない投稿
    • スパムや攻撃的な表現を用いた投稿

    評価を下げる際はその理由を明確に伝え、適切な回答に修正してもらいましょう。

  • 2017/09/15 15:19

    input.phpの <!DOCTYPE html> の上に

    <?php
    session_start();
    setToken();
    ?>

    と書いて、フォーム内に

    <input type="hidden" name="token" value="<?php echo $_SESSION['token'] ?>">

    と書く。insert.phpの冒頭では

    session_start();
    checkToken();

    と書く。これで大丈夫ですよね?

    キャンセル

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

  • ただいまの回答率 90.76%
  • 質問をまとめることで、思考を整理して素早く解決
  • テンプレート機能で、簡単に質問をまとめられる

関連した質問

同じタグがついた質問を見る

  • PHP

    19188questions

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

  • CSRF

    43questions

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