SQL 注入 是最嚴重的 Web 安全漏洞之一。本文系統整理防禦層次:參數化查詢 (核心)、輸入驗證、最小權限、WAF、審計日誌。
永遠不要用字串拼接 SQL。使用預處理語句,參數以佔位符傳遞,資料庫引擎將程式碼與資料分離。
<?php
$pdo = new PDO($dsn, $user, $pass, [PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION]);
// 安全:佔位符 ?
$stmt = $pdo->prepare('SELECT * FROM users WHERE email = ? AND status = ?');
$stmt->execute([$_POST['email'], 'active']);
$user = $stmt->fetch();
// 命名參數 (更易讀)
$stmt = $pdo->prepare('SELECT * FROM users WHERE email = :email');
$stmt->execute([':email' => $_POST['email']]);
$stmt = $mysqli->prepare('SELECT * FROM users WHERE email = ?');
$stmt->bind_param('s', $_POST['email']);
$stmt->execute();
$result = $stmt->get_result();
$user = $result->fetch_assoc();
$id = (int)$_GET['id']; // 整數
$price = (float)$_POST['price']; // 浮點數
$allowedSort = ['name', 'created_at', 'price'];
$sort = in_array($_GET['sort'], $allowedSort) ? $_GET['sort'] : 'created_at';
$order = ($_GET['order'] ?? 'DESC') === 'ASC' ? 'ASC' : 'DESC';
$sql = "SELECT * FROM products ORDER BY $sort $order"; // 欄位名無法參數化,必須白名單
$mysqli->real_escape_string($str); // mysqli
$pdo->quote($str); // PDO (含引號)
僅作為深度防禦,不可作為主要防線。
SELECT, INSERT, UPDATE, DELETE 業務表,無 DROP/ALTER/GRANT。SELECT。-- 不安全動態 SQL
SET @sql = CONCAT('SELECT * FROM ', @table);
PREPARE stmt FROM @sql; -- 仍有風險若 @table 來自用戶輸入
-- 安全:白名單驗證表名後再拼接
' OR '1'='1'; DROP TABLE users; --UNION SELECT password FROM admin --1; WAITFOR DELAY '0:0:5' -- (時間盲注)只要堅持「查詢結構與資料分離」,SQL 注入即可徹底根除。