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


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

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

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

# 脆弱性対策について

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

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


| 対策    | 結果 |
|:------:|:------------:|:-:|
| XSS対策 (エスケープ処理 )| ○ |
|パスワードのハッシュ化 | ○ |
書き方としては、`~.tpl.php`がviewで、同名の`~.php`がphp部分になります(MVC構造ではないので、phpはコントローラーにまとめられていません)。*GETメソッドで参照される部分と言えるのかな？
`~.php`に`require_once`で`~.tpl.php`を読み込みます。
下記の場合は`signup.php`に`signup.tpl.php`を読み込んでいます。

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

    //Laravelと違って、これしないとsession使えない
    session_start();
    
    //view読みこんでる
    require_once 'views/signup.tpl.php';
    exit;
```
バリデーションにsession機能を利用するので、`session_start()`します。
新規登録/ログイン処理をしたユーザーは、sessionに格納されます。
<br>
下記は投稿フォームの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>
```
<br>
上記の投稿フォームでPOSTされた情報は、下記のメソッドを参照してユーザー登録されます。
`htmlspecialchars()`でユーザーネームにエスケープ処理をし、`password_hash()`でパスワードをハッシュ化しています。
[【PHP】パスワードのハッシュ化とパスワードの認証をする方法](https://qiita.com/wakahara3/items/792943c1e0ed7a87e1ef)
[【PHP入門講座】 XSS攻撃への対策](https://qiita.com/mpyw/items/565b3670dd0c7f9162fa)...エスケープ処理について

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

```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対策は必要か](https://qiita.com/khsk/items/f654ddddc249f1f7c5e2)


| 対策    | 結果 |
|:------:|:------------:|:-:|
|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()`を使用して読み込みます。

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


```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対策をひよっこエンジニアがまとめてみた(随時更新予定)](https://html-coding.co.jp/knowhow/security/csrf/)
[サイトを安全に！PHPでcsrf対策を行う方法【初心者向け】](https://techacademy.jp/magazine/19300)
[3分で覚え直す$_SESSIONの使い方まとめ [PHP]](https://qiita.com/Yuki-Kurita/items/78816353a62af2c2436d)

###  XSS対策
[【PHP入門講座】 XSS攻撃への対策](https://qiita.com/mpyw/items/565b3670dd0c7f9162fa)
[XSSの対策をひよっこエンジニアがまとめてみた](https://html-coding.co.jp/knowhow/security/xss/)

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