問題

如果在不修改的情況下插入使用者輸入到SQL查詢中,那麼應用程式就會容易受到 SQL注入的影響,如下示例所示:

 $unsafe_variable = $_POST['user_input']; 

mysql_query("INSERT INTO `table` (`column`) VALUES ('$unsafe_variable')");
 

這是因為使用者可以輸入像value'); DROP TABLE table;--這樣的東西,查詢變成:

 INSERT INTO `table` (`column`) VALUES('value'); DROP TABLE table;--')
 

可以做些什麼來防止這種情況發生?

  最佳答案

使用已準備的語句和引數化查詢.這些是資料庫伺服器傳送到和解析的SQL語句,與任何引數分開.這樣攻擊者就不可能注入惡意的SQL.

您基本上有兩個選項來實現這一目標:

  1. 使用 PDO (對於任何支援的資料庫驅動程式):

     $stmt = $pdo->prepare('SELECT * FROM employees WHERE name = :name');
    
    $stmt->execute(array('name' => $name));
    
    foreach ($stmt as $row) {
        // Do something with $row
    }
     
  2. 使用 MySQLi (對於MySQL):

     $stmt = $dbConnection->prepare('SELECT * FROM employees WHERE name = ?');
    $stmt->bind_param('s', $name); // 's' specifies the variable type => 'string'
    
    $stmt->execute();
    
    $result = $stmt->get_result();
    while ($row = $result->fetch_assoc()) {
        // Do something with $row
    }
     

如果您連線到MySQL以外的資料庫,那麼您可以引用一個具體的第二個選項(例如,pg_prepare()pg_execute() for PostgreSQL). PDO是通用選項.

正確設定連線

注意,在使用PDO訪問MySQL資料庫時,預設情況下不會使用真正準備的語句.為了解決這個問題,您必須禁用已準備語句的模擬.使用PDO建立連線的示例是:

 $dbConnection = new PDO('mysql:dbname=dbtest;host=127.0.0.1;charset=utf8', 'user', 'password');

$dbConnection->setAttribute(PDO::ATTR_EMULATE_PREPARES, false);
$dbConnection->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
 

在上面的示例中,錯誤模式不是絕對必要的,但建議新增它.這樣,當出現問題時,指令碼不會停止使用Fatal Error.它給開發人員提供了catch任何錯誤的機會,throw n作為PDOExceptions.

但是,強制性是第一個setAttribute()行,它告訴PDO禁用已模擬的語句並使用真正準備的語句.這確保語句和值在傳送到MySQL伺服器之前不會被PHP解析(給可能的攻擊者注入惡意SQL的機會).

雖然您可以在建構函式的選項中設定 charset,但重要的是注意到 PHP 的“舊”版本(在 5.3.6 之前) 默默地忽略了 DSN 中的 charset 引數

解釋

傳遞給prepare的SQL語句由資料庫伺服器解析和編譯.透過指定引數(在上面的例子中指定?或指定的引數,如:name),您告訴資料庫引擎您想要過濾的位置.然後當您呼叫execute時,準備的語句與您指定的引數值結合起來.

這裡的重要事情是引數值與編譯語句而不是SQL字串結合. SQL注入透過在建立SQL以傳送到資料庫時將指令碼騙入惡意字串來工作.因此,透過將實際的SQL與引數分開發送,您將結束的風險限制為您不打算的東西.

使用準備語句時傳送的任何引數將被視為字串(雖然資料庫引擎可能做一些最佳化,所以引數也可能最終作為數字).當然,在上面的例子中,如果$name變數包含'Sarah'; DELETE FROM employees,結果將只是搜尋字串"'Sarah'; DELETE FROM employees",並且最終不會使用空表.

使用已準備的語句的另一個好處是,如果在同一個會話中多次執行相同的語句,它只會被解析和編譯一次,給你一些速度提高。

哦,既然你問如何做插入,這是一個例子(使用PDO):

 $preparedStatement = $db->prepare('INSERT INTO table (column) VALUES (:column)');

$preparedStatement->execute(array('column' => $unsafeValue));
 

準備好的語句可以用於動態查詢嗎?

雖然仍然可以為查詢引數使用已準備的語句,但動態查詢本身的結構不能引數化,某些查詢特性不能引數化。

對於這些特定的場景,最好的辦法是使用一個限制可能值的白名單過濾器。

 // Value whitelist
// $dir can only be 'DESC', otherwise it will be 'ASC'
if (empty($dir) || $dir !== 'DESC') {
   $dir = 'ASC';
}
 

  相同標籤的其他問題

phpmysqlsqlsecuritysql-injection