SQL 注入仍是 Web 應用頭號安全風險。本文從注入成因剖析,比較 addslashes、mysql_real_escape_string 與 PDO 預處理語句的防禦效能,建構現代 PHP 專案的標準防注入體系。
$id = $_GET['id']; // 假設攻擊者傳入:1' OR 1=1 # $sql = "DELETE FROM users WHERE id = '$id'"; // 實際執行:DELETE FROM users WHERE id = '1' OR 1=1 #'
單引號 ' 打破了資料與程式碼邊界,導致邏輯被竄改。
| 函數 | 優點 | 缺點 | 適用場景 |
|---|---|---|---|
addslashes() | 無需資料庫連線 | 不處理字元集、多位元組字元繞過風險 | 極簡腳本、無 DB 連線環境 |
mysql_real_escape_string() | 感知連線字元集 | 需有效連線;PHP 7.0 已移除(mysql_* 擴展棄用) | 舊專案維護(PHP 5.6 及以下) |
mysqli_real_escape_string() | mysqli 版本,支援字元集 | 仍屬手動轉義,易漏寫 | mysqli 程式碼庫 |
// 強制加引號包裹的封裝範例(mysqli)
function quote($str, $link) {
return "'" . mysqli_real_escape_string($link, $str) . "'";
}
預處理(Prepared Statement)將 SQL 結構與資料分離,資料庫驅動層編譯語法樹後再綁定參數,從根本杜絕注入。
$pdo = new PDO($dsn, $user, $pass);
$stmt = $pdo->prepare("SELECT * FROM users WHERE id = :id AND status = ?");
$stmt->bindValue(':id', $id, PDO::PARAM_INT);
$stmt->bindValue(1, $status, PDO::PARAM_STR);
$stmt->execute();
$rows = $stmt->fetchAll(PDO::FETCH_ASSOC);
核心優勢:
$pdo->beginTransaction();
try {
$pdo->exec("INSERT ...");
$pdo->exec("UPDATE ...");
$pdo->commit();
} catch (Exception $e) {
$pdo->rollBack();
}
$id = $pdo->lastInsertId();
PDO::quote()若必須拼接 SQL(如動態 ORDER BY),可用 $pdo->quote($val) 自動轉義加引號。
class PDODB {
private static $instance;
private $pdo;
private function __construct(array $cfg) {
$dsn = "mysql:host={$cfg['host']};port={$cfg['port']};dbname={$cfg['dbname']};charset={$cfg['charset']}";
$this->pdo = new PDO($dsn, $cfg['user'], $cfg['pass'], [
PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION,
PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC,
PDO::MYSQL_ATTR_INIT_COMMAND => "SET NAMES {$cfg['charset']}"
]);
}
public static function getInstance(array $cfg): self {
return self::$instance ??= new self($cfg);
}
public function query(string $sql): PDOStatement {
return $this->pdo->query($sql);
}
public function fetchAll(string $sql): array {
return $this->query($sql)->fetchAll();
}
public function fetchRow(string $sql): ?array {
return $this->query($sql)->fetch() ?: null;
}
}
棄用手動轉義,全面採用 PDO 預處理語句是現代 PHP 專案防 SQL 注入的鐵律。搭配嚴格的型別提示、輸入驗證與最小權限資料庫帳號,可構建多層次防禦體系。