首頁  >  問答  >  主體

如何保護 PHP 免受 SQL 注入?

如果使用者輸入未經修改就插入到 SQL 查詢中,則應用程式很容易受到 SQL 注入的攻擊,如下例所示:

$unsafe_variable = $_POST['user_input']; 

mysql_query("INSERT INTO `table` (`column`) VALUES ('$unsafe_variable')");

這是因為使用者可以輸入類似 value') 的內容; DROP TABLE table;--,查詢變成:

INSERT INTO `table` (`column`) VALUES('value'); DROP TABLE table;--')

可以採取什麼措施來防止這種情況發生?

P粉909476457P粉909476457398 天前695

全部回覆(2)我來回復

  • P粉754477325

    P粉7544773252023-10-09 13:25:04

    要使用參數化查詢,您需要使用 Mysqli 或 PDO。要使用 mysqli 重寫您的範例,我們需要如下內容。

    prepare("INSERT INTO table (column) VALUES (?)");
    // "s" means the database expects a string
    $stmt->bind_param("s", $variable);
    $stmt->execute();

    您需要閱讀的關鍵函數是 mysqli::prepare.

    此外,正如其他人所建議的,您可能會發現使用諸如 PDO< 之类的东西来提升抽象层很有用/更容易/a>.

    請注意,您詢問的案例相當簡單,更複雜的案例可能需要更複雜的方法。特別是:

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

    回覆
    0
  • P粉285587590

    P粉2855875902023-10-09 00:04:52

    無論您使用哪種資料庫,避免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) {
          // Do something with $row
      }
      
    2. 使用MySQLi(用於MySQL):
      從 PHP 8.2 開始,我們可以使用 < code>execute_query() 在一個方法中準備、綁定參數並執行 SQL 語句:

      $result = $db->execute_query('SELECT * FROM employees WHERE name = ?', [$name]);
       while ($row = $result->fetch_assoc()) {
           // Do something with $row
       }
      

      最高可達 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 以外的資料庫,則可以參考特定於驅動程式的第二個選項(例如,pg_prepare()pg_execute()< /code> 對於PostgreSQL) 。 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)。

    雖然您可以在建構函數的選項中設定字元集,但請務必注意,「較舊」版本的PHP(5.3.6 之前)默默地忽略了DSN 中的字符集參數

    Mysqli

    對於 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語句由資料庫伺服器解析和編譯。透過指定參數(? 或命名參數,如上例中的 :name),您可以告訴資料庫引擎您要過濾的位置。然後,當您呼叫 execute 時,準備好的語句將與您指定的參數值結合。

    這裡重要的是參數值與編譯後的語句結合在一起,而不是 SQL 字串。 SQL 注入的工作原理是在腳本建立要傳送到資料庫的 SQL 時欺騙腳本包含惡意字串。因此,透過將實際的 SQL 與參數分開傳送,您可以限制最終出現意外結果的風險。

    您在使用準備好的語句時發送的任何參數都會被視為字串(儘管資料庫引擎可能會進行一些最佳化,因此參數當然也可能最終被視為數字)。在上面的範例中,如果$name 變數包含'Sarah'; DELETE FROMEmployees 結果只是搜尋字串"'Sarah'; DELETE FROMEmployees",並且最終不會得到一個空表

    使用準備好的語句的另一個好處是,如果您在同一個會話中多次執行相同的語句,它只會被解析和編譯一次,從而提高速度。

    哦,既然您詢問瞭如何進行插入,這裡有一個範例(使用 PDO):

    $preparedStatement = $db->prepare('INSERT INTO table (column) VALUES (:column)');
    
    $preparedStatement->execute([ 'column' => $unsafeValue ]);
    

    準備好的語句可以用於動態查詢嗎?

    雖然您仍然可以對查詢參數使用準備好的語句,但動態查詢本身的結構無法參數化,而且某些查詢功能也無法參數化。

    對於這些特定場景,最好的方法是使用白名單過濾器來限制可能的值。

    // Value whitelist
    // $dir can only be 'DESC', otherwise it will be 'ASC'
    if (empty($dir) || $dir !== 'DESC') {
       $dir = 'ASC';
    }

    回覆
    0
  • 取消回覆