PHP Conference Japan 2024

SQL 注入

SQL 注入是一種技術,攻擊者會利用應用程式程式碼中負責建構動態 SQL 查詢的缺陷。攻擊者可以獲得應用程式的特權部分的存取權,從資料庫中檢索所有資訊,篡改現有資料,甚至在資料庫主機上執行危險的系統級命令。當開發人員在 SQL 陳述式中串連或內插任意輸入時,就會發生此漏洞。

範例 #1 將結果集分割成頁面... 並建立超級使用者 (PostgreSQL)

在以下範例中,使用者輸入直接內插到 SQL 查詢中,允許攻擊者在資料庫中獲得超級使用者帳戶。

<?php

$offset
= $_GET['offset']; // 注意,沒有輸入驗證!
$query = "SELECT id, name FROM products ORDER BY name LIMIT 20 OFFSET $offset;";
$result = pg_query($conn, $query);

?>
一般使用者會點擊 'next'、'prev' 連結,其中 $offset 會編碼到 URL 中。腳本預期傳入的 $offset 是一個數字。但是,如果有人嘗試透過將以下內容附加到 URL 來闖入會怎麼樣?
0;
insert into pg_shadow(usename,usesysid,usesuper,usecatupd,passwd)
    select 'crack', usesysid, 't','t','crack'
    from pg_shadow where usename='postgres';
--
如果發生這種情況,腳本會向攻擊者提供超級使用者存取權。請注意,0; 是為了為原始查詢提供有效的偏移量並終止它。

注意:

常見的技術是強制 SQL 解析器使用 -- 來忽略開發人員編寫的查詢其餘部分,這是 SQL 中的註解符號。

一種可行的獲取密碼的方式是規避您的搜尋結果頁面。攻擊者唯一需要做的就是查看 SQL 陳述式中是否有任何未正確處理的提交變數。這些篩選器通常可以在前面的表單中設定,以自訂 SELECT 陳述式中的 WHERE、ORDER BY、LIMITOFFSET 子句。如果您的資料庫支援 UNION 建構,攻擊者可以嘗試將整個查詢附加到原始查詢以列出任意表格中的密碼。強烈建議只儲存密碼的安全雜湊值,而不是密碼本身。

範例 #2 列出文章 ... 以及一些密碼 (任何資料庫伺服器)

<?php

$query
= "SELECT id, name, inserted, size FROM products
WHERE size = '
$size'";
$result = odbc_exec($conn, $query);

?>
查詢的靜態部分可以與另一個 SELECT 陳述式結合,以揭示所有密碼
'
union select '1', concat(uname||'-'||passwd) as name, '1971-01-01', '0' from usertable;
--

UPDATEINSERT 陳述式也容易受到此類攻擊。

範例 #3 從重設密碼 ... 到獲得更多權限 (任何資料庫伺服器)

<?php
$query
= "UPDATE usertable SET pwd='$pwd' WHERE uid='$uid';";
?>
如果惡意使用者提交值 ' or uid like'%admin%'$uid 以變更管理員的密碼,或只是將 $pwd 設定為 hehehe', trusted=100, admin='yes 以獲得更多權限,則查詢將會被扭曲
<?php

// $uid: ' or uid like '%admin%
$query = "UPDATE usertable SET pwd='...' WHERE uid='' or uid like '%admin%';";

// $pwd: hehehe', trusted=100, admin='yes
$query = "UPDATE usertable SET pwd='hehehe', trusted=100, admin='yes' WHERE
...;"
;

?>

雖然很明顯,攻擊者必須至少對資料庫架構有一些瞭解才能成功進行攻擊,但獲得此資訊通常非常簡單。例如,程式碼可能是開源軟體的一部分,並且是公開可用的。此資訊也可能會被封閉原始碼洩露 - 即使它是編碼、混淆或編譯的 - 甚至透過顯示錯誤訊息的程式碼洩露。其他方法包括使用典型的表格和欄名稱。例如,使用 'users' 表格,欄名稱為 'id'、'username' 和 'password' 的登入表單。

範例 #4 攻擊資料庫主機作業系統 (MSSQL Server)

一個令人恐懼的範例,說明如何在某些資料庫主機上存取作業系統級命令。

<?php

$query
= "SELECT * FROM products WHERE id LIKE '%$prod%'";
$result = mssql_query($query);

?>
如果攻擊者提交值 a%' exec master..xp_cmdshell 'net user test testpass /ADD' --$prod,則 $query 將會是
<?php

$query
= "SELECT * FROM products
WHERE id LIKE '%a%'
exec master..xp_cmdshell 'net user test testpass /ADD' --%'"
;
$result = mssql_query($query);

?>
MSSQL Server 執行批次中的 SQL 陳述式,包括將新使用者新增至本機帳戶資料庫的命令。如果此應用程式以 sa 身分執行,並且 MSSQLSERVER 服務以足夠的權限執行,則攻擊者現在將擁有一個可用於存取此機器的帳戶。

注意:

上面的某些範例與特定的資料庫伺服器相關,但這並不表示針對其他產品不可能進行類似的攻擊。您的資料庫伺服器可能以另一種方式同樣容易受到攻擊。

A funny example of the issues regarding SQL injection

圖片由 » xkcd 提供

避免技術

避免 SQL 注入的建議方法是透過準備好的陳述式繫結所有資料。使用參數化查詢不足以完全避免 SQL 注入,但它是向 SQL 陳述式提供輸入最簡單且最安全的方式。 WHERESETVALUES 子句中的所有動態資料常值都必須替換為佔位符。實際資料將在執行期間繫結,並與 SQL 命令分開傳送。

參數繫結只能用於資料。SQL 查詢的其他動態部分必須針對已知允許的值清單進行篩選。

範例 #5 透過使用 PDO 準備好的陳述式來避免 SQL 注入

<?php

// 動態 SQL 部分會針對預期值進行驗證
$sortingOrder = $_GET['sortingOrder'] === 'DESC' ? 'DESC' : 'ASC';
$productId = $_GET['productId'];
// SQL 使用預留位置進行準備
$stmt = $pdo->prepare("SELECT * FROM products WHERE id LIKE ? ORDER BY price {$sortingOrder}");
// 值使用 LIKE 的萬用字元提供
$stmt->execute(["%{$productId}%"]);

?>

預處理語句由 PDOMySQLi 和其他資料庫函式庫提供。

SQL 注入攻擊主要基於利用未考慮安全性的程式碼。永遠不要信任任何輸入,特別是來自客戶端的輸入,即使它來自選取方塊、隱藏的輸入欄位或 Cookie。第一個範例顯示,如此簡單的查詢可能會導致災難。

深度防禦策略涉及多種良好的程式碼編寫實務

  • 永遠不要以超級使用者或資料庫擁有者的身分連接資料庫。請務必使用具有最小權限的自訂使用者。
  • 檢查給定的輸入是否具有預期的資料類型。PHP 具有廣泛的輸入驗證函式,從在 變數函式字元類型函式中找到的最簡單函式(例如,is_numeric()ctype_digit())開始,直到Perl 相容正規表示式支援。
  • 如果應用程式期望數值輸入,請考慮使用 ctype_digit() 驗證資料,使用 settype() 靜默地變更其類型,或使用 sprintf() 使用其數值表示法。
  • 如果資料庫層不支援繫結變數,則使用資料庫特定的字串跳脫函式(例如,mysql_real_escape_string()sqlite_escape_string() 等)將傳遞至資料庫的每個非數值使用者提供的值加上引號。像 addslashes() 這樣的通用函式僅在非常特定的環境中(例如,在單一位元組字元集中禁用 NO_BACKSLASH_ESCAPES 的 MySQL)才有用,因此最好避免使用它們。
  • 請勿以任何方式印出任何資料庫特定的資訊,特別是有關綱要的資訊。另請參閱錯誤報告錯誤處理和記錄函式

除此之外,如果資料庫本身支援記錄,您也可以從在您的腳本中或由資料庫本身記錄查詢中獲益。顯然,記錄無法防止任何有害的嘗試,但它可以幫助追蹤哪個應用程式被繞過了。記錄本身沒有用,但它包含的資訊很有用。更詳細通常比更少更好。

新增註解

使用者貢獻的註解 1 則註解

Richard dot Corfield at gmail dot com
13 年前
最好的方法必須是參數化查詢。然後,無論使用者輸入什麼,資料都會以值的形式傳送到資料庫。

快速線上搜尋顯示 PHP 中有一些可能性,這很棒!即使在本網站上 - https://php.dev.org.tw/manual/en/pdo.prepared-statements.php
這也說明了這種方法在安全性和效能方面都很好的原因。
To Top