PHP MySQL 安全性

資料庫安全是 Web 應用程式中最重要的環節之一。

SQL Injection 攻擊

SQL Injection 是最常見的資料庫攻擊方式:

<?php
// ❌ 危險:直接拼接使用者輸入
$username = $_POST['username'];
$sql = "SELECT * FROM users WHERE username = '$username'";

// 攻擊者輸入:' OR '1'='1
// 結果:SELECT * FROM users WHERE username = '' OR '1'='1'
// 會回傳所有使用者!
?>

預處理語句防護

永遠使用預處理語句(Prepared Statements)

<?php
// ✅ 安全:使用預處理語句
$stmt = $pdo->prepare("SELECT * FROM users WHERE username = ?");
$stmt->execute([$_POST['username']]);
$user = $stmt->fetch();

// 使用命名參數
$stmt = $pdo->prepare("SELECT * FROM users WHERE username = :username");
$stmt->execute(['username' => $_POST['username']]);
?>

輸入驗證

<?php
function validateUserInput(array $data): array {
    $errors = [];
    
    // 必填檢查
    if (empty($data['username'])) {
        $errors[] = '使用者名稱為必填';
    }
    
    // 格式檢查
    if (!filter_var($data['email'], FILTER_VALIDATE_EMAIL)) {
        $errors[] = 'Email 格式不正確';
    }
    
    // 長度檢查
    if (strlen($data['username']) > 50) {
        $errors[] = '使用者名稱過長';
    }
    
    // 型別檢查
    if (!is_numeric($data['age'])) {
        $errors[] = '年齡必須是數字';
    }
    
    return $errors;
}
?>

密碼安全

<?php
// ✅ 正確:使用 password_hash
$password = $_POST['password'];
$hash = password_hash($password, PASSWORD_DEFAULT);

// 儲存 $hash 到資料庫
$stmt = $pdo->prepare("INSERT INTO users (username, password) VALUES (?, ?)");
$stmt->execute([$username, $hash]);

// 驗證密碼
$stmt = $pdo->prepare("SELECT * FROM users WHERE username = ?");
$stmt->execute([$username]);
$user = $stmt->fetch();

if ($user && password_verify($password, $user['password'])) {
    echo "登入成功";
} else {
    echo "帳號或密碼錯誤";
}
?>
絕對不要使用 md5() 或 sha1() 儲存密碼,這些已不安全。

最小權限原則

-- 建立專用資料庫使用者
CREATE USER 'webapp'@'localhost' IDENTIFIED BY 'strong_password';

-- 只給予必要的權限
GRANT SELECT, INSERT, UPDATE, DELETE ON myapp.* TO 'webapp'@'localhost';

-- 不要給予 DROP、ALTER 等危險權限
-- GRANT ALL PRIVILEGES ... -- 避免使用

錯誤處理

<?php
// ❌ 危險:顯示詳細錯誤
try {
    $pdo->query($sql);
} catch (PDOException $e) {
    echo $e->getMessage();  // 可能洩漏資料庫結構
}

// ✅ 安全:記錄錯誤,顯示通用訊息
try {
    $pdo->query($sql);
} catch (PDOException $e) {
    error_log($e->getMessage());  // 記錄到日誌
    echo "系統發生錯誤,請稍後再試";  // 顯示給使用者
}
?>

環境設定分離

<?php
// config.php(不要放在版本控制中)
return [
    'db' => [
        'host' => getenv('DB_HOST') ?: 'localhost',
        'name' => getenv('DB_NAME') ?: 'myapp',
        'user' => getenv('DB_USER') ?: 'root',
        'pass' => getenv('DB_PASS') ?: '',
    ],
];

// 使用
$config = require 'config.php';
$pdo = new PDO(
    "mysql:host={$config['db']['host']};dbname={$config['db']['name']}",
    $config['db']['user'],
    $config['db']['pass']
);
?>

XSS 防護

輸出資料時要跳脫:

<?php
// ❌ 危險
echo $user['name'];

// ✅ 安全
echo htmlspecialchars($user['name'], ENT_QUOTES, 'UTF-8');

// 建立輔助函數
function e(string $string): string {
    return htmlspecialchars($string, ENT_QUOTES, 'UTF-8');
}

echo e($user['name']);
?>

安全檢查清單

  • ☑️ 所有 SQL 查詢都使用預處理語句
  • ☑️ 密碼使用 password_hash() 儲存
  • ☑️ 驗證和清理所有使用者輸入
  • ☑️ 輸出時使用 htmlspecialchars()
  • ☑️ 資料庫使用者權限最小化
  • ☑️ 敏感設定使用環境變數
  • ☑️ 錯誤訊息不洩漏系統資訊
  • ☑️ 使用 HTTPS 傳輸敏感資料