早期 PHP 專案常在入口檔案引入全域過濾腳本,對 $_GET、$_POST、$_COOKIE、$_REQUEST 統一執行 addslashes_deep,試圖以「一次轉義、到處安全」阻擋 SQL 注入。本文還原經典實作,剖析其防禦範圍、繞過手法與現代替代方案。
if (!get_magic_quotes_gpc()) { // PHP 5.4+ 已移除,此判斷恆為 true
if (!empty($_GET)) $_GET = addslashes_deep($_GET);
if (!empty($_POST)) $_POST = addslashes_deep($_POST);
$_COOKIE = addslashes_deep($_COOKIE);
$_REQUEST = addslashes_deep($_REQUEST);
}
function addslashes_deep($value) {
if (empty($value)) return $value;
return is_array($value)
? array_map('addslashes_deep', $value)
: addslashes($value);
}
| 攻擊向量 | 是否防禦 | 說明 |
|---|---|---|
id=1' OR '1'='1 (值注入) | ✅ 防禦 | 單引號被轉義為 \' |
id=1; DROP TABLE users (值注入) | ✅ 防禦 | 分號、空格不受影響,但單引號阻斷字串閉合 |
order_by=name;-- (鍵名注入) | ❌ 失效 | addslashes_deep 不處理鍵名 |
id=1/*!UNION*/SELECT... (註解繞過) | ⚠️ 視情況 | 無單引號時可能繞過,需結合關鍵字過濾 |
| 二次注入 (存儲惡意資料,後續拼接) | ❌ 失效 | 入庫時已轉義,讀出再拼接時單引號變雙重轉義,但若開發者用 stripslashes 會復原 |
| 編碼繞過 (GBK 多字節、Unicode) | ❌ 失效 | addslashes 不具備字元集感知,易被寬字節攻擊 |
O'Reilly)在顯示層出現多餘反斜線,需額外 stripslashes,易造成雙重轉義/漏轉義。// 1. 邊界層:表單驗證(白名單、型別、長度、格式)
$request->validate([
'id' => 'required|integer|min:1',
'order_by' => 'in:id,name,created_at', // 白名單
]);
// 2. 資料層:參數化查詢(PDO / Eloquent / ThinkPHP)
// PDO
$stmt = $pdo->prepare('SELECT * FROM users WHERE id = :id ORDER BY :order');
$stmt->execute(['id' => $id, 'order' => $orderBy]);
// ThinkPHP 5+
User::where('id', $id)->order($orderBy)->select();
// 3. 輸出層:語境編碼
// HTML: htmlspecialchars($str, ENT_QUOTES, 'UTF-8')
// JS: json_encode($str)
// SQL: 參數化已處理,無需額外編碼
addslashes_deep 呼叫。where()、Laravel Eloquent)。stripslashes、mysql_real_escape_string 等舊代碼並重構。addslashes_deep 全域過濾是**「以犧牲資料完整性換取部分防禦」**的妥協產物,已不適合現代 PHP 開發。徹底擁抱**參數化查詢 + 邊界驗證 + 輸出編碼**三層架構,才是 SQL 注入防禦的正解。