[stackoverflow翻譯] PHP中如何防止SQL注入?

問:

如果用戶使用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);
    
    $stmt->execute();
    
    $result = $stmt->get_result();
    while ($row = $result->fetch_assoc()) {
        // do something with $row
    }

如果你是連接到除Mysql以外的數據庫, there is a driver-specific second option that you can refer to (e.g. pg_prepare() and pg_execute() for PostgreSQL). PDO 是通用的選擇.

正確設置連接

記住當使用 PDO 去連接一個MySQL數據庫時,a MySQL database real prepared statements are not used by default. 你必須禁用已有的語句來修復它. 一個使用PDO創建連接的例子是:

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

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

在上述的例子中,錯誤模塊不是必須的,但是我們建議去添加它.這樣當發生一些錯誤時,腳本不會拋出 Fatal Error 而停止,他給了開發者機會去 catch 那些throw  n as PDOException異常.

第一行 setAttribute() 是強制的, 他告訴PDO不使用預先準備的參數語句而使用當前傳入的參數. 這確保指令和語句在發送給MySQL服務之前不會被PHP解析. (不留任何注入惡意SQL的機會給可能的攻擊者).

儘管你可以設置 charset字符集作爲構造函數的參數選項, 但老版的PHP(< 5.3.6)會DSN中忽略字符集參數.

解釋

What happens is that the SQL statement you pass to prepare is parsed and compiled by the database server. 你通過傳遞特殊的變量(如一個 ? 或一個已命名的變量如上述例子中的 :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';
}
share

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章