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

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

ただいまの
回答率

89.12%

【PHP】簡易掲示板の作成①

解決済

回答 3

投稿 編集

  • 評価
  • クリップ 2
  • VIEW 2,927

tnk_fuku

score 42

PHP初心者です。

PHP,MySQLにて簡易掲示板を作成しています。

ファイルは
index.php(投稿一覧表示、新規投稿フォーム)
confirm.php(投稿内容確認画面)
complete.php(書き込み完了画面)
(DB接続、エスケープ処理はそれぞれ外部ファイル(db_info.php,encode.php)を作成)
という構成です。

現在の問題としてはcomplete.phpにて「書き込みが完了しました」と表示されますが、index.phpに戻ると投稿が表示されないことです。

ソースは下記です。

index.php

<?php
require_once('db_info.php');
require_once('encode.php');
try {
  $pdo = new PDO($dsn, $user, $pass);
  $pdo->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
} catch (PDOExeption $e){
  $error = 'Unable to connect to the database server: ' . $e->getMessage();
  include 'error.php';
  exit();
}
try {
    $sql = "select * from bbsdata order by day desc";
    $result = $pdo->query($sql);
} catch (PDOExeption $e) {
    $error = 'Error fetching threads:' . $e->getMessage();
    include 'error.php';
    exit();    
}
echo <<<eot1
<!DOCTYPE html>
<html lang="ja">
<head>
    <meta charset="UTF-8">
    <title>簡易掲示板</title>
    <style type="text/css">
      body {
        background-color: #E1EDFF;
        line-height: 1.5rem;
      }
    </style>
</head>
<body>
  <h1>簡易掲示板</h1>
  <form action="confirm.php" method="post">
    名前:<input type="text" name="name"><br>
    タイトル:<input type="text" name="title"><br>
    メッセージ:<br>
    <textarea name="msg" rows="10" cols="30"></textarea><br>
    <input type="submit" value="確認画面へ">
  </form>
  <hr>
eot1;
$i = 1;
foreach($result as $row) {
    $s = "<p>($i)タイトル: <strong>%s</strong><br>投稿者: %s 投稿日 %s<br>内容:<br>%s</p>";
    printf ($s,h($row['title']),  h($row['name']), h($row['day']), h($row['msg']));
    $i++;
}
echo <<<eot2
</body>
</html>
eot2;

confirm.php

<?php
session_start();
$token = sha1(uniqid(mt_rand(), true));
$_SESSION['token'] = $token;
require_once('db_info.php');
require_once('encode.php');
try {
  $pdo = new PDO($dsn, $user, $pass);
  $pdo->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
} catch (PDOExeption $e){
  $error = 'Unable to connect to the database server: ' . $e->getMessage();
  include 'error.php';
  exit();
}
?>

<!DOCTYPE html>
<html lang="ja">
<head>
    <meta charset="UTF-8">
    <title>確認画面</title>
</head>
<body>
    <h1>確認画面</h1>
    タイトル: <?php print h($_POST['title']); ?>
    投稿者: <?php print h($_POST['name']); ?><br>
    内容:<?php print h($_POST['msg']); ?>
    <form action="complete.php" method="post">
        <input type="hidden" name="name" value="<?php echo $_POST['name'];?>">
        <input type="hidden" name="title" value="<?php echo $_POST['title'];?>">
        <input type="hidden" name="msg" value="<?php echo $_POST['msg'];?>">
        <input type="hidden" name="token" value="<?php $token ?>">
        <input type="submit" value="送信する" name="regist">
    </form>
</body>
</html>

complete.php

<?php
session_start();
if (isset($_POST['regist'], $token, $_POST['token']) && $token === $_POST['token']) {
  unset($_SESSION['token']);
  require_once('db_info.php');

  try {
    $pdo = new PDO($dsn, $user, $pass);
    $pdo->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
  } catch (PDOExeption $e){
    $error = 'Unable to connect to the database server: ' . $e->getMessage();
    include 'error.php';
    exit();
  }
  //書き込み
      try {
          $sql = "insert into bbsdata (name, day, title, msg) values (:name, now(), :title, :msg)";
          $s = $pdo->prepare($sql);
          $s->bindValue(':name', $_POST['name']);
          $s->bindValue(':title', $_POST['title']);
          $s->bindValue(':msg', $_POST['msg']);
          $s->execute();
      } catch(PDOExeption $e) {
          $error = 'Error writing message: ' . $e->getMessage();
          include 'error.php';
          exit();        
      }
      header("Location: http://{$_SERVER['HTTP_HOST']}/practice/bbs/complete.php/", true, 303);
} else {
echo <<<eot1
<!DOCTYPE html>
<html lang="ja">
<head>
    <meta charset="UTF-8">
    <title>完了画面</title>
</head>
<body>
    <h1>書き込みが完了しました</h1>
    <a href="http://{$_SERVER['HTTP_HOST']}/practice/bbs/index.php">トップページへ戻る</a>
</body>
</html>
eot1;
}


complete.phpはリロードでの二重送信防止目的にPRGパターンを使っているため、ブラウザに表示された時にはGETになるべきだと思うのですが、POSTとして表示されます。
したがってif (isset($_POST['regist'], $token, $_POST['token']) && $token === $_POST['token'])で弾かれてしまい、DBにinsertすることなく「書き込みが完了しました」と表示されているのではと思います。

また、私のcomplete.phpの設定では不正な値、値が空といった場合でも「書き込みが完了しました」と表示されてしまうのではと思います。

下記の点についてご回答お願い致します。

  • 投稿をinsertするためにはどうしたいいか
  • 不正な値・値が空の場合の処理をどうすべきか
  • セキュリティの方向性が現在の設定(エスケープ処理、PDO、トークンの使用)で間違いはないか
  • 二重送信への対策はこれで十分か(リロード、戻るボタン使用時など)
  • 現在の処理で不要な箇所などはないか

2017.4.29追記
構成など少し変更しとりあえず自分の思っていた形に実装できました。
フィードバックを頂きたく思い、ここでは記述が長くなり見にくくなるため新たにこちらに投稿させて頂きました。
アドバイス頂けると幸いです。

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

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

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

    クリップを取り消します

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

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

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

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

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

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

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

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

    質問の評価を下げる

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

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

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

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

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

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

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

    詳細な説明はこちら

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

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

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

回答 3

checkベストアンサー

+3

ざっとしか見てませんが、complete.phpでいきなり$tokenを参照しているのが問題では。
セッションから取り出す必要があるのではないでしょうか。

<?php
session_start();
$token = $_SESSION['token'];         // ←追加
if (isset($_POST['regist'], $token, $_POST['token']) && $token === $_POST['token']) {

投稿

編集

  • 回答の評価を上げる

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

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

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

  • 回答の評価を下げる

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

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

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

  • 2017/04/20 06:30

    ご指摘の通り
    $token = $_SESSION['token'];
    を追加しましたが変化がみられません。
    なぜでしょうか・・・。

    キャンセル

  • 2017/04/20 08:31

    confirm.phpでtokenのところも違うような(小出しですみません)
    <input type="hidden" name="token" value="<?php $token ?>">

    <input type="hidden" name="token" value="<?php echo $token; ?>">

    キャンセル

  • 2017/04/21 10:08

    echoを記載することで無事に投稿できました。
    ありがとうございました!

    しかし今度は
    $token = $_SESSION['token'];
    としたことで
    unset($_SESSION['token']);
    でセッションを破棄したものがリダイレクト後にも読み込まれるため
    「書き込みが完了しました」の表示と共に
    Notice: Undefined index: token
    と警告表示されました。

    したがってcomplete.phpでは
    $token = $_SESSION['token']
    とせずに
    $_SESSION['token']のままissetすることで解決しました。

    投稿がinsertできる形になりましたので、これから他の回答者の方のアドバイスに取り組んでいきます。
    また助け船を出していただけるとありがたいです。
    ありがとうございました!

    キャンセル

+3

 トークン

$token = sha1(uniqid(mt_rand(), true));

現在、mt_rand() をトークンに使用するのはあまり好ましくないようです。
PHPのバージョンや環境によりますが、PHP5.3以降ならopenssl_random_pseudo_bytesで、PHP7.0以降ならrandom_bytesを使うのがよいかと思います。
詳細は下記をどうぞ。

 値の受け取り方

確認画面で入力された値が、空やNULLではないか確認してエラーを表示する方がよいかと思います。
isset や filter_input などで確認できます。
詳細は下記をどうぞ。

PHP7からは NULL合体演算子 ?? と呼ばれるものが導入され、もっとシンプルに値を受け取れます。
詳細は下記をどうぞ。

投稿

編集

  • 回答の評価を上げる

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

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

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

  • 回答の評価を下げる

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

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

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

  • 2017/04/22 04:45

    コメントが遅くなりすみません!
    PHP7を使用していますのでアドバイス通りrandom_bytesに変更しました。
    セキュリティをしっかり身につけたいと思っていますので非常に助かりました。
    ttyp03さんのご回答に記載している対応でとりあえず投稿は出来るようになったのでこれから値のチェックや構成など変更してみてご報告します!

    キャンセル

  • 2017/04/30 00:04

    今回アドバイス頂いた点を自分なりに考慮してまとめたものを再度別ページで質問させて頂きました。この質問の末尾に追記でリンクを張っています。

    厳しい評価を頂いていますが、お時間がある時にご覧頂ければと思います。

    キャンセル

+2

confirmはhiddenでデータの受けわたしは容易に改ざんされますのでNGです
(やってもいいですが確認画面だす意味がなくなるので)
セッションでやってみてください

completeはissetで$tokenをチェックしていますが
$_SESSION["token"]ですね
フロー上$tokenはセットされないので常にelseに流れているように見えます

条件でエラーだと完了画面をだすというフローが間違っていると思います
confirmからconfimにデータ送信、完了ならcompleteに飛ばすなどの方が良いのでは?
(completeは完了を知らせるだけのページとする)

投稿

編集

  • 回答の評価を上げる

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

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

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

  • 回答の評価を下げる

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

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

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

  • 2017/04/22 04:50

    返信が遅くなりすみません!
    $_SESSION["token"]でとりあえず投稿は出来るようになったのでいま構成の見直しをしています。
    ちょっと躓いている部分があるのでまた進捗状況をこちらで報告させて頂きます!

    キャンセル

  • 2017/04/23 07:28

    >hiddenでデータの受け渡しは容易に改ざんされますのでNGです

    トークンをhiddenでpostするのがいけないということでしょうか。
    その場合、SESSIONで生成されたトークンとPOSTされたトークンの照合はどのようにしたらよいのでしょうか。

    キャンセル

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

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