PHP中如何防止SQL注入

這是 StackOverFlow 上一個投票非常多的提問  How to prevent SQL injection in PHP?    我把問題和贊同最多的答題翻譯了下來。

提問 : 如果用戶的輸入能直接插入到SQL語句中,那麼這個應用就易收到SQL注入的***,舉個例子:
$unsafe_variable = $_POST['user_input'];
mysqli_query("INSERT INTO table (column) VALUES ('" . $unsafe_variable . "')");
用戶可以輸入諸如 :   value'); DROP TABLE table;--   ,SQL語句就變成這樣了:
INSERT INTO table (column) VALUES('value'); DROP TABLE table;--')
(譯者注:這樣做的結果就是把table表給刪掉了) 我們可以做什麼去阻止這種情況呢?
回答:
使用 prepared statements ( 預處理語句 )和參數化的查詢。這些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 :
$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
}
PDO
需要注意的是使用PDO去訪問MySQL數據庫時,真正的prepared statements默認情況下是不使用的。爲了解決這個問題,你需要禁用模擬的prepared statements。下面是使用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);
上面的例子中,錯誤報告模式並不是強制和必須的,但建議你還是添加它。通過這種方式,腳本在出問題的時候不會被一個致命錯誤終止,而是拋出PDO Exceptions,這就給了開發者機會去捕獲這個錯誤。
然而第一行的 setAttribute() 是強制性的,它使得PDO禁用模擬的prepared statements並使用真正的prepared statements。這可以確保這些語句和值在被髮送到MySQL服務器之前不會被PHP解析(這使得***者沒有注入惡意SQL的機會)。
儘管你可以使用可選的構造函數參數去設置 charset ,但重點需要注意的是小於5.3.6的PHP版本,DSN(Data Source Name)是默認忽略 charset 參數的。
說明
當你傳一個SQL語句做預處理時會發生什麼?它被數據庫服務器解析和編譯了。通過指定參數(通過之前例子中的 ? 或者像 :name 這樣的命名式參數)你告訴數據庫引擎你是想過濾它。接着當你調用 execute() 函數時,prepared statements會和你剛纔指定的參數的值結合。
在此重要的是,參數的值是和編譯過的語句結合,而非一個SQL字符串。SQL注入就是當創建被髮送到數據庫的SQL語句時,通過欺騙的手段讓腳本去引入惡意的字符串。因此當你使用單獨的參數發送真實正確的SQL時,你就限制了被某些不是你真實意圖的事情而搞掛掉的風險。使用prepared statements 傳遞的任何參數都會被當做字符串對待(不過數據庫引擎可能會做一些優化,這些參數最終也可能變成numbers)(譯者注:意思就是把參數當做一個字符串而不會去做額外的行爲)。比如在上面的例子中,如果 $name 變量的值是 'Sarah'; DELETE * FROM employees  ,產生的結果是會去搜索"'Sarah'; DELETE * FROM employees"這一整個字符串,最終的結果你也就不會面對的是一張空表了。
使用prepared statements的另一個好處是,如果你在同一session中再次執行相同的語句,也就不會被再次解析和編譯,這樣你就獲得一些速度上的提升。
哦,既然你問的是插入操作該如果防止,下面給出一個例子(使用PDO):
$preparedStatement = $db->prepare('INSERT INTO table (column) VALUES (:column)');
$preparedStatement->execute(array(':column' => $unsafeValue));
翻譯完, 本人翻譯水平有限,請讀者見諒。


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