PHP、第二章:会員登録システム。セキュリティーの強化

PHP

前回の記事では、PHPを使ってシンプルな会員登録システムを構築しました。しかし、そのシステムではセキュリティ面に課題が残っており、パスワードが平文で保存される、SQLインジェクションに弱い、エラーハンドリングが不足しているなどのリスクがありました。

今回は、そのセキュリティ課題を解決するために、以下の3つの対策を実装します。

  1. パスワードの暗号化
  2. SQLインジェクション対策
  3. エラーハンドリング

これにより、セキュアで信頼性の高い会員登録システムを構築します。それぞれのコードとポイントを解説します。

パスワードの暗号化

平文のパスワードを保存することはセキュリティ上非常に危険です。万が一データベースが流出した場合、すべてのユーザーのパスワードが漏洩してしまいます。
そこで、password_hash() を使用して、パスワードをハッシュ化します。この関数は安全な暗号アルゴリズムを使用してハッシュを生成するため、復号化が難しく、セキュリティを大幅に向上させます。

<?php
// データベース接続情報
$host = 'localhost';
$dbname = 'member_system';
$charset = 'utf8';     // 文字セット
$username = 'root';
$password = 'pass';

// フォームからの値をそれぞれ変数に代入
$name = $_POST['name'];
$mail = $_POST['mail'];
$pass = password_hash($_POST['pass'], PASSWORD_DEFAULT); // パスワードをハッシュ化


// DSN (Data Source Name) の作成
$dsn = "mysql:host=$host;dbname=$dbname;charset=$charset";
$pdo = new PDO($dsn, $username, $password);

$sql = "INSERT INTO users (name, mail, pass) VALUES ('$name', '$mail', '$pass')";
$pdo->query($sql);

echo "登録が完了しました!";
echo '<br><a href="index.html">トップに戻る</a>';
insert.php

ポイント

//$pass = $_POST['pass'];

$pass = password_hash($_POST['pass'], PASSWORD_DEFAULT); // パスワードをハッシュ化
PHP
  • password_hash() を使うことで、暗号化された形でパスワードをデータベースに保存します。
  • ハッシュ化されたパスワードは復元できませんが、認証時に password_verify() を使用して一致を確認できます。

SQLインジェクション対策

SQLインジェクションは、ユーザーが意図しないSQLを実行される攻撃です。
例えば、$_POST[‘name’] にSQL文が含まれていた場合、データベースが破壊される可能性があります。
これを防ぐために、プリペアドステートメントを使用します。これにより、SQLとデータが分離され、入力値が悪意のあるコードとして解釈されるリスクがなくなります。

<?php
// データベース接続情報
$host = 'localhost';
$dbname = 'member_system';
$charset = 'utf8';     // 文字セット
$username = 'root';
$password = 'pass';

// フォームからの値をそれぞれ変数に代入
$name = $_POST['name'];
$mail = $_POST['mail'];
$pass = password_hash($_POST['pass'], PASSWORD_DEFAULT); // パスワードをハッシュ化

// DSN (Data Source Name) の作成
$dsn = "mysql:host=$host;dbname=$dbname;charset=$charset";
$pdo = new PDO($dsn, $username, $password);

$sql = "INSERT INTO users (name, mail, pass) VALUES (?, ?, ?)";
$stmt = $pdo->prepare($sql);
$stmt->execute([$name, $mail, $pass]);

echo "登録が完了しました!";
echo '<br><a href="index.html">トップに戻る</a>';
insert.php

ポイント

//$sql = "INSERT INTO users (name, mail, pass) VALUES ('$name', '$mail', '$pass')";
//$pdo->query($sql);



$sql = "INSERT INTO users (name, mail, pass) VALUES (?, ?, ?)";
$stmt = $pdo->prepare($sql);
$stmt->execute([$name, $mail, $pass]);
PHP
  • プレースホルダー ? を使用して、データベースに渡す値を安全に扱います。
  • $stmt = $pdo->prepare($sql);↠この段階ではプレースホルダー(?)が埋め込まれており、SQL文そのものはまだ実行されていません。これにより、SQL文の構造とデータを分離、その結果が $stmt に代入されます。

SQL実行

  • $stmt->execute([$name, $mail, $pass]);↠プレースホルダーに渡す値を配列で指定して、準備したSQL文を実行します。
  • execute() メソッドが呼ばれると、プレースホルダー(?)が順番に配列の値で置き換えられ、SQL文が実行されます。
  • この方法により、渡されるデータが安全にエスケープされ、SQLインジェクションのリスクを低減します。

エラーハンドリング

エラーハンドリングがない場合、システムに予期しない問題が発生すると、その詳細情報がユーザーに表示される可能性があります。
特にデータベース接続エラーの内容が漏洩すると、システムの脆弱性が露呈してしまいます。
PDOのエラーモードを PDO::ERRMODE_EXCEPTION に設定し、エラー発生時に例外をキャッチして安全に処理します。

<?php
// データベース接続情報
$host = 'localhost';
$dbname = 'member_system';
$charset = 'utf8';     // 文字セット
$username = 'root';
$password = 'pass';

// フォームからの値をそれぞれ変数に代入
$name = $_POST['name'];
$mail = $_POST['mail'];
$pass = password_hash($_POST['pass'], PASSWORD_DEFAULT); // パスワードをハッシュ化

try {
  $dsn = "mysql:host=$host;dbname=$dbname;charset=$charset";
  $pdo = new PDO($dsn, $username, $password, [
      PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION, // 例外をスロー
  ]);

  $sql = "INSERT INTO users (name, mail, pass) VALUES (?, ?, ?)";
  $stmt = $pdo->prepare($sql);
  $stmt->execute([$name, $mail, $pass]);

  echo "登録が完了しました!";
  echo '<br><a href="index.html">トップに戻る</a>';
} catch (PDOException $e) {
  echo "エラーが発生しました: " . htmlspecialchars($e->getMessage(), ENT_QUOTES, 'UTF-8');
}
insert.php

テスト環境

テスト環境では、開発者がシステムの問題を迅速に特定・修正できるように、エラーをわかりやすく表示することが重要です。そのため、エラーメッセージを直接表示したり、詳細な情報を開発者に伝える設定が適しています。ただし、外部に公開される環境ではないことが前提です。

try {
    $pdo = new PDO($dsn, $username, $password, [
        PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION, // 例外をスロー
    ]);
} catch (PDOException $e) {
    echo "エラーが発生しました: " . htmlspecialchars($e->getMessage(), ENT_QUOTES, 'UTF-8');
}
PHP
  • PDO::ATTR_ERRMODE を PDO::ERRMODE_EXCEPTION に設定し、エラーが発生した際に例外をスローします。
  • try-catch 文を使用してエラーをキャッチし、安全なエラーメッセージを表示します。
  • htmlspecialchars() を使用してエラーメッセージ内の特殊文字をエスケープし、不正なHTMLやJavaScriptが出力されないようにします。

本番環境

本番環境では、ユーザーに直接触れるシステムが動作しているため、エラーが発生しても内部の情報(データベースの構造や接続情報など)が外部に漏れないよう、慎重なエラーハンドリングが必要です。また、ユーザーに対しては簡潔かつ一般的なエラーメッセージを表示し、詳細な情報はログに記録することでセキュリティを確保します。

try {
    $pdo = new PDO($dsn, $username, $password, [
        PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION,
    ]);
} catch (PDOException $e) {
    error_log("PDOエラー: " . $e->getMessage()); // エラーをログに記録
    echo "システムエラーが発生しました。しばらくしてから再度お試しください。";
}
PHP

これらの対策を実装することで、セキュアな会員登録システムが完成します。次回は、さらにユーザー認証やセッション管理などの機能を追加していきますので、お楽しみに!

コメント

タイトルとURLをコピーしました