SQL 注入攻擊 (SQL Injection)

SQL Injection (SQL 注入) 是一種常見的網站安全漏洞,攻擊者透過在輸入欄位中插入惡意的 SQL 程式碼,來操控資料庫執行非預期的查詢,可能導致資料外洩、資料被竄改甚至整個資料庫被刪除。

SQL Injection 攻擊原理

假設有一個登入功能,後端程式這樣組合 SQL:

-- 程式碼 (有漏洞)
query = "SELECT * FROM users WHERE username = '" + username + "' AND password = '" + password + "'";

正常情況下,使用者輸入帳號 admin 和密碼 1234,會產生:

SELECT * FROM users WHERE username = 'admin' AND password = '1234';

但如果攻擊者在密碼欄位輸入 ' OR '1'='1,SQL 會變成:

SELECT * FROM users WHERE username = 'admin' AND password = '' OR '1'='1';

由於 '1'='1' 永遠為真,這個查詢會返回所有使用者資料,攻擊者就能繞過驗證登入系統。

常見的 SQL Injection 攻擊手法

繞過登入驗證

帳號: admin' --
密碼: (任意)

產生的 SQL:

SELECT * FROM users WHERE username = 'admin' --' AND password = '';

-- 是 SQL 註解,後面的密碼驗證被忽略了。

取得額外資料 (UNION 注入)

輸入: ' UNION SELECT username, password FROM users --

攻擊者可以利用 UNION 將其他資料表的內容一起查出來。

刪除資料

輸入: '; DROP TABLE users; --

這會導致整個 users 資料表被刪除。

如何防範 SQL Injection

1. 使用參數化查詢 (Parameterized Query)

這是最有效的防範方式。讓資料庫區分「指令」和「資料」:

PHP (PDO):

$stmt = $pdo->prepare('SELECT * FROM users WHERE username = ? AND password = ?');
$stmt->execute([$username, $password]);

Python:

cursor.execute("SELECT * FROM users WHERE username = %s AND password = %s", (username, password))

Java:

PreparedStatement stmt = conn.prepareStatement("SELECT * FROM users WHERE username = ? AND password = ?");
stmt.setString(1, username);
stmt.setString(2, password);

Node.js:

connection.query('SELECT * FROM users WHERE username = ? AND password = ?', [username, password]);

2. 使用預存程序

將 SQL 邏輯封裝在預存程序中,應用程式只傳遞參數:

CREATE PROCEDURE ValidateUser
    @username VARCHAR(50),
    @password VARCHAR(50)
AS
BEGIN
    SELECT * FROM users WHERE username = @username AND password = @password;
END;

3. 輸入驗證

對使用者輸入進行檢查和過濾:

  • 檢查資料型別 (數字、日期等)
  • 限制輸入長度
  • 過濾特殊字元 (但不能只依賴這個方法)

4. 最小權限原則

資料庫帳號只給予必要的權限,例如:

  • 網站使用的帳號不應有 DROP、ALTER 權限
  • 只開放需要存取的資料表

5. 錯誤訊息處理

不要將詳細的資料庫錯誤訊息顯示給使用者,這會洩漏資料庫結構資訊。

重點整理

防範方式有效性
參數化查詢★★★★★ 最有效
預存程序★★★★☆
輸入驗證★★★☆☆ 輔助用
權限控制★★★☆☆ 降低損害
絕對不要用字串串接的方式組合 SQL 語句,這是 SQL Injection 漏洞的主要成因。