SQL injection 是指在搜尋框裡輸入的文字,成了資料庫「命令的一部分」。本文講解它的原理與可靠的防禦方法(不公開攻擊手法)。
為什麼會發生(原理)
危險的根源在於用字串串接來組裝 SQL 語句。一旦「把輸入直接混進語句裡」,輸入就會越過「值」的邊界,作為「命令」生效。
✗ 字串串接(危險)
把輸入直接拼進語句 → 輸入可能被解釋為命令的一部分
✓ 佔位符(安全)
值作為「資料」傳入 ? / $1 等佔位框 → 始終只是值
受害範圍是「那個資料庫連線能做的一切」。一旦能觸及內部資料庫,它就和 RCE、SSRF 並列,成為大規模外洩的典型原因。
防禦
使用佔位符(prepared statement)(最重要)
組裝 SQL 時不使用字串串接。值透過 ? / 具名參數等作為「資料」傳入。僅此一項,就能讓這一類漏洞幾乎消失。
交給 ORM/查詢建構器處理
多數 ORM 預設使用佔位符。越少自己寫裸 SQL 就越安全。即便必須寫裸 SQL,也務必參數化。
把資料庫使用者設為最小權限
不要給應用用的資料庫使用者多餘的權限(DROP、其他資料表、管理操作)。即使萬一被攻破,也能縮小受害範圍。
輸入校驗作為「輔助」
對型別、長度、格式做校驗是有效的,但不要把它單獨當作防禦的主力。它是佔位符之外的補充。
本站觀點:從根本上不再「手工拼裸 SQL」
SQLi 早已廣為人知,卻至今仍會發生,原因在於「圖方便,就把輸入混進語句裡」。本站的立場很明確:不製造把值用字串串接放進 SQL 的場景。只要把佔位符或 ORM 設為預設,就能在設計上防止這個漏洞。請不要依賴手動跳脫。
注意:佔位符只能傳值
佔位符並不能涵蓋一切。它能傳的只有值,而對**資料表名、欄位名、ORDER BY 的方向(升冪/降冪)**這類「查詢的結構」無能為力。
當你想把這部分做成動態的(例如:讓使用者選擇「排序鍵」「篩選欄位」),不要把輸入直接放進 SQL,而應從事先確定的許可清單裡挑出對應的正規名稱。「排序欄位」「過濾欄位」正是容易誤以為「佔位符已經守住了」的典型疏漏。
輸入不只是表單欄位
如果以為只有搜尋框和登入表單需要注意,就會有遺漏。可能進入 SQL 的值,會從許多途徑進來。
- HTTP 標頭(
User-Agent、X-Forwarded-For等)與 Cookie - API 收到的 JSON 中的值,或從檔案讀取的值
- 一度存進資料庫、之後被另一個處理程序組進 SQL 的值
最後一種稱為二階 SQL injection(second-order SQL injection)。即使儲存時使用了佔位符,如果之後另一個處理程序把儲存的值以字串串接組進 SQL,它仍然會成立。不要認為「這是從自己的資料庫來的,所以安全」:凡是組裝 SQL 的地方都要使用佔位符。
本站關於 X-Forwarded-For 偽造 的解說中,介紹了探測字串從標頭送進來、但因為值以佔位符傳遞而使 SQL injection 無法成立的案例。
與相似用語的差異
名稱中帶有「injection」的缺陷有一個共通點:輸入被當作另一種語言的命令解讀。差別在於輸入最後到了哪裡。
| 用語 | 輸入最後到了哪裡 | 主要損害 | 根本解決 |
|---|---|---|---|
| SQL injection | 對資料庫的命令(SQL) | 資料被讀取、竄改或刪除 | 佔位符 |
| XSS | 在使用者瀏覽器中執行的 HTML 或 JavaScript | 使用者的工作階段被濫用、頁面上出現假內容 | 輸出時跳脫與 CSP |
| OS 命令注入 | 由伺服器 shell 執行的命令 | 在伺服器上執行任意命令(RCE) | 使用不經過 shell、分開傳遞參數的 API |
SQL injection 與 XSS 常被一起提起,但損害落在不同的地方:SQL injection 打擊的是伺服器上的資料庫,XSS 打擊的是使用者的瀏覽器。
主要語言中的佔位符名稱
每種主要語言都提供分開傳遞值的方式。可以用來判斷在你的技術堆疊中該用哪個功能。
| 語言/函式庫 | 要使用的功能 |
|---|---|
| Java(JDBC) | PreparedStatement,搭配 setString 等設定值的方法 |
| PHP(PDO) | 用 prepare 準備語句,把值傳給 execute |
| Python(DB-API) | 把值作為 execute 的第二個參數傳入(不要嵌進 SQL 字串) |
| Node.js(pg 等) | 使用 $1 等編號符號,值以另一個陣列傳入 |
| C#(.NET) | 把值加到命令的 Parameters |
無論哪種語言,一旦用樣板字串或 + 把值嵌進 SQL,這層保護就會失效。
實際案例:從產品中的 SQL injection 開始的大規模外洩
SQL injection 早已廣為人知,卻至今仍造成重大外洩。近年的案例,起點往往不是自己寫的程式碼,而是自己使用的產品。
| 案例 | 發生了什麼 |
|---|---|
| MOVEit Transfer(2023) | 檔案傳輸產品中的 SQL injection(CVE-2023-34362)在修復前就被利用,2,700 多個組織的資料被竊,其中許多是經由委外業者(→ MOVEit 大規模資料外洩) |
| Metabase(2026) | 分析工具中無需認證的 SQL injection(CVE-2026-72898)在修復前被利用,多家公司公布客戶資料外洩(→ Metabase 遭利用與外洩,英文) |
| WordPress 核心(2026) | 與另一個缺陷組合後,無需認證的 SQL injection 可能導致 RCE(→ CVE-2026-63030 解說,英文) |
MOVEit 之後,美國 CISA 與 FBI 在 2024 年 3 月發出警示,敦促軟體廠商消除 SQL injection,並指出參數化查詢(佔位符)就是解法。在 MITRE 於 2025 年 12 月公布的「CWE Top 25」中,SQL injection(CWE-89)排名第二。
依你的角色,該做的事不同
| 你的角色 | 最先要做的事 |
|---|---|
| 自己寫 SQL(開發者) | 停止字串串接,改用佔位符或 ORM。檢查裸 SQL 函式的使用方式 |
| 營運產品或開源軟體(維運者) | 確認所營運產品的 CVE 與 KEV 狀態,更新到已修復的版本。減少可從網際網路連到的管理畫面 |
| 兩者共通 | 讓 App 的資料庫使用者只擁有所需資料表與欄位的最小權限 |
如何檢查自己的環境
不需要輸入攻擊字串來測試。從程式碼與設定就能知道需要的資訊。
找出用字串組裝的 SQL
在程式碼中搜尋 SQL 關鍵字(SELECT、WHERE 等)與字串串接或樣板字串同時出現的行。也要確認沒有把輸入串接進裸 SQL 函式($queryRawUnsafe、whereRaw、DB::raw、raw() 等)。
寫下資料庫使用者能做什麼
確認 App 的資料庫使用者能存取哪些資料表、能在上面做什麼(MySQL 用 SHOW GRANTS,PostgreSQL 用 information_schema.role_table_grants)。移除不需要的刪除權限,以及對其他 App 資料表的存取。
把使用中的產品與漏洞資料比對
用 CVE / KEV 查詢查看所營運產品的已知漏洞,以及是否正被利用。相依套件可以用 osv-scanner 一次檢查。
確認 SQL 錯誤不會顯示在畫面上
在正式環境中,確認資料庫的錯誤訊息不會原樣顯示給使用者。詳細的錯誤只留在紀錄中。
在紀錄中尋找跡象
檢查資料庫的語法錯誤是否突然增加,以及 WAF 或存取紀錄中是否有來自同一來源、含有類似 SQL 字串的連續請求。如果有,先檢查該路徑背後的程式碼。
出處(一手資料)
- OWASP, "SQL Injection Prevention Cheat Sheet" — cheatsheetseries.owasp.org(佔位符、預存程序的注意事項、以許可清單校驗輸入、最小權限、不依賴跳脫)
- CISA 與 FBI, "Secure by Design Alert: Eliminating SQL Injection Vulnerabilities in Software"(2024 年 3 月 25 日)— cisa.gov
- MITRE, "2025 CWE Top 25 Most Dangerous Software Weaknesses"(2025 年 12 月)— cwe.mitre.org(CWE-89 排名第二)
- IPA「安全なウェブサイトの作り方」(日文)— ipa.go.jp(根本解決與緩解措施的區分)
接下來閱讀
- 術語:什麼是 RCE · 什麼是 XSS · 什麼是 OWASP Top 10
- 解說:X-Forwarded-For 偽造與受信任代理的設定
- 入門:安全基礎