SQL Injection 完全指南

深入 SQL 注入:攻擊原理與類型、參數化查詢、ORM、最小權限等防護,以及無法參數化的動態拼接陷阱

屬於 OWASP Top 10 的 A03 注入;與 XSS 同類但目標是資料庫。資料庫基礎見 交易與 ACID索引


目錄


什麼是 SQL Injection?

SQL Injection(SQL 注入) 是把惡意 SQL 片段透過使用者輸入「夾帶」進查詢,讓資料庫執行攻擊者預期外的指令。屬於 A03 注入——核心問題與 XSS 相同:把不可信輸入當成程式碼(SQL)執行

輸入框 / URL 參數 / 標頭…的內容
   → 被字串拼接進 SQL
   → 改變了 SQL 的語意 → 資料庫照樣執行

為什麼危險

資料庫通常是系統最敏感的核心,SQL 注入可能導致:

  • 讀取任意資料:拖走整個使用者表、密碼雜湊、個資
  • 繞過認證:不需密碼就登入(見下方經典範例)
  • 竄改 / 刪除資料UPDATEDELETEDROP TABLE
  • 進一步入侵:某些情況可執行系統指令(RCE)、讀檔

SQL 注入是歷史最悠久也最具破壞力的 Web 漏洞之一,一個拼接點就可能洩漏整個資料庫。


核心成因:字串拼接

幾乎所有 SQL 注入都源自把輸入字串直接拼進 SQL

// ❌ 危險:把使用者輸入直接拼進查詢
const q = "SELECT * FROM users WHERE name = '" + name + "'";

name 是正常值沒事;但若 name 含 SQL 語法,整句的「結構」就被改寫了——資料庫無法分辨「哪些是指令、哪些是資料」。


攻擊類型與範例

1. 經典:繞過登入

-- 程式:SELECT * FROM users WHERE name='<輸入>' AND password='<輸入>'
-- 輸入 name: admin'--
SELECT * FROM users WHERE name='admin'--' AND password='...'
--                                    ↑ -- 把後面註解掉,password 檢查消失
-- 輸入: ' OR '1'='1
SELECT * FROM users WHERE name='' OR '1'='1' AND password='...'
-- '1'='1' 恆真 → 回傳所有列 → 繞過

2. UNION-based:撈出其他資料

UNION 把別的表的資料接到結果裡:

-- 輸入: ' UNION SELECT username, password FROM users--
SELECT name, email FROM products WHERE name='' UNION SELECT username, password FROM users--'

3. Blind(盲注)

回應看不到資料,但能用布林時間差異一位元一位元推出資料:

-- 布林盲注:頁面有無變化推斷條件真假
... AND substring(password,1,1)='a'

-- 時間盲注:用延遲判斷真假
... AND IF(condition, SLEEP(5), 0)

防護一:參數化查詢(主防線)

最核心、最有效的防護:用參數化查詢 / prepared statement,讓資料庫把輸入永遠當「資料」,不當「指令」

// ✅ 參數化:? / $1 是佔位符,輸入只會被當值
db.query("SELECT * FROM users WHERE name = ?", [name]);          // MySQL
db.query("SELECT * FROM users WHERE name = $1", [name]);         // PostgreSQL
# Python
cursor.execute("SELECT * FROM users WHERE name = %s", (name,))
// Java PreparedStatement
PreparedStatement ps = conn.prepareStatement("SELECT * FROM users WHERE name = ?");
ps.setString(1, name);

原理:SQL 的結構(含佔位符)先送給資料庫編譯,輸入之後才綁定為純值——輸入再怎麼寫都無法改變查詢結構。

鐵則:永遠用參數化,永遠不要字串拼接使用者輸入進 SQL。 這一條就能擋掉絕大多數 SQL 注入。


防護二:ORM 與查詢建構器

ORM(如 Prisma、TypeORM、Hibernate、SQLAlchemy)與查詢建構器預設使用參數化,天生較安全:

// ORM:參數自動參數化
await prisma.user.findMany({ where: { name } });   // ✅ 安全

但要注意 raw query 出口

// ⚠️ ORM 的原生查詢若字串拼接,照樣會注入
await prisma.$queryRawUnsafe(`SELECT * FROM users WHERE name = '${name}'`); // ❌

// ✅ 用 tagged template / 參數版本
await prisma.$queryRaw`SELECT * FROM users WHERE name = ${name}`;

ORM 降低風險,但用了 ORM ≠ 免疫——只要走到字串拼接的 raw SQL 就破功。


防護三:無法參數化的部分用白名單

佔位符只能綁「值」,不能綁「識別字」(表名、欄位名、ORDER BY 方向、LIMIT 等結構部分)。這些若來自使用者,不能參數化,要用白名單

// ❌ 危險:排序欄位來自使用者,直接拼
const q = `SELECT * FROM users ORDER BY ${sortColumn} ${dir}`;

// ✅ 白名單:只允許已知合法值
const allowedCols = { name: "name", date: "created_at" };
const col = allowedCols[sortColumn] ?? "created_at";
const direction = dir === "desc" ? "DESC" : "ASC";
const q = `SELECT * FROM users ORDER BY ${col} ${direction}`;

動態決定「表/欄位/排序」時最容易出包——記得:值用參數化,結構用白名單對映


防護四:最小權限與縱深防禦

即使前面都做了,仍以多層防禦降低萬一被突破的衝擊:

  • 資料庫最小權限:應用程式帳號只給必要權限(不要用 superuser;查詢用的帳號別有 DROP/FILE 權限)
  • 輸入驗證:型別/格式驗證(如 id 必為數字)當額外一層,但不可取代參數化
  • 錯誤訊息不外洩:別把 SQL 錯誤、堆疊回給前端(會幫攻擊者探查結構)
  • WAF:可擋部分自動化注入,但只是輔助

常見誤區

1. 「我有跳脫特殊字元就安全了」

手動跳脫(escape)極易漏、且因資料庫/編碼而異,是最後手段。正解是參數化,讓你根本不必跳脫。

2. 「用了 ORM 就不會 SQL 注入」

多數情況安全,但 raw query / 字串拼接 出口照樣中招(如 $queryRawUnsafe)。

3. 「只有登入框要防」

任何進入 SQL 的輸入都要防:搜尋、排序參數、URL、API body、甚至 HTTP 標頭、Cookie。

4. 「前端擋了就好」

前端驗證可繞過,防護必須在後端(與其他注入/存取控制同理)。


常見問題

問題 1:參數化查詢為什麼能防注入?

因為查詢的結構先被資料庫編譯,輸入之後才以「純值」綁定——輸入無論含什麼 SQL 語法都不會改變查詢結構,只會被當成一個字串值比對。

問題 2:ORM 能完全防 SQL 注入嗎?

預設參數化的 ORM 能擋多數情況,但只要用到字串拼接的原生查詢(raw SQL)就可能破功。用 ORM 也要避免拼接、用其參數化 API。

問題 3:表名 / 排序欄位可以用參數化嗎?

不行。佔位符只能綁「值」,不能綁識別字(表名、欄位、ORDER BYLIMIT 等結構)。這些若來自使用者要用白名單對映,不能拼接。

問題 4:SQL Injection 和 XSS 差在哪?

同屬注入,但目標不同:SQL 注入打資料庫(執行惡意 SQL);XSS 在受害者瀏覽器執行惡意 JS。共通根因都是「把輸入當程式碼」,防法各異(參數化 vs 輸出編碼)。

問題 5:跳脫(escape)和參數化哪個好?

參數化。跳脫易漏、隨資料庫/編碼而異、容易出錯;參數化從根本上把「指令」與「資料」分離,是業界標準做法。


總結

核心要點

  • SQL Injection:把惡意 SQL 經輸入夾帶進查詢執行(A03 注入)→ 拖資料、繞認證、竄改/刪除
  • 根因:字串拼接輸入進 SQL,資料庫分不清「指令」與「資料」
  • 主防線:參數化查詢 / prepared statement——永遠別拼接使用者輸入
  • ORM 預設參數化較安全,但 raw query 拼接照樣中招
  • 結構部分(表/欄位/排序)不能參數化 → 用白名單對映
  • 縱深:DB 最小權限、輸入驗證、不外洩 SQL 錯誤

快速參考

防護 作用 定位
參數化查詢 輸入永遠當值,不當指令 主防線
ORM / 查詢建構器 預設參數化(避開 raw 拼接) 主防線
白名單對映 表/欄位/排序等結構部分 必要時
DB 最小權限 降低被突破的衝擊 縱深
輸入驗證 額外一層(不可取代參數化) 縱深

建立日期:2026-06-18

🔗相關文章