使用引數化查詢防止 SQL 注入

SQL 注入是一種攻擊,允許惡意使用者修改 SQL 查詢,向其中新增不需要的命令。例如,以下程式碼易受攻擊

// Do not use this vulnerable code!
$sql = 'SELECT name, email, user_level FROM users WHERE userID = ' . $_GET['user'];
$conn->query($sql);

這允許該指令碼的任何使用者基本上隨意修改我們的資料庫。例如,考慮以下查詢字串:

page.php?user=0;%20TRUNCATE%20TABLE%20users;

這使我們的示例查詢看起來像這樣

SELECT name, email, user_level FROM users WHERE userID = 0; TRUNCATE TABLE users;

雖然這是一個極端的例子(大多數 SQL 注入攻擊的目的不是刪除資料,大多數 PHP 查詢執行函式也不支援多查詢),但這是一個示例,說明如何通過粗心組裝來實現 SQL 注入攻擊。查詢。不幸的是,像這樣的攻擊很常見,並且由於編碼人員沒有采取適當的預防措施來保護他們的資料,因此非常有效。

為防止 SQL 注入發生,建議的語句是推薦的解決方案。不是將使用者資料直接連線到查詢,而是使用佔位符。然後單獨傳送資料,這意味著 SQL 引擎不會將使用者資料混淆為一組指令。

雖然這裡的主題是 PDO,但請注意 PHP MySQLi 擴充套件還支援準備語句

PDO 支援兩種佔位符(佔位符不能用於列名或表名,只能用於值):

  1. 命名佔位符。冒號(:),後跟一個不同的名稱(例如:user

    // using named placeholders
    $sql = 'SELECT name, email, user_level FROM users WHERE userID = :user';
    $prep = $conn->prepare($sql);
    $prep->execute(['user' => $_GET['user']]); // associative array
    $result = $prep->fetchAll();
    
  2. 傳統的 SQL 位置佔位符,表示為 ?

    // using question-mark placeholders
    $sql = 'SELECT name, user_level FROM users WHERE userID = ? AND user_level = ?';
    $prep = $conn->prepare($sql);
    $prep->execute([$_GET['user'], $_GET['user_level']]); // indexed array
    $result = $prep->fetchAll();
    

如果你需要動態更改表名或列名,請知道這是你自己的安全風險和不良做法。雖然,它可以通過字串連線來完成。提高此類查詢安全性的一種方法是設定允許值表,並將要連線的值與此表進行比較。

請注意,僅通過 DSN 設定連線字符集很重要,否則如果使用某些奇數編碼,你的應用程式可能容易出現模糊的漏洞 。對於 5.3.6 之前的 PDO 版本,通過 DSN 設定字符集不可用,因此唯一的選擇是在建立後立即在連線上將 PDO::ATTR_EMULATE_PREPARES 屬性設定為 false

$conn->setAttribute(PDO::ATTR_EMULATE_PREPARES, false);

這會導致 PDO 使用底層 DBMS 的本機預處理語句,而不僅僅是模擬它。

但是,請注意,PDO 將無聲地回退到模擬 MySQL 本身無法準備的語句:它可以在手冊列出原始碼 )。