2009-12-30 22:24:22 +00:00
|
|
|
##
|
2012-02-18 02:56:55 +00:00
|
|
|
# $Id: blind_sql_query.rb 14735 2012-02-17 09:36:04Z rapid7 $
|
2009-12-30 22:24:22 +00:00
|
|
|
##
|
|
|
|
|
|
|
|
##
|
2010-04-30 08:40:19 +00:00
|
|
|
# This file is part of the Metasploit Framework and may be subject to
|
2009-12-30 22:24:22 +00:00
|
|
|
# redistribution and commercial restrictions. Please see the Metasploit
|
2012-02-21 01:40:50 +00:00
|
|
|
# web site for more information on licensing and terms of use.
|
|
|
|
# http://metasploit.com/
|
2009-12-30 22:24:22 +00:00
|
|
|
##
|
|
|
|
|
|
|
|
require 'rex/proto/http'
|
|
|
|
require 'msf/core'
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
class Metasploit3 < Msf::Auxiliary
|
|
|
|
|
|
|
|
include Msf::Exploit::Remote::HttpClient
|
2012-02-03 21:43:21 +00:00
|
|
|
include Msf::Auxiliary::WmapScanUniqueQuery
|
2009-12-30 22:24:22 +00:00
|
|
|
include Msf::Auxiliary::Scanner
|
|
|
|
include Msf::Auxiliary::Report
|
|
|
|
|
|
|
|
|
|
|
|
def initialize(info = {})
|
2010-04-30 08:40:19 +00:00
|
|
|
super(update_info(info,
|
2012-02-18 04:36:33 +00:00
|
|
|
'Name' => 'HTTP Blind SQL Injection QUERY Scanner',
|
2009-12-30 22:24:22 +00:00
|
|
|
'Description' => %q{
|
2010-04-30 08:40:19 +00:00
|
|
|
This module identifies the existence of Blind SQL injection issues
|
2012-02-18 04:36:33 +00:00
|
|
|
in GET/POST Query parameters values.
|
2009-12-30 22:24:22 +00:00
|
|
|
},
|
|
|
|
'Author' => [ 'et [at] cyberspace.org' ],
|
|
|
|
'License' => BSD_LICENSE,
|
2012-02-18 02:56:55 +00:00
|
|
|
'Version' => '$Revision: 14735 $'))
|
2010-04-30 08:40:19 +00:00
|
|
|
|
2009-12-30 22:24:22 +00:00
|
|
|
register_options(
|
|
|
|
[
|
2012-02-18 04:36:33 +00: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", ''])
|
2010-04-30 08:40:19 +00:00
|
|
|
], self.class)
|
|
|
|
|
2009-12-30 22:24:22 +00:00
|
|
|
end
|
|
|
|
|
|
|
|
def run_host(ip)
|
2010-04-30 08:40:19 +00:00
|
|
|
|
2012-02-17 09:34:39 +00:00
|
|
|
# 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
|
2012-02-18 02:56:55 +00:00
|
|
|
gvars = Hash.new()
|
|
|
|
pvars = Hash.new()
|
|
|
|
cvars = Hash.new()
|
2010-04-30 08:40:19 +00:00
|
|
|
|
|
|
|
rnum=rand(10000)
|
|
|
|
|
2009-12-30 22:24:22 +00:00
|
|
|
valstr = [
|
2010-04-30 08:40:19 +00:00
|
|
|
[ 'numeric',
|
2009-12-30 22:24:22 +00:00
|
|
|
" AND #{rnum}=#{rnum} ",
|
|
|
|
" AND #{rnum}=#{rnum+1} "
|
2010-04-30 08:40:19 +00:00
|
|
|
],
|
|
|
|
[ 'single quotes',
|
2009-12-30 22:24:22 +00:00
|
|
|
"' AND '#{rnum}'='#{rnum}",
|
|
|
|
"' AND '#{rnum}'='#{rnum+1}"
|
|
|
|
],
|
2010-04-30 08:40:19 +00:00
|
|
|
[ 'double quotes',
|
2009-12-30 22:24:22 +00:00
|
|
|
"\" AND \"#{rnum}\"=\"#{rnum}",
|
|
|
|
"\" AND \"#{rnum}\"=\"#{rnum+1}"
|
|
|
|
]
|
|
|
|
]
|
|
|
|
|
|
|
|
#
|
|
|
|
# Dealing with empty query/data and making them hashes.
|
|
|
|
#
|
2010-04-30 08:40:19 +00: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
|
2010-04-30 08:40:19 +00: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
|
2010-04-30 08:40:19 +00: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
|
|
|
|
|
|
|
|
|
2010-04-30 08:40:19 +00:00
|
|
|
#SEND NORMAL REQUEST
|
2012-02-18 02:56:55 +00:00
|
|
|
|
|
|
|
|
2009-12-30 22:24:22 +00:00
|
|
|
begin
|
|
|
|
normalres = send_request_cgi({
|
2010-09-20 08:06:27 +00:00
|
|
|
'uri' => datastore['PATH'],
|
|
|
|
'vars_get' => gvars,
|
2012-02-17 09:34:39 +00:00
|
|
|
'method' => http_method,
|
2009-12-30 22:24:22 +00:00
|
|
|
'ctype' => 'application/x-www-form-urlencoded',
|
2010-09-20 08:06:27 +00:00
|
|
|
'cookie' => datastore['COOKIE'],
|
|
|
|
'data' => datastore['DATA']
|
2009-12-30 22:24:22 +00:00
|
|
|
}, 20)
|
2010-04-30 08:40:19 +00:00
|
|
|
|
2009-12-30 22:24:22 +00:00
|
|
|
rescue ::Rex::ConnectionRefused, ::Rex::HostUnreachable, ::Rex::ConnectionTimeout
|
2010-04-30 08:40:19 +00:00
|
|
|
rescue ::Timeout::Error, ::Errno::EPIPE
|
2009-12-30 22:24:22 +00:00
|
|
|
end
|
2010-04-30 08:40:19 +00:00
|
|
|
|
2009-12-30 22:24:22 +00:00
|
|
|
sigtxt = ""
|
2010-04-30 08:40:19 +00:00
|
|
|
|
2009-12-30 22:24:22 +00:00
|
|
|
if normalres
|
|
|
|
if normalres.body.empty?
|
|
|
|
print_error("No body to obtain signature")
|
|
|
|
return
|
|
|
|
else
|
|
|
|
sigtxt = normalres.body
|
|
|
|
end
|
|
|
|
else
|
|
|
|
print_error("No response")
|
|
|
|
return
|
|
|
|
end
|
2010-04-30 08:40:19 +00:00
|
|
|
|
|
|
|
#print_status("Normal request sent.")
|
2009-12-30 22:24:22 +00:00
|
|
|
|
|
|
|
valstr.each do |tarr|
|
2010-04-30 08:40:19 +00:00
|
|
|
|
2009-12-30 22:24:22 +00:00
|
|
|
#QUERY
|
|
|
|
if gvars
|
2010-04-30 08:40:19 +00:00
|
|
|
gvars.each do |key,value|
|
2009-12-30 22:24:22 +00:00
|
|
|
gvars = queryparse(datastore['QUERY']) #Now its a Hash
|
2010-04-30 08:40:19 +00:00
|
|
|
|
|
|
|
print_status("- Testing '#{tarr[0]}' Parameter #{key}:")
|
2009-12-30 22:24:22 +00:00
|
|
|
|
|
|
|
#SEND TRUE REQUEST
|
|
|
|
gvars[key] = gvars[key]+tarr[1]
|
2010-04-30 08:40:19 +00:00
|
|
|
|
|
|
|
|
2009-12-30 22:24:22 +00:00
|
|
|
begin
|
|
|
|
trueres = send_request_cgi({
|
|
|
|
'uri' => datastore['PATH'],
|
2010-04-30 08:40:19 +00:00
|
|
|
'vars_get' => gvars,
|
2012-02-17 09:34:39 +00:00
|
|
|
'method' => http_method,
|
2009-12-30 22:24:22 +00:00
|
|
|
'ctype' => 'application/x-www-form-urlencoded',
|
|
|
|
'cookie' => datastore['COOKIE'],
|
|
|
|
'data' => datastore['DATA']
|
|
|
|
}, 20)
|
|
|
|
|
|
|
|
rescue ::Rex::ConnectionRefused, ::Rex::HostUnreachable, ::Rex::ConnectionTimeout
|
2010-04-30 08:40:19 +00:00
|
|
|
rescue ::Timeout::Error, ::Errno::EPIPE
|
2009-12-30 22:24:22 +00:00
|
|
|
end
|
|
|
|
|
|
|
|
if normalres and trueres
|
2010-04-30 08:40:19 +00:00
|
|
|
|
|
|
|
#Very simple way to compare responses, this can be improved alot , at this time just the simple way
|
|
|
|
|
|
|
|
reltruesize = trueres.body.length-(trueres.body.scan(/#{tarr[1]}/).length*tarr[1].length)
|
2009-12-30 22:24:22 +00:00
|
|
|
normalsize = normalres.body.length
|
|
|
|
|
|
|
|
#print_status("nlen #{normalsize} reltlen #{reltruesize}")
|
2010-04-30 08:40:19 +00:00
|
|
|
|
|
|
|
if reltruesize == normalsize
|
2009-12-30 22:24:22 +00:00
|
|
|
#If true it means that we have a small better chance of this being a blind sql injection.
|
|
|
|
|
|
|
|
#SEND FALSE REQUEST
|
2010-04-30 08:40:19 +00:00
|
|
|
gvars[key] = gvars[key]+tarr[2]
|
|
|
|
|
|
|
|
|
2009-12-30 22:24:22 +00:00
|
|
|
begin
|
|
|
|
falseres = send_request_cgi({
|
|
|
|
'uri' => datastore['PATH'],
|
2010-04-30 08:40:19 +00:00
|
|
|
'vars_get' => gvars,
|
2012-02-17 09:34:39 +00:00
|
|
|
'method' => http_method,
|
2009-12-30 22:24:22 +00:00
|
|
|
'ctype' => 'application/x-www-form-urlencoded',
|
|
|
|
'cookie' => datastore['COOKIE'],
|
|
|
|
'data' => datastore['DATA']
|
|
|
|
}, 20)
|
|
|
|
|
2010-04-30 08:40:19 +00:00
|
|
|
|
|
|
|
if falseres
|
2010-09-20 08:06:27 +00:00
|
|
|
#Very simple way to compare responses, this can be improved alot , at this time just the simple way
|
|
|
|
relfalsesize = falseres.body.length-(falseres.body.scan(/#{tarr[2]}/).length*tarr[2].length)
|
|
|
|
#true_false_dist = edit_distance(falseres.body,trueres.body)
|
2009-12-30 22:24:22 +00:00
|
|
|
|
2010-09-20 08:06:27 +00:00
|
|
|
#print_status("rellenf #{relfalsesize}")
|
2010-04-30 08:40:19 +00:00
|
|
|
|
2010-09-20 08:06:27 +00:00
|
|
|
if reltruesize > relfalsesize
|
|
|
|
print_status("Possible #{tarr[0]} Blind SQL Injection Found #{datastore['PATH']} #{key}")
|
2010-04-30 08:40:19 +00:00
|
|
|
|
2009-12-30 22:24:22 +00:00
|
|
|
report_note(
|
|
|
|
:host => ip,
|
2011-02-04 01:54:32 +00:00
|
|
|
:proto => 'tcp',
|
2012-02-22 05:57:55 +00:00
|
|
|
:sname => (ssl ? "https" : "http"),
|
2009-12-30 22:24:22 +00:00
|
|
|
:port => rport,
|
|
|
|
:type => 'BLIND_SQL_INJECTION',
|
|
|
|
:data => "#{datastore['PATH']} Parameter: #{key} Type: #{tarr[0]}"
|
|
|
|
)
|
2010-09-20 08:06:27 +00:00
|
|
|
else
|
|
|
|
print_status("NOT Vulnerable #{datastore['PATH']} parameter #{key}")
|
2009-12-30 22:24:22 +00:00
|
|
|
end
|
|
|
|
else
|
2010-04-30 08:40:19 +00:00
|
|
|
print_status("NO False Response.")
|
2009-12-30 22:24:22 +00:00
|
|
|
end
|
|
|
|
|
|
|
|
rescue ::Rex::ConnectionRefused, ::Rex::HostUnreachable, ::Rex::ConnectionTimeout
|
2010-04-30 08:40:19 +00:00
|
|
|
rescue ::Timeout::Error, ::Errno::EPIPE
|
2009-12-30 22:24:22 +00:00
|
|
|
end
|
|
|
|
else
|
|
|
|
print_status("Normal and True requests are different.")
|
|
|
|
end
|
|
|
|
else
|
|
|
|
print_status("No response.")
|
|
|
|
end
|
|
|
|
end
|
|
|
|
end
|
|
|
|
#DATA
|
2010-04-30 08:40:19 +00:00
|
|
|
|
2009-12-30 22:24:22 +00:00
|
|
|
if pvars
|
2010-04-30 08:40:19 +00:00
|
|
|
pvars.each do |key,value|
|
2009-12-30 22:24:22 +00:00
|
|
|
pvars = queryparse(datastore['DATA']) #Now its a Hash
|
2010-04-30 08:40:19 +00:00
|
|
|
|
|
|
|
print_status("- Testing '#{tarr[0]}' Parameter #{key}:")
|
2009-12-30 22:24:22 +00:00
|
|
|
|
|
|
|
#SEND TRUE REQUEST
|
|
|
|
pvars[key] = pvars[key]+tarr[1]
|
2010-04-30 08:40:19 +00:00
|
|
|
|
2009-12-30 22:24:22 +00:00
|
|
|
pvarstr = ""
|
|
|
|
pvars.each do |tkey,tvalue|
|
|
|
|
if pvarstr
|
|
|
|
pvarstr << '&'
|
|
|
|
end
|
|
|
|
pvarstr << tkey+'='+tvalue
|
|
|
|
end
|
2010-04-30 08:40:19 +00:00
|
|
|
|
|
|
|
|
2009-12-30 22:24:22 +00:00
|
|
|
begin
|
|
|
|
trueres = send_request_cgi({
|
|
|
|
'uri' => datastore['PATH'],
|
2010-04-30 08:40:19 +00:00
|
|
|
'vars_get' => gvars,
|
2012-02-17 09:34:39 +00:00
|
|
|
'method' => http_method,
|
2009-12-30 22:24:22 +00:00
|
|
|
'ctype' => 'application/x-www-form-urlencoded',
|
|
|
|
'cookie' => datastore['COOKIE'],
|
|
|
|
'data' => pvarstr
|
|
|
|
}, 20)
|
|
|
|
|
|
|
|
rescue ::Rex::ConnectionRefused, ::Rex::HostUnreachable, ::Rex::ConnectionTimeout
|
2010-04-30 08:40:19 +00:00
|
|
|
rescue ::Timeout::Error, ::Errno::EPIPE
|
2009-12-30 22:24:22 +00:00
|
|
|
end
|
|
|
|
|
|
|
|
if normalres and trueres
|
2010-04-30 08:40:19 +00:00
|
|
|
|
|
|
|
#Very simple way to compare responses, this can be improved alot , at this time just the simple way
|
|
|
|
|
|
|
|
reltruesize = trueres.body.length-(trueres.body.scan(/#{tarr[1]}/).length*tarr[1].length)
|
2009-12-30 22:24:22 +00:00
|
|
|
normalsize = normalres.body.length
|
|
|
|
|
|
|
|
#print_status("nlen #{normalsize} reltlen #{reltruesize}")
|
2010-04-30 08:40:19 +00:00
|
|
|
|
|
|
|
if reltruesize == normalsize
|
2009-12-30 22:24:22 +00:00
|
|
|
#If true it means that we have a small better chance of this being a blind sql injection.
|
|
|
|
|
|
|
|
#SEND FALSE REQUEST
|
|
|
|
pvars[key] = pvars[key]+tarr[2]
|
2010-04-30 08:40:19 +00:00
|
|
|
|
2009-12-30 22:24:22 +00:00
|
|
|
pvarstr = ""
|
|
|
|
pvars.each do |tkey,tvalue|
|
|
|
|
if pvarstr
|
|
|
|
pvarstr << '&'
|
|
|
|
end
|
|
|
|
pvarstr << tkey+'='+tvalue
|
2010-04-30 08:40:19 +00:00
|
|
|
end
|
|
|
|
|
|
|
|
|
2009-12-30 22:24:22 +00:00
|
|
|
begin
|
|
|
|
falseres = send_request_cgi({
|
|
|
|
'uri' => datastore['PATH'],
|
2010-04-30 08:40:19 +00:00
|
|
|
'vars_get' => gvars,
|
2012-02-17 09:34:39 +00:00
|
|
|
'method' => http_method,
|
2009-12-30 22:24:22 +00:00
|
|
|
'ctype' => 'application/x-www-form-urlencoded',
|
|
|
|
'cookie' => datastore['COOKIE'],
|
|
|
|
'data' => pvarstr
|
|
|
|
}, 20)
|
|
|
|
|
2010-04-30 08:40:19 +00:00
|
|
|
|
|
|
|
if falseres
|
2010-09-20 08:06:27 +00:00
|
|
|
#Very simple way to compare responses, this can be improved alot , at this time just the simple way
|
|
|
|
relfalsesize = falseres.body.length-(falseres.body.scan(/#{tarr[2]}/).length*tarr[2].length)
|
|
|
|
#true_false_dist = edit_distance(falseres.body,trueres.body)
|
2010-04-30 08:40:19 +00:00
|
|
|
|
2010-09-20 08:06:27 +00:00
|
|
|
#print_status("rellenf #{relfalsesize}")
|
2009-12-30 22:24:22 +00:00
|
|
|
|
2010-09-20 08:06:27 +00:00
|
|
|
if reltruesize > relfalsesize
|
|
|
|
print_status("Possible #{tarr[0]} Blind SQL Injection Found #{datastore['PATH']} #{key}")
|
2010-04-30 08:40:19 +00:00
|
|
|
|
2009-12-30 22:24:22 +00:00
|
|
|
report_note(
|
|
|
|
:host => ip,
|
2011-02-04 01:54:32 +00:00
|
|
|
:proto => 'tcp',
|
2009-12-30 22:24:22 +00:00
|
|
|
:port => rport,
|
|
|
|
:type => 'BLIND_SQL_INJECTION',
|
|
|
|
:data => "#{datastore['PATH']} Parameter: #{key} Type: #{tarr[0]}"
|
|
|
|
)
|
|
|
|
|
2010-09-20 08:06:27 +00:00
|
|
|
else
|
|
|
|
print_status("NOT Vulnerable #{datastore['PATH']} parameter #{key}")
|
2009-12-30 22:24:22 +00:00
|
|
|
end
|
|
|
|
else
|
2010-04-30 08:40:19 +00:00
|
|
|
print_status("NO False Response.")
|
2009-12-30 22:24:22 +00:00
|
|
|
end
|
|
|
|
|
|
|
|
rescue ::Rex::ConnectionRefused, ::Rex::HostUnreachable, ::Rex::ConnectionTimeout
|
2010-04-30 08:40:19 +00:00
|
|
|
rescue ::Timeout::Error, ::Errno::EPIPE
|
2009-12-30 22:24:22 +00:00
|
|
|
end
|
|
|
|
else
|
|
|
|
print_status("Normal and True requests are different.")
|
|
|
|
end
|
|
|
|
else
|
|
|
|
print_status("No response.")
|
|
|
|
end
|
|
|
|
end
|
2010-04-30 08:40:19 +00:00
|
|
|
end
|
|
|
|
|
|
|
|
end
|
2009-12-30 22:24:22 +00:00
|
|
|
end
|
|
|
|
end
|