目次
はじめに
前回のデータベース操作に続き、今回はPHPにおけるセッション管理とWebアプリケーションのセキュリティについて解説します。ユーザー認証、セッション管理、そしてよくある脆弱性とその対策について学んでいきましょう。
セッション管理の基本
セッションの開始と使用
<?php
// セッションの開始
session_start();
// セッションへのデータ保存
$_SESSION['user_id'] = 123;
$_SESSION['username'] = 'yamada';
// セッションからのデータ取得
if (isset($_SESSION['user_id'])) {
echo "ログインユーザーID: " . $_SESSION['user_id'];
}
// セッションの破棄
session_destroy();
セッションセキュリティの設定
<?php
// セッションの設定
ini_set('session.cookie_httponly', 1); // JavaScriptからのアクセスを防ぐ
ini_set('session.use_only_cookies', 1); // URLからのセッションIDを無効化
ini_set('session.cookie_secure', 1); // HTTPSのみでセッションを有効化
// セッションIDの再生成
session_regenerate_id(true);
ユーザー認証の実装
安全なパスワード管理
class UserAuthentication {
private $pdo;
public function __construct(PDO $pdo) {
$this->pdo = $pdo;
}
// ユーザー登録
public function register(string $username, string $password, string $email): bool {
$hashedPassword = password_hash($password, PASSWORD_DEFAULT);
$sql = "INSERT INTO users (username, password, email)
VALUES (:username, :password, :email)";
try {
$stmt = $this->pdo->prepare($sql);
return $stmt->execute([
':username' => $username,
':password' => $hashedPassword,
':email' => $email
]);
} catch (PDOException $e) {
error_log($e->getMessage());
return false;
}
}
// ログイン認証
public function login(string $username, string $password): ?array {
$sql = "SELECT * FROM users WHERE username = :username";
try {
$stmt = $this->pdo->prepare($sql);
$stmt->execute([':username' => $username]);
$user = $stmt->fetch();
if ($user && password_verify($password, $user['password'])) {
// セッションIDを再生成
session_regenerate_id(true);
// セッションにユーザー情報を保存
$_SESSION['user_id'] = $user['id'];
$_SESSION['username'] = $user['username'];
return $user;
}
return null;
} catch (PDOException $e) {
error_log($e->getMessage());
return null;
}
}
}
クロスサイトスクリプティング(XSS)対策
出力のエスケープ処理
class HTMLPurifier {
public static function escape(string $str): string {
return htmlspecialchars($str, ENT_QUOTES, 'UTF-8');
}
public static function escapeArray(array $array): array {
return array_map([self::class, 'escape'], $array);
}
}
// 使用例
$userInput = "<script>alert('XSS');</script>";
echo HTMLPurifier::escape($userInput); // 安全な出力
CSRFトークンの実装
class CSRFProtection {
public static function generateToken(): string {
if (empty($_SESSION['csrf_token'])) {
$_SESSION['csrf_token'] = bin2hex(random_bytes(32));
}
return $_SESSION['csrf_token'];
}
public static function validateToken(?string $token): bool {
if (empty($_SESSION['csrf_token']) || empty($token)) {
return false;
}
return hash_equals($_SESSION['csrf_token'], $token);
}
}
// フォームでの使用例
?>
<form method="POST" action="/submit">
<input type="hidden" name="csrf_token"
value="<?php echo CSRFProtection::generateToken(); ?>">
<!-- フォームの内容 -->
</form>
実践的な例:セキュアなログインシステム
<?php
class SecureLoginSystem {
private $pdo;
private $maxLoginAttempts = 5;
private $lockoutTime = 900; // 15分
public function __construct(PDO $pdo) {
$this->pdo = $pdo;
}
public function attemptLogin(string $username, string $password): array {
if ($this->isIPBlocked($_SERVER['REMOTE_ADDR'])) {
return [
'success' => false,
'message' => 'Too many failed attempts. Please try again later.'
];
}
try {
$user = $this->validateCredentials($username, $password);
if ($user) {
$this->resetLoginAttempts($_SERVER['REMOTE_ADDR']);
$this->createLoginSession($user);
return [
'success' => true,
'message' => 'Login successful'
];
}
$this->incrementLoginAttempts($_SERVER['REMOTE_ADDR']);
return [
'success' => false,
'message' => 'Invalid credentials'
];
} catch (Exception $e) {
error_log($e->getMessage());
return [
'success' => false,
'message' => 'An error occurred'
];
}
}
private function createLoginSession(array $user): void {
$_SESSION['user_id'] = $user['id'];
$_SESSION['username'] = $user['username'];
$_SESSION['login_time'] = time();
$_SESSION['ip_address'] = $_SERVER['REMOTE_ADDR'];
// セッションIDを再生成
session_regenerate_id(true);
}
private function validateCredentials(string $username, string $password): ?array {
$sql = "SELECT * FROM users WHERE username = :username";
$stmt = $this->pdo->prepare($sql);
$stmt->execute([':username' => $username]);
$user = $stmt->fetch();
if ($user && password_verify($password, $user['password'])) {
return $user;
}
return null;
}
// 以下、レート制限関連のメソッド
private function isIPBlocked(string $ip): bool {
$sql = "SELECT COUNT(*) as attempts, MAX(attempt_time) as last_attempt
FROM login_attempts
WHERE ip_address = :ip
AND attempt_time > DATE_SUB(NOW(), INTERVAL 15 MINUTE)";
$stmt = $this->pdo->prepare($sql);
$stmt->execute([':ip' => $ip]);
$result = $stmt->fetch();
return ($result['attempts'] >= $this->maxLoginAttempts);
}
private function incrementLoginAttempts(string $ip): void {
$sql = "INSERT INTO login_attempts (ip_address) VALUES (:ip)";
$stmt = $this->pdo->prepare($sql);
$stmt->execute([':ip' => $ip]);
}
private function resetLoginAttempts(string $ip): void {
$sql = "DELETE FROM login_attempts WHERE ip_address = :ip";
$stmt = $this->pdo->prepare($sql);
$stmt->execute([':ip' => $ip]);
}
}
セキュリティチェックリスト
- 入力検証
- すべてのユーザー入力を検証
- 適切なデータ型とフォーマットの強制
- 不正な入力の拒否
- 出力エスケープ
- HTMLエスケープの実施
- JavaScriptエスケープの実施
- SQLエスケープ(プリペアドステートメント使用)
- セッション管理
- セッションIDの定期的な再生成
- セキュアなセッション設定
- セッションタイムアウトの実装
- パスワードセキュリティ
- 強力なハッシュアルゴリズムの使用
- ソルトの適用
- パスワード強度の要件設定
まとめ
本日は、PHPでのセッション管理とセキュリティ対策について学びました。適切なセキュリティ対策は、Webアプリケーション開発において非常に重要です。
練習問題
- 以下の機能を持つログインシステムを作成してください:
- ユーザー登録(パスワードのハッシュ化)
- ログイン認証
- パスワードリセット機能
- ログイン試行回数の制限
- CSRFトークンを実装したフォーム送信システムを作成してください:
- トークンの生成と検証
- フォームの自動保護
- エラー処理
次回は、PHPでのファイル操作とアップロード機能について解説していきます。お楽しみに!
コメント