SQL Injection(SQL 注入)發生在應用程式把不受信任的輸入當成 SQL 指令的一部分,讓攻擊者有機會改變原本只想查詢一筆資料的語意。真正有效的防護不是持續擴充黑名單,而是讓 SQL 結構固定、外部輸入只能以資料參數傳入,再以最小資料庫權限限制失守時的影響。

先講結論:SQL Injection 是資料改變了查詢指令
正常情況下,網站應先定義固定的 SQL 指令結構,再把使用者提供的帳號、搜尋詞、篩選值或識別碼放進資料參數。SQL Injection 出現時,應用程式沒有守住這條邊界,使外部資料被資料庫解讀成運算子、條件、子查詢或其他 SQL 結構,原本的查詢意圖因此被改寫。
MITRE 將這類問題歸為 CWE-89:對 SQL 指令中的特殊元素處理不當。OWASP Top 10:2025 則把 Injection 列為 A05,並把『讓資料與指令保持分離』列為首要防護原則。這也是為什麼只過濾引號、SQL 關鍵字或特定字串無法根治:不同資料庫、編碼與查詢上下文仍可能有其他語法路徑。
安全設計的核心可以濃縮成一句話:應用程式決定查詢結構,使用者只能提供資料值。只要把這個不變條件落實在所有資料存取層,SQL Injection 的主要攻擊路徑就會被切斷。
- 固定 SQL 結構:查詢語意由程式碼或可信資料存取層定義
- 參數綁定:外部輸入以具型別的值傳入,不參與 SQL 語法解析
- 伺服器端允許清單:欄位、排序方向等無法參數化的結構由固定對照表選取
- 最小權限:應用程式資料庫帳號只擁有完成業務所需的最低權限
- 可驗證複驗:修復後不只確認畫面正常,還要證明所有同根因查詢都已改正
從 HTTP 請求到資料庫:風險在哪一段產生?
SQL Injection 並不是資料庫單方面的問題。典型資料流會經過瀏覽器或 API Client、路由與 Controller、商業邏輯、資料存取層、資料庫驅動程式,最後才到資料庫。風險通常產生在某一層把外部輸入與 SQL 字串直接串接,或把原本應為資料的值交給可以執行動態 SQL 的介面。
輸入來源也不只網址列和表單。JSON Body、GraphQL 變數、Cookie、HTTP Header、排序欄位、匯入檔案、Webhook、訊息佇列,以及先被儲存、之後再組成查詢的資料,都可能穿越信任邊界。只檢查公開搜尋框,無法代表整個網站沒有 SQL Injection。
下圖左側呈現錯誤資料流:紅色輸入直接成為查詢結構的一塊,使資料庫回傳超過預期的紀錄。檢測與修復都應沿著同一條資料流定位:輸入在哪裡進入、經過哪些轉換、由哪個 API 組成查詢、使用哪個資料庫帳號執行,以及結果如何回到使用者。
| 輸入來源 | 常見位置 | 容易漏掉的情境 |
|---|---|---|
| URL 與表單 | Query String、Path、登入、搜尋與篩選 | 數字 ID、分頁、排序與隱藏欄位也屬外部輸入 |
| API 資料 | JSON、XML、GraphQL、REST Path/Body | 巢狀欄位、批次參數與未在前端顯示的 API |
| HTTP 狀態 | Cookie、User-Agent、Referer、自訂 Header | 日誌、分析或風控流程可能把 Header 再寫入資料庫 |
| 儲存資料 | 個人資料、內容、檔案 Metadata、Webhook | 寫入時安全,但在後續報表或後台查詢被動態串接 |
| 後台與整合 | 管理介面、排程、匯入、訊息佇列 | 被視為可信的內部來源仍可能攜帶外部資料 |
SQL Injection 有哪些類型?差異在取得回饋的方式
SQL Injection 的分類常依攻擊者如何觀察結果,而不是依根因區分。無論是錯誤訊息、同一頁面的內容差異、真假條件造成的行為差異,或資料庫回應時間,根因仍是外部資料改變了查詢結構。分類的價值在於協助測試人員選擇證據與修復人員理解暴露面,而不是提供未授權攻擊操作。
有些弱點能直接在 HTTP 回應看到資料,稱為 in-band;有些只能從真假條件或時間推論,稱為 inferential/blind;少數情境會讓資料庫透過另一個通道產生互動,稱為 out-of-band。Second-order SQL Injection 則強調惡意資料先被正常儲存,直到另一個功能讀取並拼接時才觸發。
檢測時不應只追求畫面出現資料庫錯誤。正式環境可能隱藏錯誤,CDN 或 WAF 也可能改寫回應;反過來,單次變慢或錯誤頁也不等於 SQL Injection。可靠判定要有可控制的基線、重複結果、伺服器端日誌或程式碼資料流等交叉證據。
| 類型 | 測試時可能觀察到的訊號 | 判讀重點 |
|---|---|---|
| Error-based | 特定輸入造成穩定且與資料庫相關的錯誤差異 | 避免把一般驗證錯誤或代理錯誤誤判為資料庫錯誤 |
| UNION/In-band | 查詢結果結構被改變,回應出現超出原意的資料 | 確認資料確由相同查詢路徑產生,並立即停止敏感資料擷取 |
| Boolean-based Blind | 控制真假條件時,狀態、內容長度或業務結果呈一致差異 | 需排除快取、個人化、負載平衡與隨機內容 |
| Time-based Blind | 控制條件與回應延遲呈可重複的因果關係 | 需建立多次基線並設定負載與停止條件,避免影響服務 |
| Out-of-band | 資料庫或後端服務嘗試透過另一通道互動 | 僅在明確授權與隔離環境驗證,檢查資料庫網路權限 |
| Second-order | 資料寫入時無異常,之後在報表、後台或批次查詢中觸發 | 追蹤資料生命週期,不能因輸入已存入資料庫就視為可信 |
影響不只資料外洩,也可能破壞完整性與可用性
SQL Injection 最常被聯想到資料讀取,但實際影響取決於查詢功能、資料庫帳號權限、驅動程式設定、資料庫產品與部署架構。弱點可能造成未授權查詢、登入或篩選條件繞過、資料竄改或刪除,也可能讓攻擊者取得更多資料庫權限。若資料庫帳號過度授權,風險會被顯著放大。
並非每個 SQL Injection 都能直接取得整個資料庫或作業系統權限。報告應區分已證實的行為、合理可達的影響與尚未驗證的最壞情境。過度誇大會讓開發團隊失去信任;只因沒有展示敏感資料就低估風險,也會錯失真正的修復時機。
評估業務影響時,要把受影響資料表、資料分類、租戶隔離、交易完整性、稽核紀錄與服務可用性納入。對會員、醫療、金融或企業管理系統而言,即使只影響一個查詢,也可能跨越重要的授權與資料邊界。
- 機密性:讀取非本人、其他租戶、個資、憑證或商業機密
- 完整性:變更帳號、訂單、權限、內容、交易或稽核資料
- 可用性:高成本查詢、鎖定、資料破壞或資源耗盡造成服務異常
- 權限邊界:繞過原有篩選或存取控制,擴大到資料庫帳號可及範圍
- 法規與事件應變:若存在實際利用跡象,需評估保存證據、通報與受影響資料
怎麼安全檢測?先有授權、基線與停止條件
SQL Injection 測試必須在明確授權範圍內執行。測試計畫要列出網域、API、環境、帳號、允許方法、資料庫與業務限制,並事先定義可接受的驗證深度。對生產環境應優先使用無破壞性證據,限制併發、延遲與資料量,並設定錯誤率、延遲、交易異常或告警觸發時的停止條件。
有效驗證會先建立正常請求基線,再一次只改變一個輸入,觀察回應狀態、內容結構、時間與伺服器端紀錄是否呈現可重複差異。測試結果應能被另一位驗證者理解與重測,但不需要大量擷取資料、修改真實紀錄或執行破壞性操作來證明風險。
若能存取程式碼或 APM/資料庫追蹤,應同步確認輸入如何到達查詢,以及驅動程式收到的是預先編譯的查詢與參數,還是一段已拼接完成的 SQL 字串。動態測試證明實際行為,資料流與查詢追蹤則協助確認根因;兩者結合會比單一錯誤訊息可靠。
一筆可信的 SQL Injection 發現需要哪些證據?
報告至少要回答:哪個資產、哪條路徑、哪種方法、哪個參數、什麼角色與租戶、在什麼時間與版本下出現差異。若只寫『登入頁存在 SQL Injection』,開發人員通常無法定位查詢,也無法判斷是否只出現在測試環境或特定節點。
證據應呈現安全基線與測試條件的差異,並說明為什麼差異支持查詢結構被改變。可以保存遮罩後的請求/回應片段、回應雜湊、狀態與長度、重複時間統計、應用程式錯誤 ID、APM Trace、資料庫 Query Log 或程式碼路徑;不應把完整個資、Session、密碼或大量資料直接放入一般工單。
嚴重度則要分開評估。技術證據證明弱點存在,資產暴露、所需權限、可讀寫資料、資料庫帳號權限與補償性控制決定處置優先級。保留這兩層資訊,才能避免把『有弱點』與『風險多大』混為一談。
| 證據 | 應記錄內容 | 保護要求 |
|---|---|---|
| 資產與條件 | Host、路徑、方法、參數、角色、租戶、環境與版本 | 避免只記 IP 或模糊功能名稱 |
| 正常基線 | 安全輸入下的狀態、內容特徵、時間與業務結果 | 保存足以比較的特徵,不必保存完整敏感頁面 |
| 驗證差異 | 一次一變因且可重複的回應、錯誤、時間或查詢差異 | 不執行刪除、批次匯出或高負載驗證 |
| 根因線索 | 資料流、Query Trace、日誌事件、程式碼位置或框架用法 | 限制日誌權限並遮罩參數值 |
| 影響範圍 | 查詢目的、資料表、資料分類、DB 帳號與可用操作 | 區分已證實與推測的最壞情境 |
| 複驗資料 | 修復版本、部署環境、原始測試條件與預期安全結果 | 讓驗證者能重測而不暴露可濫用細節 |
核心修復:使用參數化查詢或 Prepared Statement
參數化查詢會先把 SQL 指令模板交給資料庫,再以獨立參數傳入資料值。資料庫知道哪些部分是指令、哪些部分是資料,因此即使參數包含具有 SQL 意義的字元,也只會被當成值處理。OWASP、CWE-89 與 ASVS 都把這種 code/data separation 視為主要防護。
修復不能只改報告中的單一路徑。團隊應搜尋相同 Repository、DAO、Query Builder、Raw SQL Helper、批次與報表程式,確認同一套字串串接模式沒有存在其他端點。若共用函式本身不安全,只改呼叫端容易留下變形路徑。
參數型別也應與資料模型一致。識別碼使用整數或 UUID,日期使用日期型別,布林值與列舉使用明確型別;這不取代參數化,但能縮小錯誤輸入與業務邏輯歧義。下圖右側呈現安全資料流:查詢結構保持完整,輸入進入獨立參數槽,資料庫只回傳預期紀錄。

無法參數化的欄位名稱、排序與表格怎麼處理?
參數通常只能代表資料值,不能直接代替資料表名稱、欄位名稱、排序方向或 SQL 關鍵字。如果產品允許使用者選擇排序欄位或報表維度,不能把前端傳來的名稱直接拼進查詢,也不能只靠移除特殊字元。
正確作法是由伺服器端建立固定對照表。例如外部值 newest 只能映射到程式碼內預先定義的建立時間降冪排序;未知值直接拒絕或使用安全預設。資料表切換、報表欄位與聚合方式也應由可信列舉選取,不接受任意 SQL 片段。
如果需求真的需要高度動態查詢,應使用能保留參數綁定的成熟 Query Builder,對每個可變結構建立明確語法模型與允許清單,並進行專門的程式碼審查與測試。『動態』不等於必須用自由格式字串。
- 資料值:使用 Prepared Statement 或框架原生參數綁定
- 排序欄位:外部選項映射到伺服器端固定欄位列舉
- 升冪/降冪:使用布林或列舉決定預先定義的 ASC/DESC
- 表格與報表維度:依業務場景選擇固定查詢模板
- 複雜篩選:使用參數安全且經審查的 Query Builder,不接受 Raw SQL Fragment
ORM、Query Builder 與 Stored Procedure 不是自動安全
ORM 能降低手寫 SQL 的比例,但只要使用 Raw Query、字串插值、自由格式條件、動態排序或不安全的 Query Builder Escape Hatch,仍可能把外部輸入帶回 SQL 結構。OWASP Top 10:2025 也特別提醒,未經處理的 ORM 搜尋參數仍可能暴露未授權紀錄。
Stored Procedure 只有在內部使用固定 SQL 與安全參數時才有防護效果。如果 Procedure 仍把參數串接後交給動態執行功能,風險只是從應用程式搬到資料庫。更糟的是,組織可能為了執行 Procedure 給應用程式過高權限,使成功利用的影響更大。
審查時不要只問『有沒有 ORM』或『是不是 Stored Procedure』,而要追到最終資料庫呼叫:Query Template 是否固定?值是否透過驅動程式參數傳入?是否存在 Raw SQL 與動態執行?資料庫帳號實際擁有哪些權限?
| 方式 | 能否作為主要防護 | 必要條件與限制 |
|---|---|---|
| 參數化查詢/Prepared Statement | 可以 | 所有資料值都以驅動程式參數傳入,沒有先自行拼接 SQL |
| 安全 ORM API | 可以 | 避開 Raw SQL、自由格式 Filter/Order,確認框架確實使用參數綁定 |
| Stored Procedure | 有條件 | Procedure 內不得動態串接 SQL,執行帳號仍需最小權限 |
| 允許清單驗證 | 輔助/結構必要 | 適用無法參數化的欄位、排序與有限選項,不取代值的參數化 |
| 輸入跳脫 | 不建議作主要防護 | 依資料庫、編碼與上下文而異,容易遺漏或錯用 |
| WAF/RASP | 只能補強 | 可降低部分已知攻擊,但可能被繞過,也無法修正程式根因 |
輸入驗證很重要,但不能取代參數化
輸入驗證的任務是確保資料符合業務規則,例如 ID 必須是合法 UUID、數量在允許範圍、狀態值屬於固定集合。它能減少模糊輸入、改善錯誤處理並縮小攻擊面,但不能保證資料不會改變 SQL 結構,因為合法資料本身也可能包含引號、連字號、Unicode 或其他被錯誤處理的內容。
常見反模式包括封鎖 SQL 關鍵字、移除引號、用正規表示式猜測攻擊 Payload,或只做瀏覽器端驗證。黑名單無法涵蓋不同編碼、大小寫、註解與資料庫方言;前端檢查則能被直接呼叫 API 繞過。
正確順序是先以參數化確保資料不會成為指令,再以伺服器端允許清單與型別/範圍驗證確保資料符合業務需求。輸出編碼是 XSS 等輸出情境的重要控制,但也不是 SQL Injection 的替代修復。
用最小權限限制成功利用後的爆炸半徑
即使程式已有參數化,資料庫權限仍應遵循 least privilege。應用程式不該使用 DBA、Schema Owner 或可建立帳號與任意物件的帳號連線。不同服務、環境與讀寫用途應盡量分離帳號,讓一個查詢路徑失守時無法橫跨全部資料與管理功能。
OWASP 建議從需求出發授權,而不是先給完整權限再期待應用程式自行限制。只需要讀取特定資料的功能,可透過唯讀帳號、受限 View 或固定 Procedure 暴露必要欄位;寫入功能則限制到特定表格與操作。資料庫執行所使用的作業系統帳號也不應具管理權限。
權限調整前要盤點現有查詢與排程,避免直接收權造成營運中斷。完成後應用自動化測試確認正常流程,並由資料庫端實際檢查授權,而不是只相信文件。若懷疑 SQL Injection 已被利用,還要評估輪替資料庫憑證與其他可被讀取的祕密。
- 生產、測試與開發使用不同帳號與憑證,不共用高權限連線
- 依服務與讀寫用途分離帳號,限制可見 Schema、Table、View 與操作
- 禁止應用程式帳號建立使用者、授權、載入外部檔案或執行非必要管理功能
- 用 Vault/Secret Manager 管理短週期憑證,避免寫入程式碼與映像檔
- 持續檢查實際 Grants、閒置帳號、權限漂移與異常大量查詢
錯誤處理、日誌與監控:外部少揭露,內部要可追查
對使用者的錯誤訊息不應包含 SQL 片段、資料表、欄位、資料庫產品、Stack Trace 或內部路徑;這些資訊會協助攻擊者理解查詢與架構。前端可以回傳一致的業務錯誤與關聯 ID,詳細原因則保存於受保護的伺服器端日誌。
內部日誌要能關聯請求、使用者、服務、查詢模板、資料庫錯誤與部署版本,但不要無限制記錄完整參數、密碼、Token 或敏感資料。較安全的方式是記錄查詢模板 ID、參數型別、遮罩值、資料列數、耗時與 Trace ID,並對異常錯誤、回傳量或查詢時間建立告警。
監控不是修復,但能縮短發現與應變時間。若同一來源在多個參數產生資料庫語法錯誤、真假條件型回應差異、異常延遲或大量資料列輸出,安全團隊應能追蹤到應用程式與資料庫事件。告警規則也要調整誤報,避免正常搜尋與報表被當成攻擊。
WAF 可以暫時降低暴露,但不能當作結案證據
WAF 能封鎖部分已知 Payload、爭取修復時間並提供攻擊觀測,在公開高風險端點或緊急事件中很有價值。但 WAF 只看到經過它的流量,可能漏掉其他 Host、API、內部路徑、編碼、非 HTTP 資料來源與 Second-order 路徑,也可能因規則放寬或架構變更失去覆蓋。
因此 WAF 應記錄為補償性控制:規則適用哪些資產與路徑、阻擋或監控模式、如何測試、何時到期、誰監看、控制失效如何告警。即使控制有效,程式中的字串串接根因仍存在,修復工單與複驗責任不能直接關閉。
若暫時只能部署 WAF,優先建立精準規則、限制受影響功能、降低資料庫權限、提高監控並安排永久修復。之後複驗應分別確認應用程式根因消失與 WAF 不是唯一防線。
把 SAST、DAST、IAST、程式碼審查與資料庫觀測放在一起
不同方法看到的是 SQL Injection 的不同切面。SAST 可沿著資料流尋找輸入到危險查詢 API 的路徑,但可能不了解執行時框架與設定;DAST 從執行中的網站觀察可利用行為,但未必能覆蓋所有登入角色與隱藏功能;IAST 在測試流量下追蹤應用程式內部資料流,能協助定位 Sink,但依賴完整測試覆蓋。
程式碼審查適合檢查共用 Query Helper、Raw SQL、Stored Procedure 與動態排序等高風險設計;資料庫 Query Log、APM 與 Runtime Telemetry 則能驗證實際查詢模板、參數使用與異常結果量。沒有任何單一工具能保證所有路徑都被測到。
成熟流程會在開發階段以安全 API、Lint/SAST 與 Review 防止新問題,在整合與測試環境用 IAST/DAST 驗證,在正式環境持續監控錯誤與異常查詢,最後把每次發現回饋到安全編碼規則與回歸測試。
修復後怎麼複驗?不能只確認錯誤訊息消失
SQL Injection 複驗應先用原始資產、角色、路徑、方法與參數重現基線,再確認造成差異的測試條件不再改變查詢結果、時間或錯誤。單純把錯誤頁改成通用訊息,或讓 WAF 回傳 403,並不證明底層查詢已安全。
驗證者應取得修復版本與部署範圍,必要時檢查 Query Trace 或程式碼,確認查詢模板固定且參數由驅動程式綁定。接著擴大到同根因端點、其他角色、API 版本、批次與報表功能,並測試正常輸入、合法特殊字元與邊界值,避免修復造成搜尋或國際化資料損壞。
最後檢查資料庫帳號權限、錯誤訊息、監控與回歸測試是否同步改善。若只修正一個 Controller,卻保留共用不安全 Helper 或 Stored Procedure,工單不應視為完整結案。
| 複驗面向 | 通過條件 | 不充分的證據 |
|---|---|---|
| 原始發現 | 相同條件不再改變查詢語意,且正常功能維持 | 頁面不再顯示錯誤 |
| 根因 | Query Template 固定,資料值由驅動程式參數綁定 | 程式碼增加字元過濾 |
| 同根因範圍 | 相關端點、角色、API 版本、批次與報表均完成檢查 | 只測報告中的單一路徑 |
| 動態結構 | 欄位、排序與表格由伺服器端允許清單選取 | 前端 Dropdown 限制選項 |
| 權限與曝露 | DB 帳號最小權限,錯誤不外洩,必要監控已啟用 | WAF 規則目前會阻擋 |
| 回歸 | 正常資料、合法特殊字元、邊界值與多語系輸入可正確運作 | 只測攻擊型輸入 |
如果懷疑已遭利用,從弱點修復切換到事件應變
若日誌出現可疑查詢、異常大量資料、權限變更、資料庫錯誤探測或不明外連,工作不再只是修一個 Bug。團隊應依事件應變程序保存應用程式、WAF、資料庫、身分、主機與網路證據,限制受影響功能或帳號,並確認攻擊時間、入口、資料存取與橫向移動範圍。
修復程式與封鎖來源不能取代影響評估。若資料庫憑證、API Key、Session Secret 或其他機密可能被讀取,應依風險輪替;若資料遭未授權存取或變更,還要評估備份、完整性驗證、法規、契約與通報義務。
調查期間要避免破壞證據,並保留明確時間線。完成根因修正後,仍需驗證入侵持久化、異常帳號、資料變更與其他同類端點,而不是只確認原始 URL 回應正常。
開發與維運團隊的 SQL Injection 防護清單
SQL Injection 防護不應等掃描報告才開始。把安全查詢 API、Raw SQL 審查、最小權限、錯誤處理與自動化測試變成 Definition of Done,能在問題進入正式環境前切斷大部分風險。以下清單可用於設計審查、Pull Request、發布與複驗。
- 所有資料值使用參數化查詢;禁止以字串插值或串接建立 SQL
- 動態欄位、排序與表格使用伺服器端固定映射,不接受任意 SQL Fragment
- 盤點 ORM Raw Query、Stored Procedure、Query Builder Escape Hatch 與共用 Helper
- 伺服器端執行型別、範圍與允許清單驗證,但不把過濾當成主要防護
- 應用程式資料庫帳號依服務與讀寫需求最小化,定期檢查 Grants 與權限漂移
- 對外錯誤不洩漏 SQL/Schema,內部日誌可關聯且遮罩敏感參數
- 在 CI 執行 SAST/測試,在授權環境執行 DAST/IAST,為已修復根因建立回歸案例
- 修復後由獨立驗證者重測原始條件、同根因範圍、正常功能與資料庫權限
真正的修復,是讓資料永遠只能是資料
SQL Injection 看似有許多類型與測試技巧,但防護原則非常穩定:查詢結構必須由可信程式碼決定,外部輸入只能透過參數綁定成為資料。對無法參數化的結構使用伺服器端允許清單,再用最小權限、錯誤處理、監控與多種測試方法建立縱深防禦。
專業的弱點處理也不以『擋住一個 Payload』為終點。團隊要保存可複驗證據、定位共用根因、檢查 ORM 與 Stored Procedure 的真實用法、修正所有同類路徑,最後以查詢行為、正常回歸與資料庫權限共同證明風險已關閉。
資料來源
- OWASP SQL Injection Prevention Cheat Sheet↗
- OWASP Query Parameterization Cheat Sheet↗
- OWASP Input Validation Cheat Sheet↗
- OWASP Top 10:2025 — A05 Injection↗
- OWASP Web Security Testing Guide — Testing for SQL Injection↗
- OWASP Application Security Verification Standard 5.0↗
- MITRE CWE-89 — Improper Neutralization of Special Elements used in an SQL Command↗


