PHP中如何防止SQL注入攻擊?
P粉939473759
P粉939473759 2023-08-22 10:23:01
0
2
486
<p>如果使用者輸入未經修改地插入SQL查詢中,則應用程式將變得容易受到SQL注入攻擊,就像以下範例所示:</p> <pre class="lang-php prettyprint-override"><code>$unsafe_variable = $_POST['user_input']; mysql_query("INSERT INTO `table` (`column`) VALUES ('$unsafe_variable')"); </code></pre> <p>這是因為使用者可以輸入類似 <code>value'); DROP TABLE table;--</code> 的內容,查詢將變成:</p> <pre class="brush:php;toolbar:false;">INSERT INTO `table` (`column`) VALUES('value'); DROP TABLE table;--')</pre> <p>如何防止這種情況發生? </p>
P粉939473759
P粉939473759

全部回覆(2)
P粉891237912

要使用參數化查詢,您需要使用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等更高級的抽象層會更有用/更容易。

請注意,您提到的情況相當簡單,更複雜的情況可能需要更複雜的方法。特別是:

  • 如果您想根據使用者輸入來變更SQL的結構,參數化查詢將無法幫助您,所需的轉義不包含在mysql_real_escape_string中。在這種情況下,最好將使用者的輸入透過白名單傳遞,以確保只允許通過「安全」值。
P粉771233336

無論您使用哪個資料庫,避免SQL注入攻擊的正確方法是將資料與SQL分離,讓資料保持資料的形式,永遠不會被SQL解析器解釋為指令。可以建立具有正確格式化資料部分的SQL語句,但如果您不完全了解細節,您應該始終使用預處理語句和參數化查詢。這些是將SQL語句與任何參數分開傳送並由資料庫伺服器分析的SQL語句。這樣,攻擊者就無法注入惡意SQL。

基本上有兩種方法可以實現這一點:

  1. 使用PDO(適用於任何支援的資料庫驅動程式):

    $stmt = $pdo->prepare('SELECT * FROM employees WHERE name = :name');
    $stmt->execute([ 'name' => $name ]);
    
    foreach ($stmt as $row) {
        // 对$row进行操作
    }
    
  2. 使用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

請注意,當使用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,我們需要遵循相同的例程:

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';
}
熱門教學
更多>
最新下載
更多>
網站特效
網站源碼
網站素材
前端模板
關於我們 免責聲明 Sitemap
PHP中文網:公益線上PHP培訓,幫助PHP學習者快速成長!