Real-time collaboration for Jupyter Notebooks, Linux Terminals, LaTeX, VS Code, R IDE, and more,
all in one place.
Real-time collaboration for Jupyter Notebooks, Linux Terminals, LaTeX, VS Code, R IDE, and more,
all in one place.
Path: blob/master/lib/msf/core/auxiliary/scanner.rb
Views: 11784
# -*- coding: binary -*-12module Msf34###5#6# This module provides methods for scanning modules7#8###910module Auxiliary::Scanner1112class AttemptFailed < Msf::Auxiliary::Failed13end1415#16# Initializes an instance of a recon auxiliary module17#18def initialize(info = {})19super2021register_options([22Opt::RHOSTS,23OptInt.new('THREADS', [ true, "The number of concurrent threads (max one per host)", 1 ] )24], Auxiliary::Scanner)2526register_advanced_options([27OptBool.new('ShowProgress', [true, 'Display progress messages during a scan', true]),28OptInt.new('ShowProgressPercent', [true, 'The interval in percent that progress should be shown', 10])29], Auxiliary::Scanner)3031end3233def has_check?34respond_to?(:check_host)35end3637def check38nmod = replicant39begin40nmod.check_host(datastore['RHOST'])41rescue NoMethodError42Exploit::CheckCode::Unsupported43end44end454647def peer48# IPv4 addr can be 16 chars + 1 for : and + 5 for port49super.ljust(21)50end5152#53# The command handler when launched from the console54#55def run56@show_progress = datastore['ShowProgress']57@show_percent = datastore['ShowProgressPercent'].to_i5859if self.respond_to?(:session) && session60datastore['RHOSTS'] = session.address61end6263rhosts_walker = Msf::RhostsWalker.new(self.datastore['RHOSTS'], self.datastore).to_enum64@range_count = rhosts_walker.count || 065@range_done = 066@range_percent = 06768threads_max = datastore['THREADS'].to_i69@thread_list = []70@scan_errors = []7172res = Queue.new73results = Hash.new7475#76# Sanity check threading given different conditions77#7879if datastore['CPORT'].to_i != 0 && threads_max > 180print_error("Warning: A maximum of one thread is possible when a source port is set (CPORT)")81print_error("Thread count has been adjusted to 1")82threads_max = 183end8485if(Rex::Compat.is_windows)86if(threads_max > 16)87print_error("Warning: The Windows platform cannot reliably support more than 16 threads")88print_error("Thread count has been adjusted to 16")89threads_max = 1690end91end9293if(Rex::Compat.is_cygwin)94if(threads_max > 200)95print_error("Warning: The Cygwin platform cannot reliably support more than 200 threads")96print_error("Thread count has been adjusted to 200")97threads_max = 20098end99end100101begin102103if (self.respond_to?('run_host'))104loop do105# Stop scanning if we hit a fatal error106break if has_fatal_errors?107108# Spawn threads for each host109while (@thread_list.length < threads_max)110111# Stop scanning if we hit a fatal error112break if has_fatal_errors?113114begin115datastore = rhosts_walker.next116rescue StopIteration117datastore = nil118end119break unless datastore120121@thread_list << framework.threads.spawn("ScannerHost(#{self.refname})-#{datastore['RHOST']}", false, datastore.dup) do |thr_datastore|122targ = thr_datastore['RHOST']123nmod = self.replicant124nmod.datastore = thr_datastore125126begin127res << { targ => nmod.run_host(targ) }128rescue ::Rex::BindFailed129if datastore['CHOST']130@scan_errors << "The source IP (CHOST) value of #{datastore['CHOST']} was not usable"131end132rescue Msf::Auxiliary::Scanner::AttemptFailed => e133nmod.vprint_error("#{e}")134rescue ::Rex::ConnectionError, ::Rex::ConnectionProxyError, ::Errno::ECONNRESET, ::Errno::EINTR, ::Rex::TimeoutError, ::Timeout::Error, ::EOFError135rescue ::Interrupt,::NoMethodError, ::RuntimeError, ::ArgumentError, ::NameError136raise $!137rescue ::Exception => e138print_status("Error: #{targ}: #{e.class} #{e.message}")139elog("Error running against host #{targ}", error: e)140ensure141nmod.cleanup142end143end144end145146# Do as much of this work as possible while other threads are running147while !res.empty?148results.merge! res.pop149end150151# Stop scanning if we hit a fatal error152break if has_fatal_errors?153154# Exit once we run out of hosts155if(@thread_list.length == 0)156break157end158159# Attempt to wait for the oldest thread for a second,160# remove any finished threads from the list161# and continue on.162tla = @thread_list.length163@thread_list.first.join(1)164@thread_list.delete_if { |t| not t.alive? }165tlb = @thread_list.length166167@range_done += (tla - tlb)168scanner_show_progress() if @show_progress169end170171scanner_handle_fatal_errors172return results173end174175if (self.respond_to?('run_batch'))176177if (! self.respond_to?('run_batch_size'))178print_status("This module needs to export run_batch_size()")179return180end181182size = run_batch_size()183184rhosts_walker = Msf::RhostsWalker.new(self.datastore['RHOSTS'], self.datastore).to_enum185186while(true)187nohosts = false188189# Stop scanning if we hit a fatal error190break if has_fatal_errors?191192while (@thread_list.length < threads_max)193194batch = []195196# Create batches from each set197while (batch.length < size)198begin199datastore = rhosts_walker.next200rescue StopIteration201datastore = nil202end203if (not datastore)204nohosts = true205break206end207batch << datastore['RHOST']208end209210# Create a thread for each batch211if (batch.length > 0)212thread = framework.threads.spawn("ScannerBatch(#{self.refname})", false, batch) do |bat|213nmod = self.replicant214mybatch = bat.dup215begin216nmod.run_batch(mybatch)217rescue ::Rex::BindFailed218if datastore['CHOST']219@scan_errors << "The source IP (CHOST) value of #{datastore['CHOST']} was not usable"220end221rescue Msf::Auxiliary::Scanner::AttemptFailed => e222print_error("#{e}")223rescue ::Rex::ConnectionError, ::Rex::ConnectionProxyError, ::Errno::ECONNRESET, ::Errno::EINTR, ::Rex::TimeoutError, ::Timeout::Error224rescue ::Interrupt,::NoMethodError, ::RuntimeError, ::ArgumentError, ::NameError225raise $!226rescue ::Exception => e227print_status("Error: #{mybatch[0]}-#{mybatch[-1]}: #{e}")228ensure229nmod.cleanup230end231end232thread[:batch_size] = batch.length233@thread_list << thread234end235236# Exit once we run out of hosts237if (@thread_list.length == 0 or nohosts)238break239end240end241242# Stop scanning if we hit a fatal error243break if has_fatal_errors?244245# Exit if there are no more pending threads246if (@thread_list.length == 0)247break248end249250# Attempt to wait for the oldest thread for a second,251# remove any finished threads from the list252# and continue on.253tla = 0254@thread_list.map {|t| tla += t[:batch_size] if t[:batch_size] }255@thread_list.first.join(1)256@thread_list.delete_if { |t| not t.alive? }257tlb = 0258@thread_list.map {|t| tlb += t[:batch_size] if t[:batch_size] }259260@range_done += tla - tlb261scanner_show_progress() if @show_progress262end263264scanner_handle_fatal_errors265return266end267268print_error("This module defined no run_host or run_batch methods")269270rescue ::Interrupt271print_status("Caught interrupt from the console...")272return273ensure274seppuko!()275end276end277278def seppuko!279@thread_list.each do |t|280begin281t.kill if t.alive?282rescue ::Exception283end284end285end286287def has_fatal_errors?288@scan_errors && !@scan_errors.empty?289end290291def scanner_handle_fatal_errors292return unless has_fatal_errors?293return unless @thread_list294295# First kill any running threads296@thread_list.each {|t| t.kill if t.alive? }297298# Show the unique errors triggered by the scan299uniq_errors = @scan_errors.uniq300uniq_errors.each do |emsg|301print_error("Fatal: #{emsg}")302end303print_error("Scan terminated due to #{uniq_errors.size} fatal error(s)")304end305306def scanner_progress307return 0 unless @range_done and @range_count308pct = (@range_done / @range_count.to_f) * 100309end310311def scanner_show_progress312# it should already be in the process of shutting down if there are fatal errors313return if has_fatal_errors?314pct = scanner_progress315if pct >= (@range_percent + @show_percent)316@range_percent = @range_percent + @show_percent317tdlen = @range_count.to_s.length318print_status(sprintf("Scanned %#{tdlen}d of %d hosts (%d%% complete)", @range_done, @range_count, pct))319end320end321322def add_delay_jitter(_delay, _jitter)323# Introduce the delay324delay_value = _delay.to_i325original_value = delay_value326jitter_value = _jitter.to_i327328# Retrieve the jitter value and delay value329# Delay = number of milliseconds to wait between each request330# Jitter = percentage modifier. For example:331# Delay is 1000ms (i.e. 1 second), Jitter is 50.332# 50/100 = 0.5; 0.5*1000 = 500. Therefore, the per-request333# delay will be 1000 +/- a maximum of 500ms.334if delay_value > 0335if jitter_value > 0336rnd = Random.new337if (rnd.rand(2) == 0)338delay_value += rnd.rand(jitter_value)339else340delay_value -= rnd.rand(jitter_value)341end342if delay_value < 0343delay_value = 0344end345end346final_delay = delay_value.to_f / 1000.0347vprint_status("Delaying for #{final_delay} second(s) (#{original_value}ms +/- #{jitter_value}ms)")348sleep final_delay349end350end351352def fail_with(reason, msg = nil, abort: false)353if abort354# raising Failed will case the run to be aborted355raise Msf::Auxiliary::Failed, "#{reason.to_s}: #{msg}"356else357# raising AttemptFailed will cause the run_host / run_batch to be aborted358raise Msf::Auxiliary::Scanner::AttemptFailed, "#{reason.to_s}: #{msg}"359end360end361362end363end364365366