UNION 能把兩個相容 SELECT 的結果合併。當外部資料被放進查詢結構時,這項正常資料庫功能可能被濫用。本文只以抽象查詢形狀說明,不提供資料擷取自動化或真實目標步驟。

本篇會學到什麼
- 理解 UNION 的欄位數與型別相容條件
- 辨認結果反射為何使風險可見
- 用固定查詢與最小權限消除問題
必備名詞與心智模型
原查詢若輸出三欄,合併的 SELECT 通常也要提供三個相容欄位。這不是 UNION 本身的漏洞,而是應用程式讓輸入跨越資料與 SQL 結構的邊界。結果若直接顯示在頁面,額外資料也可能被呈現。
發生情境與生活化比喻
商品詳細頁依數字 ID 查詢 id、name、price。生活化比喻是報表系統允許使用者把另一張格式相同的表附加到正式報表;問題是使用者不該控制報表 SQL。
不安全的原始碼
$id = $_GET['id'] ?? '';
$sql = "SELECT id,name,price FROM products WHERE id=" . $id;
$product = $pdo->query($sql)->fetch(PDO::FETCH_ASSOC);
此片段刻意省略連線、路由與部署設定,只用來辨識資料流,請勿放入任何服務。
逐步看懂資料如何變成查詢
- id 看似數字但 HTTP 輸入本質仍是字串
- 串接後輸入位於 WHERE 運算式而非資料參數
- 資料庫依完整 SQL 語法解析
- 若查詢形狀可被擴充且結果被反射,其他相容列可能混入
- 應用程式帳號可讀哪些表決定最大暴露範圍
修補後原始碼
$id = filter_var($_GET['id'] ?? null, FILTER_VALIDATE_INT,
['options'=>['min_range'=>1]]);
if ($id === false) { /* 回傳 400,不查資料庫 */ }
$stmt = $pdo->prepare('SELECT id,name,price FROM products WHERE id=:id');
$stmt->bindValue(':id', $id, PDO::PARAM_INT);
$stmt->execute();
修補的共同原則是先固定 SQL 結構,再把值獨立綁定;無法作為值綁定的識別字只能從程式內允許清單產生。
如何驗證修補
- 正整數可查詢,負數、小數與文字被拒絕
- 特殊字元不改變欄位數、回傳列數或錯誤類型
- 應用程式帳號不能讀使用者密碼雜湊等不相干欄位
- 頁面只渲染預期 schema,避免任意欄位輸出
閱讀提醒:以下分析的目的,是讓你能在自己的程式、測試資料與授權環境中辨識風險。面對正式系統時,先取得明確授權並保存變更紀錄;不要用錯誤訊息或一次回應就推論資料外洩,也不要把文章中的概念改造成自動化探測。安全工作的完成條件,是能說明根因、修補位置、回歸結果與剩餘風險。
更多應用情境、常見誤判與觀察方法
把原理放回真實開發情境
UNION 的概念重點是兩側欄位數量與型別相容;文章只用靜態範例解釋結果集合。 實作時不要只盯著畫面上的輸入框,還要檢查 API 參數、伺服器端預設值、資料轉換與最後呼叫的資料庫介面。最實用的閱讀方式,是把每一段程式標成「外部來源、轉換、控制、資料庫 sink」,確認資料值沒有在途中重新變回 SQL 結構。
常見誤判
錯誤頁看不到資料不表示安全,應回到程式確認是否仍把輸入拼進 SQL。 安全判斷不能只靠畫面、單次錯誤或某一個防護產品;應同時看原始碼、驅動程式實際執行方式、資料庫權限與回歸測試。若無法證明查詢結構固定,就應把它視為待修的設計風險。
開發與 SOC 可以觀察什麼
防守端應追查異常查詢形狀、欄位型別錯誤與非預期結果筆數,而不是只比對單一關鍵字。 日誌宜記錄時間、路由、請求關聯 ID、查詢名稱、結果狀態與耗時,敏感值則遮罩或雜湊。偵測規則的用途是縮短發現時間;阻擋之後仍須回到程式碼修補,並搜尋其他共用元件與相同資料流。
審查時的三個追問
- 這個值最早由誰控制,經過哪些轉換?
- 它在資料庫呼叫中是資料值,還是欄位、排序、運算式等結構?
- 若預防失效,最小權限、監控與事件流程能限制並發現多少影響?
開發與維運防禦清單
- 不要用正規表示式攔 UNION 作主要防線
- 數字要做伺服器端型別與範圍驗證,再參數化
- 拆分商品讀取與敏感資料庫權限
- 監控同一端點持續發生欄位數或型別錯誤
重點整理與下一篇
UNION 依賴結果可回傳;若應用程式顯示資料庫錯誤,攻擊者還可能利用錯誤訊息理解查詢。下一篇說明 Error-based SQL Injection 與安全錯誤處理。
參考資料與更新日期
更新日期:2026-07-20












