LoginSignup
10
9

More than 3 years have passed since last update.

PHP フレームワークを使わない掲示板の制作 *初心者の勉強用

Last updated at Posted at 2020-02-18

目的

フレームワーク無しのPHP(PHP+Eloquent)で掲示板を作る事で、基本的な技術を確認する(主に脆弱性について)。

*今回はEloquentを使用したのですが、SQLインジェクション対策を学ぶ場合はEloquent無しの方が良いかと思います。

要件定義

入力フォームでのクロスサイトスクリプティングの対策,CSRF対策。
簡単なログイン実装し、パスワードはハッシュ化、バリデーションもする。

脆弱性対策について

新規登録/ログインの処理を例にして書きたいと思います。
参考のURLは下記にまとめています。

新規登録

新規登録の際は、XSS対策のエスケープ処理とパスワードのハッシュ化の処理をしてます。

対策 結果
XSS対策 (エスケープ処理 )
パスワードのハッシュ化

書き方としては、~.tpl.phpがviewで、同名の~.phpがphp部分になります(MVC構造ではないので、phpはコントローラーにまとめられていません)。*GETメソッドで参照される部分と言えるのかな?
~.phprequire_once~.tpl.phpを読み込みます。
下記の場合はsignup.phpsignup.tpl.phpを読み込んでいます。

signup.php
    //データベースの接続読みこんでる
    require_once 'bootstrap.php';

    //Laravelと違って、これしないとsession使えない
    session_start();

    //view読みこんでる
    require_once 'views/signup.tpl.php';
    exit;

バリデーションにsession機能を利用するので、session_start()します。
新規登録/ログイン処理をしたユーザーは、sessionに格納されます。


下記は投稿フォームのviewになります。

signup.tpl.php

<!DOCTYPE html>
<html lang='ja'>
<?php include('header.inc.php'); ?>

<body>
  <?php include('nav.php'); ?>
  <div class="container">
    <form action='newAccount.php' method='post'>

      //新規登録フォームでCSRF対策はしていないのですが、Keyとして一応。
      <input type="hidden" name='user_pass' value='user_pass'>

      <label for='name'>Name</label><br>
      <input type='text' name='name'>
      <p></p>
      <label for='email'>Email</label><br>
      <input type='text' name='email'>
      <p></p>
      <label for='password'>Password</label><br>
      <input text='password' name='password'>
      <p></p>
      <button type='submit'>作成する</button>
    </form>
    <p><a href='index.php'>一覧に戻る</a></p>
  </div>


  <?php include('footer.inc.php'); ?>
</body>

</html>



上記の投稿フォームでPOSTされた情報は、下記のメソッドを参照してユーザー登録されます。
htmlspecialchars()でユーザーネームにエスケープ処理をし、password_hash()でパスワードをハッシュ化しています。
【PHP】パスワードのハッシュ化とパスワードの認証をする方法
【PHP入門講座】 XSS攻撃への対策...エスケープ処理について

変更点新規登録のメールアドレスやパスワードにエスケープ処理をすると、特殊文字を使用した場合はログインできなくなるのでユーザーネームにだけエスケープ処理してます(エスケープ処理のせいで一致しない)。


newAccount.php
require_once 'bootstrap.php';

    session_start();

    if (isset($_POST["user_pass"])) {
        $user = new User;
        $name = $_REQUEST['name'];
        $user->name = htmlspecialchars($name, ENT_QUOTES, 'UTF-8');
        $user->email = $_REQUEST['email'];
        $password = $_REQUEST['password'];
        $user->password = password_hash($password, PASSWORD_DEFAULT);
        $user->save();
        $_SESSION['username'] = $_POST['name'];
        header('Location: index.php');
      }
    exit;

ログイン

こっちは新規登録時にハッシュ化されてデータベースに保存されたデータを、読み込む処理をしてます。
また、CSRF対策もしています。「ログイン処理にCSRF対策必要?」とまだ十分に理解していない部分もあるのですが、必要みたいです。下記参照。
ログイン時のCSRF対策は必要か

対策 結果
CSRF対策
XSS対策 (エスケープ処理 )
パスワードのハッシュ化
signin.php
    require_once 'bootstrap.php';

    session_start();

    //ログインページを表示するとトークンを作成します。
    $toke_byte = openssl_random_pseudo_bytes(16);
    $csrf_token = bin2hex($toke_byte);
    //作成したトークンをセッションに格納します。
    $_SESSION['csrf_token'] = $csrf_token;

    require_once 'views/signin.tpl.php';
    exit;

上記で作った$csrf_tokenを下記のフォームでログイン情報と一緒にポストします。

signin.tpl.php
<!DOCTYPE html>
<html lang='ja'>
<?php include('header.inc.php'); ?>

<body>
  <?php include('nav.php'); ?>
  <div class="container">
    <form action='signin_check.php' method='post'>
    <input type="hidden" name="csrf_token" value="<?=$csrf_token?>">
      <label for='name'>Name</label><br>
      <input type='text' name='name'>
      <p></p>
      <label for='email'>Email</label><br>
      <input type='text' name='email'>
      <p></p>
      <label for='password'>Password</label><br>
      <input text='password' name='password'>
      <p></p>
      <button type='submit'>ログイン</button>
    </form>
    <p><a href="signup.php">Create an account</a></p>
    <p><a href="#">Forgot password?</a></p>
    <p><a href='index.php'>一覧に戻る</a></p>
  </div>

  <?php include('footer.inc.php'); ?>
</body>

</html>

セッションに格納されたトークンとフォームからPOSTされたトークンが一致した場合のみ処理が始まります。
パスワードはハッシュ化されている為、password_verify()を使用して読み込みます。

変更点ポストされたユーザーネームと一致するユーザーネームをデータベースから探して、一致したもののパスワードをpassword_verify()を使用して再度ポストされたものと一致するか調べます

signin-check.php

    require_once 'bootstrap.php';

    session_start();

    $users = User::where('name', $_POST['name'])
                        ->get();

    if (isset($_POST["csrf_token"])
         && $_POST["csrf_token"] === $_SESSION['csrf_token']) {
        foreach($users as $user){
          if (password_verify ($_POST['password'],$user->password)){

            $_SESSION['username'] = $_POST['name'];
          break;
           }
          }
          header('Location: index.php');
        }        
        echo "不正なリクエストです";

    exit;

CSRF対策

CSRF対策をひよっこエンジニアがまとめてみた(随時更新予定)
サイトを安全に!PHPでcsrf対策を行う方法【初心者向け】
3分で覚え直す$_SESSIONの使い方まとめ [PHP]

XSS対策

【PHP入門講座】 XSS攻撃への対策
XSSの対策をひよっこエンジニアがまとめてみた

まとめ

かなり行き当たりばったりで始めた為、勉強法として半端な感じが否めないです。
反省点も洗い出して、勉強法としてブラッシュアップしたいなと思ってます。

10
9
2

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
10
9