Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
rapid7
GitHub Repository: rapid7/metasploit-framework
Path: blob/master/lib/msf/core/auxiliary/scanner.rb
19670 views
1
# -*- coding: binary -*-
2
3
module Msf
4
5
###
6
#
7
# This module provides methods for scanning modules
8
#
9
###
10
11
module Auxiliary::Scanner
12
13
include Msf::Auxiliary::MultipleTargetHosts
14
15
class AttemptFailed < Msf::Auxiliary::Failed
16
end
17
18
#
19
# Initializes an instance of a recon auxiliary module
20
#
21
def initialize(info = {})
22
super
23
24
register_options([
25
Opt::RHOSTS,
26
OptInt.new('THREADS', [ true, "The number of concurrent threads (max one per host)", 1 ] )
27
], Auxiliary::Scanner)
28
29
register_advanced_options([
30
OptBool.new('ShowProgress', [true, 'Display progress messages during a scan', true]),
31
OptInt.new('ShowProgressPercent', [true, 'The interval in percent that progress should be shown', 10])
32
], Auxiliary::Scanner)
33
34
end
35
36
def peer
37
# IPv4 addr can be 16 chars + 1 for : and + 5 for port
38
super.ljust(21)
39
end
40
41
#
42
# The command handler when launched from the console
43
#
44
def run
45
@show_progress = datastore['ShowProgress']
46
@show_percent = datastore['ShowProgressPercent'].to_i
47
48
if self.respond_to?(:session) && session
49
datastore['RHOSTS'] = session.address
50
end
51
52
rhosts_walker = Msf::RhostsWalker.new(self.datastore['RHOSTS'], self.datastore).to_enum
53
@range_count = rhosts_walker.count || 0
54
@range_done = 0
55
@range_percent = 0
56
57
threads_max = datastore['THREADS'].to_i
58
@thread_list = []
59
@scan_errors = []
60
61
res = Queue.new
62
results = Hash.new
63
64
#
65
# Sanity check threading given different conditions
66
#
67
68
if datastore['CPORT'].to_i != 0 && threads_max > 1
69
print_error("Warning: A maximum of one thread is possible when a source port is set (CPORT)")
70
print_error("Thread count has been adjusted to 1")
71
threads_max = 1
72
end
73
74
if(Rex::Compat.is_windows)
75
if(threads_max > 16)
76
print_error("Warning: The Windows platform cannot reliably support more than 16 threads")
77
print_error("Thread count has been adjusted to 16")
78
threads_max = 16
79
end
80
end
81
82
if(Rex::Compat.is_cygwin)
83
if(threads_max > 200)
84
print_error("Warning: The Cygwin platform cannot reliably support more than 200 threads")
85
print_error("Thread count has been adjusted to 200")
86
threads_max = 200
87
end
88
end
89
90
begin
91
92
if (self.respond_to?('run_host'))
93
loop do
94
# Stop scanning if we hit a fatal error
95
break if has_fatal_errors?
96
97
# Spawn threads for each host
98
while (@thread_list.length < threads_max)
99
100
# Stop scanning if we hit a fatal error
101
break if has_fatal_errors?
102
103
begin
104
datastore = rhosts_walker.next
105
rescue StopIteration
106
datastore = nil
107
end
108
break unless datastore
109
110
@thread_list << framework.threads.spawn("ScannerHost(#{self.refname})-#{datastore['RHOST']}", false, datastore.dup) do |thr_datastore|
111
targ = thr_datastore['RHOST']
112
nmod = self.replicant
113
nmod.datastore = thr_datastore
114
115
begin
116
res << { targ => nmod.run_host(targ) }
117
rescue ::Rex::BindFailed
118
if datastore['CHOST']
119
@scan_errors << "The source IP (CHOST) value of #{datastore['CHOST']} was not usable"
120
end
121
rescue Msf::Auxiliary::Scanner::AttemptFailed => e
122
nmod.vprint_error("#{e}")
123
rescue ::Rex::ConnectionError, ::Rex::ConnectionProxyError, ::Errno::ECONNRESET, ::Errno::EINTR, ::Rex::TimeoutError, ::Timeout::Error, ::EOFError
124
rescue ::Interrupt,::NoMethodError, ::RuntimeError, ::ArgumentError, ::NameError
125
raise $!
126
rescue ::Exception => e
127
print_status("Error: #{targ}: #{e.class} #{e.message}")
128
elog("Error running against host #{targ}", error: e)
129
ensure
130
nmod.cleanup
131
end
132
end
133
end
134
135
# Do as much of this work as possible while other threads are running
136
while !res.empty?
137
results.merge! res.pop
138
end
139
140
# Stop scanning if we hit a fatal error
141
break if has_fatal_errors?
142
143
# Exit once we run out of hosts
144
if(@thread_list.length == 0)
145
break
146
end
147
148
# Attempt to wait for the oldest thread for a second,
149
# remove any finished threads from the list
150
# and continue on.
151
tla = @thread_list.length
152
@thread_list.first.join(1)
153
@thread_list.delete_if { |t| not t.alive? }
154
tlb = @thread_list.length
155
156
@range_done += (tla - tlb)
157
scanner_show_progress() if @show_progress
158
end
159
160
scanner_handle_fatal_errors
161
return results
162
end
163
164
if (self.respond_to?('run_batch'))
165
166
if (! self.respond_to?('run_batch_size'))
167
print_status("This module needs to export run_batch_size()")
168
return
169
end
170
171
size = run_batch_size()
172
173
rhosts_walker = Msf::RhostsWalker.new(self.datastore['RHOSTS'], self.datastore).to_enum
174
175
while(true)
176
nohosts = false
177
178
# Stop scanning if we hit a fatal error
179
break if has_fatal_errors?
180
181
while (@thread_list.length < threads_max)
182
183
batch = []
184
185
# Create batches from each set
186
while (batch.length < size)
187
begin
188
datastore = rhosts_walker.next
189
rescue StopIteration
190
datastore = nil
191
end
192
if (not datastore)
193
nohosts = true
194
break
195
end
196
batch << datastore['RHOST']
197
end
198
199
# Create a thread for each batch
200
if (batch.length > 0)
201
thread = framework.threads.spawn("ScannerBatch(#{self.refname})", false, batch) do |bat|
202
nmod = self.replicant
203
mybatch = bat.dup
204
begin
205
nmod.run_batch(mybatch)
206
rescue ::Rex::BindFailed
207
if datastore['CHOST']
208
@scan_errors << "The source IP (CHOST) value of #{datastore['CHOST']} was not usable"
209
end
210
rescue Msf::Auxiliary::Scanner::AttemptFailed => e
211
print_error("#{e}")
212
rescue ::Rex::ConnectionError, ::Rex::ConnectionProxyError, ::Errno::ECONNRESET, ::Errno::EINTR, ::Rex::TimeoutError, ::Timeout::Error
213
rescue ::Interrupt,::NoMethodError, ::RuntimeError, ::ArgumentError, ::NameError
214
raise $!
215
rescue ::Exception => e
216
print_status("Error: #{mybatch[0]}-#{mybatch[-1]}: #{e}")
217
ensure
218
nmod.cleanup
219
end
220
end
221
thread[:batch_size] = batch.length
222
@thread_list << thread
223
end
224
225
# Exit once we run out of hosts
226
if (@thread_list.length == 0 or nohosts)
227
break
228
end
229
end
230
231
# Stop scanning if we hit a fatal error
232
break if has_fatal_errors?
233
234
# Exit if there are no more pending threads
235
if (@thread_list.length == 0)
236
break
237
end
238
239
# Attempt to wait for the oldest thread for a second,
240
# remove any finished threads from the list
241
# and continue on.
242
tla = 0
243
@thread_list.map {|t| tla += t[:batch_size] if t[:batch_size] }
244
@thread_list.first.join(1)
245
@thread_list.delete_if { |t| not t.alive? }
246
tlb = 0
247
@thread_list.map {|t| tlb += t[:batch_size] if t[:batch_size] }
248
249
@range_done += tla - tlb
250
scanner_show_progress() if @show_progress
251
end
252
253
scanner_handle_fatal_errors
254
return
255
end
256
257
print_error("This module defined no run_host or run_batch methods")
258
259
rescue ::Interrupt
260
print_status("Caught interrupt from the console...")
261
return
262
ensure
263
seppuko!()
264
end
265
end
266
267
def seppuko!
268
@thread_list.each do |t|
269
begin
270
t.kill if t.alive?
271
rescue ::Exception
272
end
273
end
274
end
275
276
def has_fatal_errors?
277
@scan_errors && !@scan_errors.empty?
278
end
279
280
def scanner_handle_fatal_errors
281
return unless has_fatal_errors?
282
return unless @thread_list
283
284
# First kill any running threads
285
@thread_list.each {|t| t.kill if t.alive? }
286
287
# Show the unique errors triggered by the scan
288
uniq_errors = @scan_errors.uniq
289
uniq_errors.each do |emsg|
290
print_error("Fatal: #{emsg}")
291
end
292
print_error("Scan terminated due to #{uniq_errors.size} fatal error(s)")
293
end
294
295
def scanner_progress
296
return 0 unless @range_done and @range_count
297
pct = (@range_done / @range_count.to_f) * 100
298
end
299
300
def scanner_show_progress
301
# it should already be in the process of shutting down if there are fatal errors
302
return if has_fatal_errors?
303
pct = scanner_progress
304
if pct >= (@range_percent + @show_percent)
305
@range_percent = @range_percent + @show_percent
306
tdlen = @range_count.to_s.length
307
print_status(sprintf("Scanned %#{tdlen}d of %d hosts (%d%% complete)", @range_done, @range_count, pct))
308
end
309
end
310
311
def add_delay_jitter(_delay, _jitter)
312
# Introduce the delay
313
delay_value = _delay.to_i
314
original_value = delay_value
315
jitter_value = _jitter.to_i
316
317
# Retrieve the jitter value and delay value
318
# Delay = number of milliseconds to wait between each request
319
# Jitter = percentage modifier. For example:
320
# Delay is 1000ms (i.e. 1 second), Jitter is 50.
321
# 50/100 = 0.5; 0.5*1000 = 500. Therefore, the per-request
322
# delay will be 1000 +/- a maximum of 500ms.
323
if delay_value > 0
324
if jitter_value > 0
325
rnd = Random.new
326
if (rnd.rand(2) == 0)
327
delay_value += rnd.rand(jitter_value)
328
else
329
delay_value -= rnd.rand(jitter_value)
330
end
331
if delay_value < 0
332
delay_value = 0
333
end
334
end
335
final_delay = delay_value.to_f / 1000.0
336
vprint_status("Delaying for #{final_delay} second(s) (#{original_value}ms +/- #{jitter_value}ms)")
337
sleep final_delay
338
end
339
end
340
341
def fail_with(reason, msg = nil, abort: false)
342
if abort
343
# raising Failed will case the run to be aborted
344
raise Msf::Auxiliary::Failed, "#{reason.to_s}: #{msg}"
345
else
346
# raising AttemptFailed will cause the run_host / run_batch to be aborted
347
raise Msf::Auxiliary::Scanner::AttemptFailed, "#{reason.to_s}: #{msg}"
348
end
349
end
350
351
end
352
end
353
354