P粉8912379122023-08-23 00:21:40
要使用參數化查詢,您需要使用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
。
此外,正如其他人建議的那樣,您可能會發現使用PDO等更高級的抽象層會更有用/更容易。
請注意,您提到的情況相當簡單,更複雜的情況可能需要更複雜的方法。特別是:
mysql_real_escape_string
中。在這種情況下,最好將使用者的輸入透過白名單傳遞,以確保只允許通過「安全」值。 P粉7712333362023-08-23 00:20:03
無論您使用哪個資料庫,避免SQL注入攻擊的正確方法是將資料與SQL分離,讓資料保持資料的形式,永遠不會被SQL解析器解釋為指令。可以建立具有正確格式化資料部分的SQL語句,但如果您不完全了解細節,您應該始終使用預處理語句和參數化查詢。這些是將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()
方法來準備、綁定參數和執行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解析(使潛在的攻擊者無法注入惡意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與參數分開發送,可以限制意外結果的風險。
使用預處理語句傳送的任何參數都將被視為字串(儘管資料庫引擎可能會對參數進行一些最佳化,因此參數最終可能是數字)。在上面的範例中,如果$name
變數包含'Sarah'; DELETE FROM employees
,結果將僅是搜尋字串"'Sarah'; DELETE FROM employees"
,您將不會得到一個空表。
使用預處理語句的另一個好處是,如果在同一會話中執行多次相同的語句,它只會被解析和編譯一次,從而提高一些速度。
哦,既然您問到如何對插入進行操作,這裡有一個範例(使用PDO):
$preparedStatement = $db->prepare('INSERT INTO table (column) VALUES (:column)');
$preparedStatement->execute([ 'column' => $unsafeValue ]);
雖然您仍然可以對查詢參數使用預處理語句,但動態查詢本身的結構無法進行參數化,並且某些查詢功能也無法進行參數化。
對於這些特定的場景,最好的做法是使用白名單過濾器來限制可能的值。
// 值白名单 // $dir只能是'DESC',否则将为'ASC' if (empty($dir) || $dir !== 'DESC') { $dir = 'ASC'; }