2012-06-29 05:18:28 +00:00
|
|
|
# -*- coding: binary -*-
|
2006-08-12 08:31:38 +00:00
|
|
|
module Msf
|
|
|
|
|
|
|
|
###
|
|
|
|
#
|
2006-08-12 23:08:20 +00:00
|
|
|
# This module provides methods for scanning modules
|
2006-08-12 08:31:38 +00:00
|
|
|
#
|
|
|
|
###
|
|
|
|
|
|
|
|
module Auxiliary::Scanner
|
|
|
|
|
2006-08-12 23:08:20 +00:00
|
|
|
|
2006-08-12 08:31:38 +00:00
|
|
|
#
|
|
|
|
# Initializes an instance of a recon auxiliary module
|
|
|
|
#
|
|
|
|
def initialize(info = {})
|
2013-08-30 21:28:33 +00:00
|
|
|
super
|
2006-08-12 08:31:38 +00:00
|
|
|
|
2013-08-30 21:28:33 +00:00
|
|
|
register_options([
|
|
|
|
OptAddressRange.new('RHOSTS', [ true, "The target address range or CIDR identifier"]),
|
|
|
|
OptInt.new('THREADS', [ true, "The number of concurrent threads", 1 ] )
|
|
|
|
], Auxiliary::Scanner)
|
2009-11-17 12:45:34 +00:00
|
|
|
|
2013-08-30 21:28:33 +00:00
|
|
|
# RHOST should not be used in scanner modules, only RHOSTS
|
|
|
|
deregister_options('RHOST')
|
2009-11-17 12:45:34 +00:00
|
|
|
|
2013-08-30 21:28:33 +00:00
|
|
|
register_advanced_options([
|
|
|
|
OptBool.new('ShowProgress', [true, 'Display progress messages during a scan', true]),
|
|
|
|
OptInt.new('ShowProgressPercent', [true, 'The interval in percent that progress should be shown', 10])
|
|
|
|
], Auxiliary::Scanner)
|
2012-03-08 19:07:03 +00:00
|
|
|
|
2006-08-12 08:31:38 +00:00
|
|
|
end
|
|
|
|
|
2006-08-12 23:08:20 +00:00
|
|
|
|
2014-01-24 23:54:40 +00:00
|
|
|
def check
|
2014-01-26 22:25:30 +00:00
|
|
|
nmod = replicant
|
2014-01-26 02:25:01 +00:00
|
|
|
begin
|
2014-01-26 22:25:30 +00:00
|
|
|
nmod.check_host(datastore['RHOST'])
|
2014-01-26 02:25:01 +00:00
|
|
|
rescue NoMethodError
|
2014-01-26 22:25:30 +00:00
|
|
|
Exploit::CheckCode::Unsupported
|
2014-01-26 02:25:01 +00:00
|
|
|
end
|
2014-01-24 23:54:40 +00:00
|
|
|
end
|
|
|
|
|
|
|
|
|
2006-08-12 08:31:38 +00:00
|
|
|
#
|
|
|
|
# The command handler when launched from the console
|
|
|
|
#
|
|
|
|
def run
|
2006-12-10 06:04:51 +00:00
|
|
|
|
2013-08-30 21:28:33 +00:00
|
|
|
@show_progress = datastore['ShowProgress']
|
|
|
|
@show_percent = datastore['ShowProgressPercent'].to_i
|
|
|
|
|
|
|
|
ar = Rex::Socket::RangeWalker.new(datastore['RHOSTS'])
|
|
|
|
@range_count = ar.length || 0
|
|
|
|
@range_done = 0
|
|
|
|
@range_percent = 0
|
|
|
|
|
|
|
|
threads_max = datastore['THREADS'].to_i
|
|
|
|
@tl = []
|
2014-11-11 20:59:41 +00:00
|
|
|
@scan_errors = []
|
2013-08-30 21:28:33 +00:00
|
|
|
|
|
|
|
#
|
2014-11-11 19:21:09 +00:00
|
|
|
# Sanity check threading given different conditions
|
2013-08-30 21:28:33 +00:00
|
|
|
#
|
|
|
|
|
2014-11-11 19:21:09 +00:00
|
|
|
if datastore['CPORT'].to_i != 0 && threads_max > 1
|
|
|
|
print_error("Warning: A maximum of one thread is possible when a source port is set (CPORT)")
|
|
|
|
print_error("Thread count has been adjusted to 1")
|
|
|
|
threads_max = 1
|
|
|
|
end
|
|
|
|
|
2013-08-30 21:28:33 +00:00
|
|
|
if(Rex::Compat.is_windows)
|
|
|
|
if(threads_max > 16)
|
|
|
|
print_error("Warning: The Windows platform cannot reliably support more than 16 threads")
|
|
|
|
print_error("Thread count has been adjusted to 16")
|
|
|
|
threads_max = 16
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
|
|
|
if(Rex::Compat.is_cygwin)
|
|
|
|
if(threads_max > 200)
|
|
|
|
print_error("Warning: The Cygwin platform cannot reliably support more than 200 threads")
|
|
|
|
print_error("Thread count has been adjusted to 200")
|
|
|
|
threads_max = 200
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
|
|
|
begin
|
|
|
|
|
|
|
|
if (self.respond_to?('run_range'))
|
2014-11-11 20:59:41 +00:00
|
|
|
# No automated progress reporting or error handling for run_range
|
2013-08-30 21:28:33 +00:00
|
|
|
return run_range(datastore['RHOSTS'])
|
|
|
|
end
|
|
|
|
|
|
|
|
if (self.respond_to?('run_host'))
|
|
|
|
|
2014-01-26 22:27:36 +00:00
|
|
|
loop do
|
2014-11-11 20:59:41 +00:00
|
|
|
# Stop scanning if we hit a fatal error
|
2014-11-12 15:32:06 +00:00
|
|
|
break if has_fatal_errors?
|
2014-11-11 20:59:41 +00:00
|
|
|
|
2013-08-30 21:28:33 +00:00
|
|
|
# Spawn threads for each host
|
|
|
|
while (@tl.length < threads_max)
|
2014-11-11 20:59:41 +00:00
|
|
|
|
|
|
|
# Stop scanning if we hit a fatal error
|
2014-11-12 15:32:06 +00:00
|
|
|
break if has_fatal_errors?
|
2014-11-11 20:59:41 +00:00
|
|
|
|
2013-08-30 21:28:33 +00:00
|
|
|
ip = ar.next_ip
|
|
|
|
break if not ip
|
|
|
|
|
|
|
|
@tl << framework.threads.spawn("ScannerHost(#{self.refname})-#{ip}", false, ip.dup) do |tip|
|
|
|
|
targ = tip
|
|
|
|
nmod = self.replicant
|
|
|
|
nmod.datastore['RHOST'] = targ
|
|
|
|
|
|
|
|
begin
|
|
|
|
nmod.run_host(targ)
|
2014-11-11 20:59:41 +00:00
|
|
|
rescue ::Rex::BindFailed
|
|
|
|
if datastore['CHOST']
|
|
|
|
@scan_errors << "The source IP (CHOST) value of #{datastore['CHOST']} was not usable"
|
|
|
|
end
|
2013-08-30 21:28:33 +00:00
|
|
|
rescue ::Rex::ConnectionError, ::Rex::ConnectionProxyError, ::Errno::ECONNRESET, ::Errno::EINTR, ::Rex::TimeoutError, ::Timeout::Error, ::EOFError
|
|
|
|
rescue ::Interrupt,::NoMethodError, ::RuntimeError, ::ArgumentError, ::NameError
|
|
|
|
raise $!
|
|
|
|
rescue ::Exception => e
|
|
|
|
print_status("Error: #{targ}: #{e.class} #{e.message}")
|
|
|
|
elog("Error running against host #{targ}: #{e.message}\n#{e.backtrace.join("\n")}")
|
|
|
|
ensure
|
|
|
|
nmod.cleanup
|
|
|
|
end
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
2014-11-11 20:59:41 +00:00
|
|
|
# Stop scanning if we hit a fatal error
|
2014-11-12 15:32:06 +00:00
|
|
|
break if has_fatal_errors?
|
2014-11-11 20:59:41 +00:00
|
|
|
|
2013-08-30 21:28:33 +00:00
|
|
|
# Exit once we run out of hosts
|
|
|
|
if(@tl.length == 0)
|
|
|
|
break
|
|
|
|
end
|
|
|
|
|
|
|
|
# Assume that the oldest thread will be one of the
|
|
|
|
# first to finish and wait for it. After that's
|
|
|
|
# done, remove any finished threads from the list
|
|
|
|
# and continue on. This will open up at least one
|
|
|
|
# spot for a new thread
|
|
|
|
tla = @tl.length
|
|
|
|
@tl.first.join
|
|
|
|
@tl.delete_if { |t| not t.alive? }
|
|
|
|
tlb = @tl.length
|
|
|
|
|
|
|
|
@range_done += (tla - tlb)
|
|
|
|
scanner_show_progress() if @show_progress
|
|
|
|
end
|
|
|
|
|
2014-11-12 15:32:06 +00:00
|
|
|
scanner_handle_fatal_errors
|
2013-08-30 21:28:33 +00:00
|
|
|
return
|
|
|
|
end
|
|
|
|
|
|
|
|
if (self.respond_to?('run_batch'))
|
|
|
|
|
|
|
|
if (! self.respond_to?('run_batch_size'))
|
|
|
|
print_status("This module needs to export run_batch_size()")
|
|
|
|
return
|
|
|
|
end
|
|
|
|
|
|
|
|
size = run_batch_size()
|
|
|
|
|
|
|
|
ar = Rex::Socket::RangeWalker.new(datastore['RHOSTS'])
|
|
|
|
|
|
|
|
while(true)
|
|
|
|
nohosts = false
|
2014-11-11 20:59:41 +00:00
|
|
|
|
|
|
|
# Stop scanning if we hit a fatal error
|
2014-11-12 15:32:06 +00:00
|
|
|
break if has_fatal_errors?
|
2014-11-11 20:59:41 +00:00
|
|
|
|
2013-08-30 21:28:33 +00:00
|
|
|
while (@tl.length < threads_max)
|
|
|
|
|
|
|
|
batch = []
|
|
|
|
|
|
|
|
# Create batches from each set
|
|
|
|
while (batch.length < size)
|
|
|
|
ip = ar.next_ip
|
|
|
|
if (not ip)
|
|
|
|
nohosts = true
|
|
|
|
break
|
|
|
|
end
|
|
|
|
batch << ip
|
|
|
|
end
|
|
|
|
|
|
|
|
# Create a thread for each batch
|
|
|
|
if (batch.length > 0)
|
|
|
|
thread = framework.threads.spawn("ScannerBatch(#{self.refname})", false, batch) do |bat|
|
|
|
|
nmod = self.replicant
|
|
|
|
mybatch = bat.dup
|
|
|
|
begin
|
|
|
|
nmod.run_batch(mybatch)
|
2014-11-11 20:59:41 +00:00
|
|
|
rescue ::Rex::BindFailed
|
|
|
|
if datastore['CHOST']
|
|
|
|
@scan_errors << "The source IP (CHOST) value of #{datastore['CHOST']} was not usable"
|
|
|
|
end
|
2013-08-30 21:28:33 +00:00
|
|
|
rescue ::Rex::ConnectionError, ::Rex::ConnectionProxyError, ::Errno::ECONNRESET, ::Errno::EINTR, ::Rex::TimeoutError, ::Timeout::Error
|
|
|
|
rescue ::Interrupt,::NoMethodError, ::RuntimeError, ::ArgumentError, ::NameError
|
|
|
|
raise $!
|
|
|
|
rescue ::Exception => e
|
|
|
|
print_status("Error: #{mybatch[0]}-#{mybatch[-1]}: #{e}")
|
|
|
|
ensure
|
|
|
|
nmod.cleanup
|
|
|
|
end
|
|
|
|
end
|
|
|
|
thread[:batch_size] = batch.length
|
|
|
|
@tl << thread
|
|
|
|
end
|
|
|
|
|
|
|
|
# Exit once we run out of hosts
|
|
|
|
if (@tl.length == 0 or nohosts)
|
|
|
|
break
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
2014-11-11 20:59:41 +00:00
|
|
|
# Stop scanning if we hit a fatal error
|
2014-11-12 15:32:06 +00:00
|
|
|
break if has_fatal_errors?
|
2014-11-11 20:59:41 +00:00
|
|
|
|
2013-08-30 21:28:33 +00:00
|
|
|
# Exit if there are no more pending threads
|
|
|
|
if (@tl.length == 0)
|
|
|
|
break
|
|
|
|
end
|
|
|
|
|
|
|
|
# Assume that the oldest thread will be one of the
|
|
|
|
# first to finish and wait for it. After that's
|
|
|
|
# done, remove any finished threads from the list
|
|
|
|
# and continue on. This will open up at least one
|
|
|
|
# spot for a new thread
|
|
|
|
tla = 0
|
|
|
|
@tl.map {|t| tla += t[:batch_size] }
|
|
|
|
@tl.first.join
|
|
|
|
@tl.delete_if { |t| not t.alive? }
|
|
|
|
tlb = 0
|
|
|
|
@tl.map {|t| tlb += t[:batch_size] }
|
|
|
|
|
|
|
|
@range_done += tla - tlb
|
|
|
|
scanner_show_progress() if @show_progress
|
|
|
|
end
|
|
|
|
|
2014-11-12 15:32:06 +00:00
|
|
|
scanner_handle_fatal_errors
|
2013-08-30 21:28:33 +00:00
|
|
|
return
|
|
|
|
end
|
|
|
|
|
|
|
|
print_error("This module defined no run_host, run_range or run_batch methods")
|
|
|
|
|
|
|
|
rescue ::Interrupt
|
|
|
|
print_status("Caught interrupt from the console...")
|
|
|
|
return
|
|
|
|
ensure
|
|
|
|
seppuko!()
|
|
|
|
end
|
2010-11-24 00:08:39 +00:00
|
|
|
end
|
|
|
|
|
|
|
|
def seppuko!
|
2013-08-30 21:28:33 +00:00
|
|
|
@tl.each do |t|
|
|
|
|
begin
|
|
|
|
t.kill if t.alive?
|
|
|
|
rescue ::Exception
|
|
|
|
end
|
|
|
|
end
|
2009-10-26 03:49:07 +00:00
|
|
|
end
|
2007-04-24 06:27:39 +00:00
|
|
|
|
2014-11-12 15:32:06 +00:00
|
|
|
def has_fatal_errors?
|
|
|
|
@scan_errors && !@scan_errors.empty?
|
|
|
|
end
|
|
|
|
|
|
|
|
def scanner_handle_fatal_errors
|
|
|
|
return unless has_fatal_errors?
|
2014-11-11 20:59:41 +00:00
|
|
|
return unless @tl
|
|
|
|
|
|
|
|
# First kill any running threads
|
|
|
|
@tl.each {|t| t.kill if t.alive? }
|
|
|
|
|
|
|
|
# Show the unique errors triggered by the scan
|
2014-11-12 15:38:07 +00:00
|
|
|
uniq_errors = @scan_errors.uniq
|
|
|
|
uniq_errors.each do |emsg|
|
2014-11-11 20:59:41 +00:00
|
|
|
print_error("Fatal: #{emsg}")
|
|
|
|
end
|
2014-11-12 15:38:07 +00:00
|
|
|
print_error("Scan terminated due to #{uniq_errors.size} fatal error(s)")
|
2014-11-11 20:59:41 +00:00
|
|
|
end
|
|
|
|
|
2010-01-14 18:20:32 +00:00
|
|
|
def scanner_progress
|
2013-08-30 21:28:33 +00:00
|
|
|
return 0 unless @range_done and @range_count
|
|
|
|
pct = (@range_done / @range_count.to_f) * 100
|
2010-01-14 18:20:32 +00:00
|
|
|
end
|
|
|
|
|
|
|
|
def scanner_show_progress
|
2014-11-12 15:32:06 +00:00
|
|
|
# it should already be in the process of shutting down if there are fatal errors
|
|
|
|
return if has_fatal_errors?
|
2013-08-30 21:28:33 +00:00
|
|
|
pct = scanner_progress
|
2014-10-31 17:25:01 +00:00
|
|
|
if pct >= (@range_percent + @show_percent)
|
2013-08-30 21:28:33 +00:00
|
|
|
@range_percent = @range_percent + @show_percent
|
|
|
|
tdlen = @range_count.to_s.length
|
2014-10-31 17:25:01 +00:00
|
|
|
print_status(sprintf("Scanned %#{tdlen}d of %d hosts (%d%% complete)", @range_done, @range_count, pct))
|
2013-08-30 21:28:33 +00:00
|
|
|
end
|
2006-08-12 08:31:38 +00:00
|
|
|
end
|
|
|
|
|
2015-12-05 15:02:31 +00:00
|
|
|
def add_delay_jitter(_delay, _jitter)
|
|
|
|
# Introduce the delay
|
|
|
|
delay_value = _delay.to_i
|
|
|
|
original_value = delay_value
|
|
|
|
jitter_value = _jitter.to_i
|
|
|
|
|
|
|
|
# Retrieve the jitter value and delay value
|
|
|
|
# Delay = number of milliseconds to wait between each request
|
|
|
|
# Jitter = percentage modifier. For example:
|
|
|
|
# Delay is 1000ms (i.e. 1 second), Jitter is 50.
|
|
|
|
# 50/100 = 0.5; 0.5*1000 = 500. Therefore, the per-request
|
|
|
|
# delay will be 1000 +/- a maximum of 500ms.
|
|
|
|
if delay_value>0
|
|
|
|
if jitter_value>0
|
|
|
|
rnd = Random.new
|
|
|
|
if (rnd.rand(2)==0)
|
|
|
|
delay_value += rnd.rand(jitter_value)
|
|
|
|
else
|
|
|
|
delay_value -= rnd.rand(jitter_value)
|
|
|
|
end
|
|
|
|
if delay_value<0
|
|
|
|
delay_value = 0
|
|
|
|
end
|
|
|
|
end
|
|
|
|
final_delay = delay_value.to_f/1000.0
|
|
|
|
vprint_status("Delaying for #{final_delay} second(s) (#{original_value}ms +/- #{jitter_value}ms)")
|
|
|
|
sleep final_delay
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
2006-08-12 08:31:38 +00:00
|
|
|
end
|
2008-11-03 09:17:08 +00:00
|
|
|
end
|
2009-11-17 12:45:34 +00:00
|
|
|
|