名詞解釋

什麼是 SQL injection(SQLi):讓輸入能改寫資料庫命令的漏洞

SQL injection(SQLi)是讓使用者輸入改寫資料庫命令(SQL)含義的漏洞,會導致資料被讀取、竄改或全部刪除。圖解原理,並從防禦視角說明佔位符(prepared statement)、ORM 與最小權限資料庫。

發布於 2026-06-08 更新於 2026-10-06 閱讀時間 4 分鐘
本頁內容

SQL injection 是指在搜尋框裡輸入的文字,成了資料庫「命令的一部分」。本文講解它的原理與可靠的防禦方法(不公開攻擊手法)。

為什麼會發生(原理)

危險的根源在於用字串串接來組裝 SQL 語句。一旦「把輸入直接混進語句裡」,輸入就會越過「值」的邊界,作為「命令」生效。

✗ 字串串接(危險)

把輸入直接拼進語句 → 輸入可能被解釋為命令的一部分

✓ 佔位符(安全)

值作為「資料」傳入 ? / $1 等佔位框 → 始終只是值

用字串串接混入輸入,就會越過「值的邊界」、改變命令的含義。而佔位符能讓值始終只是值。

受害範圍是「那個資料庫連線能做的一切」。一旦能觸及內部資料庫,它就和 RCE、SSRF 並列,成為大規模外洩的典型原因。

防禦

1

使用佔位符(prepared statement)(最重要)

組裝 SQL 時不使用字串串接。值透過 ? / 具名參數等作為「資料」傳入。僅此一項,就能讓這一類漏洞幾乎消失。

2

交給 ORM/查詢建構器處理

多數 ORM 預設使用佔位符。越少自己寫裸 SQL 就越安全。即便必須寫裸 SQL,也務必參數化。

3

把資料庫使用者設為最小權限

不要給應用用的資料庫使用者多餘的權限(DROP、其他資料表、管理操作)。即使萬一被攻破,也能縮小受害範圍。

4

輸入校驗作為「輔助」

對型別、長度、格式做校驗是有效的,但不要把它單獨當作防禦的主力。它是佔位符之外的補充。

本站觀點:從根本上不再「手工拼裸 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 的資料庫使用者只擁有所需資料表與欄位的最小權限

如何檢查自己的環境

不需要輸入攻擊字串來測試。從程式碼與設定就能知道需要的資訊。

1

找出用字串組裝的 SQL

在程式碼中搜尋 SQL 關鍵字(SELECT、WHERE 等)與字串串接或樣板字串同時出現的行。也要確認沒有把輸入串接進裸 SQL 函式($queryRawUnsafe、whereRaw、DB::raw、raw() 等)。

2

寫下資料庫使用者能做什麼

確認 App 的資料庫使用者能存取哪些資料表、能在上面做什麼(MySQL 用 SHOW GRANTS,PostgreSQL 用 information_schema.role_table_grants)。移除不需要的刪除權限,以及對其他 App 資料表的存取。

3

把使用中的產品與漏洞資料比對

用 CVE / KEV 查詢查看所營運產品的已知漏洞,以及是否正被利用。相依套件可以用 osv-scanner 一次檢查。

4

確認 SQL 錯誤不會顯示在畫面上

在正式環境中,確認資料庫的錯誤訊息不會原樣顯示給使用者。詳細的錯誤只留在紀錄中。

5

在紀錄中尋找跡象

檢查資料庫的語法錯誤是否突然增加,以及 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(根本解決與緩解措施的區分)

接下來閱讀

分類編號

CWE 對造成漏洞的弱點類型進行分類,MITRE ATT&CK 對攻擊者的技術進行分類。兩者皆由 MITRE 維護,廣泛用於標註漏洞資訊與偵測規則。

CWE™: Copyright © 2006–2026, The MITRE Corporation. CWE is a trademark of The MITRE Corporation. The MITRE Corporation hereby grants you a non-exclusive, royalty-free license to use CWE for research, development, and commercial purposes. Any copy you make for such purposes is authorized on the condition that you reproduce MITRE’s copyright designation and this license in any such copy. CWE Terms of Use

© 2026 The MITRE Corporation. This work is reproduced and distributed with the permission of The MITRE Corporation. MITRE ATT&CK and ATT&CK are registered trademarks of The MITRE Corporation. The MITRE Corporation (MITRE) hereby grants you a non-exclusive, royalty-free license to use ATT&CK® for research, development, and commercial purposes. Any copy you make for such purposes is authorized provided that you reproduce MITRE's copyright designation and this license in any such copy. ATT&CK Terms of Use

FAQ

QSQL injection 會造成什麼後果?
A

攻擊者可以改變資料庫查詢的含義,讀取本不該看到的資料、竄改資料,最糟時甚至能全部刪除或繞過身分驗證。它是個人資訊大規模外洩的典型原因之一。

Q最可靠的防禦是什麼?
A

就是「用佔位符(prepared statement)傳值」。組裝 SQL 語句時不使用字串串接,值始終作為「資料」透過另一條路徑傳入。這樣輸入就沒有被當作命令解釋的餘地了。多數 ORM 預設就這麼做。

Q對輸入做跳脫就夠了嗎?
A

手動跳脫容易遺漏,不推薦。真正有效的還是佔位符。此外要把資料庫使用者設為最小權限,以便萬一被突破時限制損害。

Q用了 ORM 就安全嗎?
A

一般用法下是安全的:ORM 預設以佔位符傳值。但如果把輸入串接進它們提供的裸 SQL 函式(Prisma 的 $queryRawUnsafe、Laravel 的 whereRaw 與 DB::raw、Django 的 raw 與 extra 等),即使用了 ORM,同樣的缺陷也會回來。排序欄位名等佔位符無法傳入的部分,也需要留意。

QWAF 能擋下 SQL injection 嗎?
A

能幫助減輕損害,但不是根本解決。日本 IPA 的指南《安全なウェブサイトの作り方》也把根本解決(佔位符)與只能減輕損害的緩解措施分開。WAF 屬於緩解措施,有些輸入會漏過偵測。請把它當作程式碼修好之前的權宜之計。

Q怎麼知道我用的產品有沒有 SQL injection?
A

用產品名稱與版本,比對已公開的漏洞(CVE),以及 CISA 確認已被利用的漏洞清單 KEV。本站的 CVE / KEV 查詢可以做到這件事。正如 MOVEit Transfer 與 Metabase 所示,所用產品中的 SQL injection 已不只一次導致大規模資料外洩。

Q什麼是 prepared statement?
A

一種先把 SQL 語句的形狀(值要放在哪裡)送給資料庫,之後再另外送出值的機制。放值的位置用 ? 或 $1 等符號(佔位符)標示。因為形狀先確定了,之後送出的值無論包含什麼字元,都不會被當作命令解讀。

Q什麼是盲注型 SQL injection(blind SQL injection)?
A

一種 SQL injection:資料庫內容與錯誤不會顯示在畫面上,但仍能從回應內容或回應時間的差異一點一點讀出資訊。畫面上什麼都不顯示,並不代表網站安全。原因與修正方式和一般的 SQL injection 相同:使用佔位符就無法成立。

Q隱藏錯誤訊息就能防止嗎?
A

不能。隱藏錯誤會讓缺陷更難被發現,但正如盲注型 SQL injection 所示,即使沒有顯示錯誤,資訊仍可能被讀取。錯誤本來就不該顯示在畫面上,但除此之外,仍須修正所有以字串串接組裝 SQL 的地方。

Q預存程序(stored procedure)安全嗎?
A

寫法正確的話,效果與佔位符相同。但 OWASP 提醒,如果程序內部把輸入串接起來組裝 SQL(動態 SQL),同樣的問題就會回來。請確認程序內部在做什麼。