CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutSign UpSign In
rapid7

Real-time collaboration for Jupyter Notebooks, Linux Terminals, LaTeX, VS Code, R IDE, and more,
all in one place.

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