目的
フレームワーク無しの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
を読み込んでいます。
//データベースの接続読みこんでる
require_once 'bootstrap.php';
//Laravelと違って、これしないとsession使えない
session_start();
//view読みこんでる
require_once 'views/signup.tpl.php';
exit;
バリデーションにsession機能を利用するので、session_start()
します。
新規登録/ログイン処理をしたユーザーは、sessionに格納されます。
下記は投稿フォームのviewになります。
<!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】パスワードのハッシュ化とパスワードの認証をする方法](https://qiita.com/wakahara3/items/792943c1e0ed7a87e1ef) [【PHP入門講座】 XSS攻撃への対策](https://qiita.com/mpyw/items/565b3670dd0c7f9162fa)...エスケープ処理について
変更点
新規登録のメールアドレスやパスワードにエスケープ処理をすると、特殊文字を使用した場合はログインできなくなるのでユーザーネームにだけエスケープ処理してます(エスケープ処理のせいで一致しない)。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対策 (エスケープ処理 )| ○ |
|パスワードのハッシュ化 | ○ |
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を下記のフォームでログイン情報と一緒にポストします。
<!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()を使用して再度ポストされたものと一致するか調べます
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の対策をひよっこエンジニアがまとめてみた
まとめ
かなり行き当たりばったりで始めた為、勉強法として半端な感じが否めないです。
反省点も洗い出して、勉強法としてブラッシュアップしたいなと思ってます。