要使用參數化查詢,您需要使用Mysqli或PDO。要使用mysqli重寫您的範例,我們需要類似以下的程式碼。
<?php mysqli_report(MYSQLI_REPORT_ERROR | MYSQLI_REPORT_STRICT); $mysqli = new mysqli("服務器", "用戶名", "密碼", "數據庫名稱"); $variable = $_POST["user-input"]; $stmt = $mysqli->prepare("INSERT INTO 表名 (列名) VALUES (?)"); // "s"表示數據庫期望一個字符串 $stmt->bind_param("s", $variable); $stmt->execute();
您可能需要閱讀的關鍵函數是mysqli::prepare
。
此外,正如其他人建議的那樣,您可能會發(fā)現使用PDO等更高級的抽象層會更有用/更容易。
請注意,您提到的情況相當簡單,更複雜的情況可能需要更複雜的方法。特別是:
mysql_real_escape_string
中。在這種情況下,最好將使用者的輸入透過白名單傳遞,以確保只允許通過「安全」值。 無論您使用哪個資料庫,避免SQL注入攻擊的正確方法是將資料與SQL分離,讓資料保持資料的形式,永遠不會被SQL解析器解釋為指令??梢越⒕哂姓_格式化資料部分的SQL語句,但如果您不完全了解細節(jié),您應該始終使用預處理語句和參數化查詢。這些是將SQL語句與任何參數分開傳送並由資料庫伺服器分析的SQL語句。這樣,攻擊者就無法注入惡意SQL。
基本上有兩種方法可以實現這一點:
使用PDO(適用於任何支援的資料庫驅動程式):
$stmt = $pdo->prepare('SELECT * FROM employees WHERE name = :name'); $stmt->execute([ 'name' => $name ]); foreach ($stmt as $row) { // 對$row進行操作 }
使用MySQLi(適用於MySQL):
自PHP 8.2 以來,我們可以使用execute_query()
方法來準備、綁定參數和執(zhí)行SQL語句:
$result = $db->execute_query('SELECT * FROM employees WHERE name = ?', [$name]); while ($row = $result->fetch_assoc()) { // 對$row進行操作 }
在PHP8.1之前:
$stmt = $db->prepare('SELECT * FROM employees WHERE name = ?'); $stmt->bind_param('s', $name); // 's'指定變量類型 => 'string' $stmt->execute(); $result = $stmt->get_result(); while ($row = $result->fetch_assoc()) { // 對$row進行操作 }
如果您連接的是MySQL以外的資料庫,可以參考特定於驅動程式的第二個選項(例如,對於PostgreSQL,可以使用pg_prepare()
和pg_execute()
)。 PDO是通用選項。
請注意,當使用PDO存取MySQL資料庫時,預設會不會使用真正的預處理語句。為了解決這個問題,您需要停用預處理語句的模擬。以下是使用PDO建立連線的範例:
$dbConnection = new PDO('mysql:dbname=dbtest;host=127.0.0.1;charset=utf8mb4', 'user', 'password'); $dbConnection->setAttribute(PDO::ATTR_EMULATE_PREPARES, false); $dbConnection->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
在上面的範例中,錯誤模式並不是嚴格必要的,但建議添加它。這樣PDO將透過拋出PDOException
來通知您所有的MySQL錯誤。
然而,必須的是第一行setAttribute()
,它告訴PDO禁用模擬的預處理語句並使用真正的預處理語句。這樣確保語句和值在傳送到MySQL伺服器之前不會由PHP解析(使?jié)撛诘墓粽邿o法注入惡意SQL)。
雖然您可以在建構函式的選項中設定charset
,但需要注意的是,「舊版」PHP(5.3.6之前)在DSN中靜默忽略了charset參數。
對於mysqli,我們需要遵循相同的例程:
mysqli_report(MYSQLI_REPORT_ERROR | MYSQLI_REPORT_STRICT); // 錯誤報告 $dbConnection = new mysqli('127.0.0.1', 'username', 'password', 'test'); $dbConnection->set_charset('utf8mb4'); // 字符集
您傳遞給prepare
的SQL語句由資料庫伺服器解析和編譯。透過指定參數(在上面的範例中,可以是?
或命名參數,例如:name
),您告訴資料庫引擎您要在哪裡進行過濾。然後,當您呼叫execute
時,準備好的語句將與指定的參數值組合。
這裡重要的是參數值與編譯後的語句組合,而不是與SQL字串組合。 SQL注入是透過欺騙腳本在建立要傳送到資料庫的SQL時包含惡意字串來運作的。因此,透過將實際的SQL與參數分開發(fā)送,可以限制意外結果的風險。
使用預處理語句傳送的任何參數都將被視為字串(儘管資料庫引擎可能會對參數進行一些最佳化,因此參數最終可能是數字)。在上面的範例中,如果$name
變數包含'Sarah'; DELETE FROM employees
,結果將僅是搜尋字串"'Sarah'; DELETE FROM employees"
,您將不會得到一個空表。
使用預處理語句的另一個好處是,如果在同一會話中執(zhí)行多次相同的語句,它只會被解析和編譯一次,從而提高一些速度。
哦,既然您問到如何對插入進行操作,這裡有一個範例(使用PDO):
$preparedStatement = $db->prepare('INSERT INTO table (column) VALUES (:column)'); $preparedStatement->execute([ 'column' => $unsafeValue ]);
雖然您仍然可以對查詢參數使用預處理語句,但動態(tài)查詢本身的結構無法進行參數化,並且某些查詢功能也無法進行參數化。
對於這些特定的場景,最好的做法是使用白名單過濾器來限制可能的值。
// 值白名單 // $dir只能是'DESC',否則將為'ASC' if (empty($dir) || $dir !== 'DESC') { $dir = 'ASC'; }