首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
您现在的位置: 程式碼之家 >> 編程語言 >  >> 網頁程式設計 >> PHP 程式設計 >> PHP 綜合知識

PHP 防止 SQL 注入完整防禦指南:原理、分類與多層實戰策略

     編輯:PHP 綜合知識

SQL 注入 是將惡意 SQL 片段透過應用程式輸入傳遞,竄改原本查詢邏輯,導致資料洩露、竄改、刪除甚至伺服器被控制。本文系統整理攻擊分類、注入點特徵、繞過技術,並提供從程式碼、資料庫、環境、架構四層面的完整防禦體系。

一、 注入攻擊分類與危害

類型注入點位置典型 Payload危害
SELECT 注入WHERE, ORDER BY, LIMIT' OR '1'='1, UNION SELECT...繞過驗證、讀取敏感表、導出檔案
UPDATE 注入SET 值, WHERE 條件', level='3, ') WHERE username='admin'#提權、修改任意用戶資料
INSERT 注入VALUES 列表', '3')#註冊提權、寫入惡意資料
DELETE 注入WHERE 條件' OR '1'='1刪除全表資料
多語句注入任何支援多語句的驅動'; GRANT ALL...;--執行任意 SQL、建立後門帳號

二、 多語句注入與驅動程式行為

  • MySQL C API / mysqli:預設不支援多語句 (單次 query() 只執行第一條)。需呼叫 mysqli_multi_query() 才能執行多條。
  • PDO:預設不支援多語句 (視驅動屬性 PDO::MYSQL_ATTR_MULTI_STATEMENTS)。
  • SQLite (PDO_SQLITE):預設支援多語句 (批次 INSERT 優化),極高風險。sqlite_query() 若用於 SELECT 則不支援多語句。
  • 結論:雖驅動預設阻擋多語句,但單語句注入 (UNION、WHERE 竄改) 仍極具殺傷力,不可掉以輕心。

三、 經典案例:Invision Power Board 登入注入 (2005)


// 易受攻擊代碼
$mid = intval($cookie['member_id']);       // 強制整數,安全
$pid = $cookie['pass_hash'];               // 直接用 Cookie 值,未過濾!
$sql = "SELECT * FROM ibf_members WHERE id=$mid AND password='$pid'";
// 攻擊:修改 Cookie pass_hash = ' OR '1'='1
// 產生: ... WHERE id=123 AND password='' OR '1'='1'

教訓:任何外部輸入 (含 Cookie、Header、Session 讀取值) 進入 SQL 必須視為不信任,採用參數化查詢。

四、 核心防禦原則:查詢與資料分離

1. 參數化查詢 / 預處理語句 (唯一根本解法)

SQL 邏輯先編譯,資料後綁定,資料永不被解析為 SQL 指令。


// PDO (推薦)
$stmt = $pdo->prepare("SELECT * FROM users WHERE username = :username AND status = 1");
$stmt->execute([':username' => $inputUsername]);
$user = $stmt->fetch();

// MySQLi
$stmt = $mysqli->prepare("SELECT * FROM users WHERE username = ?");
$stmt->bind_param("s", $inputUsername);
$stmt->execute();

2. 所有值在 SQL 中必須加引號 (字串型)

即使使用跳脫函數,WHERE id = $id (無引號) 仍可被 1 OR 1=1 注入。正確:WHERE id = '$id' (配合跳脫/預處理)。

3. 嚴格型別轉換與白名單驗證 (輔助防線)

  • 整數:$id = (int)$_GET['id']; 或 intval()。
  • 浮點數:$price = (float)$_POST['price'];。
  • 列舉/識別碼:in_array($sort, ['asc','desc'], true),in_array($col, $allowedColumns, true)。
  • 字串長度/格式:strlen(), preg_match() 白名單正則。
  • 禁止分號:if (strpos($input, ';') !== false) die(); (針對極少數無法用預處理的舊代碼)。

4. 資料庫層:最小權限帳號

  • Web 應用帳號:GRANT SELECT, INSERT, UPDATE, DELETE ON db.* TO 'webuser'@'localhost'。
  • 嚴禁:FILE (導出/讀取檔案)、GRANT, SUPER, PROCESS, SHUTDOWN, CREATE USER。
  • 不同信任層級模組 (前台/後台/API) 使用不同帳號。

5. 環境層:錯誤隱藏與危險函數禁用

; php.ini 生產環境建議
display_errors = Off
log_errors = On
error_log = /var/log/php/error.log
disable_functions = exec,system,passthru,shell_exec,proc_open,popen,fsockopen,pcntl_exec,...
; 視專案需求保留必要函數

6. 架構層:WAF 與自動化掃描

  • 部署 ModSecurity + OWASP Core Rule Set (CRS) 攔截已知攻擊特徵。
  • CI/CD 整合靜態分析 (PHPStan, Psalm, SonarQube) 偵測 SQL 拼接模式。
  • 定期滲透測試驗證防禦有效性。

五、 針對舊代碼無法立即改用預處理的過渡修補

僅作為臨時緩解,必須規劃重構時程。


function safeQuery($mysqli, $sql, $params, $types) {
    $stmt = $mysqli->prepare($sql);
    if ($params) $stmt->bind_param($types, ...$params);
    $stmt->execute();
    return $stmt;
}

// 將舊代碼
// $sql = "SELECT * FROM t WHERE name = '$name' AND age > $age";
// 改為
$stmt = safeQuery($mysqli, "SELECT * FROM t WHERE name = ? AND age > ?", [$name, $age], 'si');

若極少數場景無法用預處理 (如動態 ORDER BY 欄位),必須嚴格白名單驗證:


$allowed = ['id','name','created_at'];
$order = in_array($_GET['order'], $allowed, true) ? $_GET['order'] : 'id';
$sql = "SELECT * FROM t ORDER BY `$order`"; // 反引號包裹識別碼

六、 總結:防禦檢查清單

  • [ ] 所有 SQL 查詢使用 PDO/MySQLi 預處理語句 (無例外)。
  • [ ] 字串型參數在 SQL 中被單引號包裹 (預處理自動處理)。
  • [ ] 整數/浮點數參數強制型別轉換。
  • [ ] 動態識別碼 (表名/欄位名/排序欄位) 嚴格白名單驗證。
  • [ ] 資料庫連線帳號最小權限 (無 FILE/GRANT/SUPER)。
  • [ ] 生產環境 display_errors = Off,錯誤寫入日誌。
  • [ ] 部署 WAF (ModSecurity/Cloud WAF) 提供虛擬補丁。
  • [ ] 定期代碼審計與滲透測試。
SQL 注入防禦的核心唯有一條:**永遠不要將不可信資料拼接進 SQL 字串**。參數化查詢是實現此原則的標準工具。其他措施 (跳脫、驗證、權限、WAF) 皆為輔助纏深度防禦,無法替代參數化查詢。
  1. 上一篇文章:
  2. 下一篇文章:
Copyright © 程式碼之家 All Rights Reserved