原文提供的防注入碼存在嚴重缺陷:
eregi() 自 PHP 5.3 棄用、PHP 7.0 移除。file_get_contents("php://input") 僅能讀取一次,且不涵蓋 multipart/form-data。核心原則:代碼與資料分離。SQL 結構在準備階段確定,參數在執行階段綁定,資料庫引擎保證參數永遠不會被解釋為 SQL 指令。
<?php
$mysqli = new mysqli('localhost', 'user', 'pass', 'db');
$mysqli->set_charset('utf8mb4');
$keyword = $_GET['q'] ?? '';
// ✅ 正確:佔位符 ? 對應字串參數
$stmt = $mysqli->prepare("SELECT * FROM products WHERE name LIKE ? AND status = 1");
$like = "%{$keyword}%";
$stmt->bind_param('s', $like);
$stmt->execute();
$result = $stmt->get_result();
while ($row = $result->fetch_assoc()) {
// 輸出...
}
$stmt->close();
<?php
$pdo = new PDO('mysql:host=localhost;dbname=db;charset=utf8mb4', 'user', 'pass', [
PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION,
PDO::ATTR_EMULATE_PREPARES => false, // 關鍵:使用真實預處理
]);
$keyword = $_GET['q'] ?? '';
// ✅ 正確:陣列傳參自動綁定
$stmt = $pdo->prepare("SELECT * FROM products WHERE name LIKE ? AND status = 1");
$stmt->execute(["%{$keyword}%"]);
foreach ($stmt as $row) {
// 輸出...
}
| 場景 | 解決方案 | 範例 |
|---|---|---|
| 動態欄位名/表名 | 白名單驗證 | if (!in_array($col, $allowedCols)) throw new Exception(); |
| 動態 ORDER BY / LIMIT | 白名單 + 型別轉換 | ORDER BY {$allowed[$col]} LIMIT " . (int)$limit |
| IN 子句動態參數數量 | 動態產生佔位符 | $in = implode(',', array_fill(0, count($ids), '?')); |
| 舊專案無法重構 | 專用逃逸函數 (次佳) | $mysqli->real_escape_string($val) / $pdo->quote($val) |
<?php
$ids = [1, 5, 9, 20]; // 來自前端陣列
// 動態產生 ?,?,?,?
$placeholders = implode(',', array_fill(0, count($ids), '?'));
$sql = "SELECT * FROM users WHERE id IN ($placeholders)";
$stmt = $pdo->prepare($sql);
$stmt->execute($ids); // 陣列直接對應佔位符
$users = $stmt->fetchAll();
mysql_* / ereg* 函數prepare() + bind_param/execute([])"WHERE id = $id" ❌ → "WHERE id = ?" ✅mysqli_report(MYSQLI_REPORT_ERROR | MYSQLI_REPORT_STRICT) / PDO::ERRMODE_EXCEPTIONdisplay_errors = Off,log_errors = On徹底貫徹「參數化查詢」原則,才是防禦 SQL 注入的根本之道。黑名單過濾只是假象,請立即移除。