本文展示一個基礎的 checklogin 類別,演示如何封裝帳號密碼驗證、資料庫查詢與 Session 管理。然而,原始碼存在嚴重的 SQL 注入漏洞、使用已移除的 mysql_* 函數、明文密碼比對等安全隱憂。本文在保留教學示範價值的同時,詳細分析風險並提供現代化重構方案。
class checklogin {
var $name;
var $pwd;
function __construct($username, $password) {
$this->name = $username;
$this->pwd = $password;
}
function checkinput() {
global $db; // 假設 $db 為資料庫連線物件
// ⚠️ 直接拼接變數 → SQL 注入高風險
$sql = "select * from tb_manager where name='$this->name' and pwd='$this->pwd'";
$res = $db->query($sql);
$info = $db->fetch_array($res);
if ($info['name'] == $this->name and $info['pwd'] == $this->pwd) {
$_SESSION['admin_name'] = $info['name'];
$_SESSION['pwd'] = $info['pwd']; // ⚠️ 明文密碼存 Session
echo "";
} else {
echo "";
exit;
}
}
}
| 問題 | 風險 | 修正方向 |
|---|---|---|
| 字串拼接 SQL | SQL 注入,繞過驗證 | 使用 Prepared Statements (參數綁定) |
| 明文密碼存儲/比對 | 資料庫洩露即帳號全失效 | 使用 password_hash() / password_verify() |
| 密碼寫入 Session | Session 劫持即拿到密碼 | 僅存用戶 ID、角色等非敏感資訊 |
| JavaScript 跳轉 | 依賴前端、易被攔截/繞過 | 使用 PHP header('Location: ...') |
| 全域 $db 變數 | 耦合度高、難測試 | 依賴注入 (DI) 傳入連線物件 |
class LoginService {
private PDO $pdo;
public function __construct(PDO $pdo) {
$this->pdo = $pdo;
}
public function attempt(string $username, string $password): bool {
$stmt = $this->pdo->prepare("SELECT id, password_hash FROM users WHERE username = ?");
$stmt->execute([$username]);
$user = $stmt->fetch(PDO::FETCH_ASSOC);
if ($user && password_verify($password, $user['password_hash'])) {
$_SESSION['user_id'] = $user['id'];
$_SESSION['username'] = $username;
// 可選:重新生成 Session ID 防止 fixation
session_regenerate_id(true);
return true;
}
return false;
}
}
// 使用方式
$pdo = new PDO("mysql:host=localhost;dbname=app;charset=utf8mb4", 'user', 'pass', [
PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION,
PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC,
]);
$login = new LoginService($pdo);
if ($login->attempt($_POST['username'] ?? '', $_POST['password'] ?? '')) {
header('Location: dashboard.php');
exit;
} else {
$error = '帳號或密碼錯誤';
}
登入機制是系統安全的第一道關卡,務必採用參數化查詢、強雜湊演算法、最小權限 Session 管理,並定期進行滲透測試與代碼審查。