## # 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 Error Based SQL Injection Scanner', 'Description' => %q{ This module identifies the existence of Error Based SQL injection issues. Still requires a lot of work }, 'Author' => [ 'et [at] cyberspace.org' ], 'License' => BSD_LICENSE, 'Notes' => { 'Reliability' => UNKNOWN_RELIABILITY, 'Stability' => UNKNOWN_STABILITY, 'SideEffects' => UNKNOWN_SIDE_EFFECTS } ) ) register_options( [ OptEnum.new('METHOD', [true, 'HTTP Request Method', 'GET', ['GET', 'POST']]), OptString.new('PATH', [ true, "The path/file to test SQL injection", '/default.aspx']), OptString.new('QUERY', [ false, "HTTP URI Query", '']), OptString.new('DATA', [ false, "HTTP Body/Data Query", '']) ] ) register_advanced_options( [ OptBool.new('NoDetailMessages', [ false, "Do not display detailed test messages", true ]) ] ) end def run_host(ip) http_method = datastore['METHOD'].upcase qvars = nil sqlinj = [ [ "'", 'Single quote'], [ "')", 'Single quote and parenthesis'], [ "\"", 'Double quote'], [ "%u0027", 'unicode single quote'], [ "%u02b9", 'unicode single quote'], [ "%u02bc", 'unicode single quote'], [ "%u02c8", 'unicode single quote'], [ "%c0%27", 'unicode single quote'], [ "%c0%a7", 'unicode single quote'], [ "%e0%80%a7", 'unicode single quote'], [ "#{rand(10)}'", 'Random value with single quote'] ] errorstr = [ ["Unclosed quotation mark after the character string", 'MSSQL', 'string'], ["Syntax error in string in query expression", 'MSSQL', 'string'], ["Microsoft OLE DB Provider", 'MSSQL', 'unknown'], ["You have an error in your SQL syntax", 'MySQL', 'unknown'], ["java.sql.SQLException", 'unknown', 'unknown'], ["ORA-", 'ORACLE', 'unknown'], ["PLS-", 'ORACLE', 'unknown'], ["Syntax error", 'unknown', 'unknown'], ] # # Dealing with empty query/data and making them hashes. # if http_method == 'GET' if not datastore['QUERY'].empty? qvars = queryparse(datastore['QUERY']) # Now its a Hash else print_error("You need to set QUERY param for GET") return end else if not datastore['DATA'].empty? qvars = queryparse(datastore['DATA']) # Now its a Hash else print_error("You need to set DATA parameter for POST") return end end # # Send normal request to check if error is generated # (means the error is caused by other means) # # if http_method == 'POST' reqinfo = { 'uri' => normalize_uri(datastore['PATH']), 'query' => datastore['QUERY'], 'data' => datastore['DATA'], 'method' => http_method, 'ctype' => 'application/x-www-form-urlencoded', 'encode' => false } else reqinfo = { 'uri' => normalize_uri(datastore['PATH']), 'query' => datastore['QUERY'], 'method' => http_method, 'ctype' => 'application/x-www-form-urlencoded', 'encode' => false } end begin normalres = send_request_raw(reqinfo, 20) rescue ::Rex::ConnectionRefused, ::Rex::HostUnreachable, ::Rex::ConnectionTimeout rescue ::Timeout::Error, ::Errno::EPIPE end if !datastore['NoDetailMessages'] print_status("Normal request sent.") end found = false inje = nil dbt = nil injt = nil if normalres errorstr.each do |estr, dbtype, injtype| if normalres.body.include? estr found = true inje = estr dbt = dbtype injt = injtype end end if found print_error("[#{wmap_target_host}] Error string appears in the normal response, unable to test") print_error("[#{wmap_target_host}] Error string: '#{inje}'") print_error("[#{wmap_target_host}] DB TYPE: #{dbt}, Error type '#{injt}'") report_web_vuln( :host => ip, :port => rport, :vhost => vhost, :ssl => ssl, :path => datastore['PATH'], :method => datastore['METHOD'], :pname => "", :proof => "Error: #{inje}", :risk => 2, :confidence => 50, :category => 'Database error', :description => "Error string appears in the normal response #{inje} #{dbt}", :name => 'Database error' ) return end else print_error("[#{wmap_target_host}] No response") return end # # Test URI Query parameters # found = false if qvars sqlinj.each do |istr, idesc| if found break end qvars.each do |key, value| if http_method == 'POST' qvars = queryparse(datastore['DATA']) # Now its a Hash else qvars = queryparse(datastore['QUERY']) # Now its a Hash end qvars[key] = qvars[key] + istr if !datastore['NoDetailMessages'] print_status("- Testing query with #{idesc}. Parameter #{key}:") end fstr = "" qvars.each_pair do |var, val| fstr += var + "=" + val + "&" end if http_method == 'POST' reqinfo = { 'uri' => normalize_uri(datastore['PATH']), 'query' => datastore['QUERY'], 'data' => fstr, 'method' => http_method, 'ctype' => 'application/x-www-form-urlencoded', 'encode' => false } else reqinfo = { 'uri' => normalize_uri(datastore['PATH']), 'query' => fstr, 'method' => http_method, 'ctype' => 'application/x-www-form-urlencoded', 'encode' => false } end begin testres = send_request_raw(reqinfo, 20) rescue ::Rex::ConnectionRefused, ::Rex::HostUnreachable, ::Rex::ConnectionTimeout rescue ::Timeout::Error, ::Errno::EPIPE end if testres errorstr.each do |estr, dbtype, injtype| if testres.body.include? estr found = true inje = estr dbt = dbtype injt = injtype end end if found print_good("[#{wmap_target_host}] SQL Injection found. (#{idesc}) (#{datastore['PATH']})") print_good("[#{wmap_target_host}] Error string: '#{inje}' Test Value: #{qvars[key]}") print_good("[#{wmap_target_host}] Vuln query parameter: #{key} DB TYPE: #{dbt}, Error type '#{injt}'") report_web_vuln( :host => ip, :port => rport, :vhost => vhost, :ssl => ssl, :path => datastore['PATH'], :method => datastore['METHOD'], :pname => key, :proof => istr, :risk => 2, :confidence => 50, :category => 'SQL injection', :description => "Error string appears in the normal response #{inje} #{dbt}", :name => 'SQL injection' ) return end else print_error("[#{wmap_target_host}] No response") return end end end if http_method == 'POST' qvars = queryparse(datastore['DATA']) # Now its a Hash else qvars = queryparse(datastore['QUERY']) # Now its a Hash end end end end