要使用參數(shù)化查詢,您需要使用 Mysqli 或 PDO。要使用 mysqli 重寫您的示例,我們需要如下內(nèi)容。
<?php mysqli_report(MYSQLI_REPORT_ERROR | MYSQLI_REPORT_STRICT); $mysqli = new mysqli("server", "username", "password", "database_name"); $variable = $_POST["user-input"]; $stmt = $mysqli->prepare("INSERT INTO table (column) VALUES (?)"); // "s" means the database expects a string $stmt->bind_param("s", $variable); $stmt->execute();
您需要閱讀的關(guān)鍵函數(shù)是 mysqli::prepare
.
此外,正如其他人所建議的,您可能會(huì)發(fā)現(xiàn)使用諸如 PDO.
請(qǐng)注意,您詢問的案例相當(dāng)簡單,更復(fù)雜的案例可能需要更復(fù)雜的方法。特別是:
mysql_real_escape_string
不包含所需的轉(zhuǎn)義。在這種情況下,您最好通過白名單傳遞用戶的輸入,以確保只允許“安全”值通過。無論您使用哪種數(shù)據(jù)庫,避免 SQL 注入攻擊的正確方法都是將數(shù)據(jù)與 SQL 分離,這樣數(shù)據(jù)仍然是數(shù)據(jù),并且 >永遠(yuǎn)不會(huì)被 SQL 解析器解釋為命令。可以使用格式正確的數(shù)據(jù)部分創(chuàng)建 SQL 語句,但如果您完全不了解詳細(xì)信息,則應(yīng)始終使用準(zhǔn)備好的語句和參數(shù)化查詢。是與任何參數(shù)分開發(fā)送到數(shù)據(jù)庫服務(wù)器并由數(shù)據(jù)庫服務(wù)器解析的 SQL 語句。這樣攻擊者就不可能注入惡意SQL。
您基本上有兩種選擇來實(shí)現(xiàn)此目的:
使用PDO(用于任何支持的數(shù)據(jù)庫驅(qū)動(dòng)程序):
$stmt = $pdo->prepare('SELECT * FROM employees WHERE name = :name'); $stmt->execute([ 'name' => $name ]); foreach ($stmt as $row) { // Do something with $row }
使用MySQLi(用于MySQL):
從 PHP 8.2+ 開始,我們可以使用 execute_query() 在一個(gè)方法中準(zhǔn)備、綁定參數(shù)并執(zhí)行 SQL 語句:
$result = $db->execute_query('SELECT * FROM employees WHERE name = ?', [$name]); while ($row = $result->fetch_assoc()) { // Do something with $row }
最高可達(dá) PHP8.1:
$stmt = $db->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 以外的數(shù)據(jù)庫,則可以參考特定于驅(qū)動(dòng)程序的第二個(gè)選項(xiàng)(例如,pg_prepare()
和 pg_execute() 對(duì)于 PostgreSQL)。 PDO 是通用選項(xiàng)。
請(qǐng)注意,當(dāng)使用PDO訪問MySQL數(shù)據(jù)庫時(shí),真正的準(zhǔn)備好的語句默認(rèn)情況下不使用。要解決此問題,您必須禁用準(zhǔn)備語句的模擬。使用 PDO 創(chuàng)建連接的示例是:
$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);
在上面的示例中,錯(cuò)誤模式并不是絕對(duì)必要的,但建議添加它。這樣,PDO 將通過拋出 PDOException
的方式通知您所有 MySQL 錯(cuò)誤。
但是,強(qiáng)制是第一行 setAttribute()
行,它告訴 PDO 禁用模擬準(zhǔn)備好的語句并使用真實(shí)準(zhǔn)備好的語句聲明。這可以確保語句和值在發(fā)送到 MySQL 服務(wù)器之前不會(huì)被 PHP 解析(讓可能的攻擊者沒有機(jī)會(huì)注入惡意 SQL)。
雖然您可以在構(gòu)造函數(shù)的選項(xiàng)中設(shè)置字符集
,但請(qǐng)務(wù)必注意,“較舊”版本的 PHP(5.3.6 之前)默默地忽略了 DSN 中的字符集參數(shù)。
對(duì)于 mysqli,我們必須遵循相同的例程:
mysqli_report(MYSQLI_REPORT_ERROR | MYSQLI_REPORT_STRICT); // error reporting $dbConnection = new mysqli('127.0.0.1', 'username', 'password', 'test'); $dbConnection->set_charset('utf8mb4'); // charset
您傳遞給prepare
的SQL語句由數(shù)據(jù)庫服務(wù)器解析和編譯。通過指定參數(shù)(?
或命名參數(shù),如上例中的 :name
),您可以告訴數(shù)據(jù)庫引擎您要過濾的位置。然后,當(dāng)您調(diào)用 execute
時(shí),準(zhǔn)備好的語句將與您指定的參數(shù)值結(jié)合起來。
這里重要的是參數(shù)值與編譯后的語句結(jié)合在一起,而不是 SQL 字符串。 SQL 注入的工作原理是在腳本創(chuàng)建要發(fā)送到數(shù)據(jù)庫的 SQL 時(shí)欺騙腳本包含惡意字符串。因此,通過將實(shí)際的 SQL 與參數(shù)分開發(fā)送,您可以限制最終出現(xiàn)意外情況的風(fēng)險(xiǎn)。
您在使用準(zhǔn)備好的語句時(shí)發(fā)送的任何參數(shù)都將被視為字符串(盡管數(shù)據(jù)庫引擎可能會(huì)進(jìn)行一些優(yōu)化,因此參數(shù)當(dāng)然也可能最終被視為數(shù)字)。在上面的示例中,如果 $name
變量包含 'Sarah'; DELETE FROMEmployees
結(jié)果只是搜索字符串 "'Sarah'; DELETE FROMEmployees"
,并且最終不會(huì)得到 一個(gè)空表。
使用準(zhǔn)備好的語句的另一個(gè)好處是,如果您在同一個(gè)會(huì)話中多次執(zhí)行相同的語句,它只會(huì)被解析和編譯一次,從而提高速度。
哦,既然您詢問了如何進(jìn)行插入,這里有一個(gè)示例(使用 PDO):
$preparedStatement = $db->prepare('INSERT INTO table (column) VALUES (:column)'); $preparedStatement->execute([ 'column' => $unsafeValue ]);
雖然您仍然可以對(duì)查詢參數(shù)使用準(zhǔn)備好的語句,但動(dòng)態(tài)查詢本身的結(jié)構(gòu)無法參數(shù)化,并且某些查詢功能也無法參數(shù)化。
對(duì)于這些特定場(chǎng)景,最好的辦法是使用白名單過濾器來限制可能的值。
// Value whitelist // $dir can only be 'DESC', otherwise it will be 'ASC' if (empty($dir) || $dir !== 'DESC') { $dir = 'ASC'; }