搜尋

首頁  >  問答  >  主體

PHP中如何防止SQL注入攻擊?

<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粉939473759P粉939473759456 天前541

全部回覆(2)我來回復

  • P粉891237912

    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等更高級的抽象層會更有用/更容易。

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

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

    回覆
    0
  • P粉771233336

    P粉7712333362023-08-23 00:20:03

    無論您使用哪個資料庫,避免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';
    }

    回覆
    0
  • 取消回覆