Class: Arachni::Framework

Inherits:
Object show all
Includes:
Mixins::Observable, UI::Output, Utilities
Defined in:
lib/arachni/framework.rb

Overview

The Framework class ties together all the components.

It should be wrapped by a UI class.

It’s the brains of the operation, it bosses the rest of the classes around.

It runs the audit, loads modules and reports and runs them according to user options.

Author:

Direct Known Subclasses

RPC::Server::Framework

Constant Summary collapse

REVISION =

the version of this class

'0.2.7'

Instance Attribute Summary collapse

Class Method Summary collapse

Instance Method Summary collapse

Methods included from Mixins::Observable

#method_missing

Methods included from Utilities

#cookie_encode, #cookies_from_document, #cookies_from_file, #cookies_from_response, #exception_jail, #exclude_path?, #extract_domain, #form_decode, #form_encode, #form_parse_request_body, #forms_from_document, #forms_from_response, #get_path, #hash_keys_to_str, #html_decode, #html_encode, #include_path?, #links_from_document, #links_from_response, #normalize_url, #page_from_response, #page_from_url, #parse_query, #parse_set_cookie, #parse_url_vars, #path_in_domain?, #path_too_deep?, #remove_constants, #seed, #skip_path?, #to_absolute, #uri_decode, #uri_encode, #uri_parse, #uri_parser, #url_sanitize

Methods included from UI::Output

#debug?, #debug_off, #debug_on, #disable_only_positives, #flush_buffer, #mute, #muted?, old_reset_output_options, #only_positives, #only_positives?, #print_bad, #print_debug, #print_debug_backtrace, #print_debug_pp, #print_error, #print_error_backtrace, #print_info, #print_line, #print_ok, #print_status, #print_verbose, #reroute_to_file, #reroute_to_file?, reset_output_options, #set_buffer_cap, #uncap_buffer, #unmute, #verbose, #verbose?

Constructor Details

#initialize(opts = Arachni::Options.instance) ⇒ Framework

Initializes system components.



135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
# File 'lib/arachni/framework.rb', line 135

def initialize( opts = Arachni::Options.instance )

    Encoding.default_external = 'BINARY'
    Encoding.default_internal = 'BINARY'

    @opts = opts

    @modules = Module::Manager.new( self )
    @reports = Report::Manager.new( @opts )
    @plugins = Plugin::Manager.new( self )

    @session = Session.new( @opts )
    reset_spider
    @http    = HTTP.instance

    # will store full-fledged pages generated by the Trainer since these
    # may not be be accessible simply by their URL
    # @page_queue = ::Arachni::Database::Queue.new
    @page_queue = Queue.new
    @page_queue_total_size = 0

    # will hold paths found by the spider in order to be converted to pages
    # and ultimately audited by the modules
    @url_queue = Queue.new
    @url_queue_total_size = 0

    # deep clone the redundancy rules to preserve their counter
    # for the reports
    @orig_redundant = @opts.redundant.deep_clone

    @running = false
    @status  = :ready
    @paused  = []

    @auditmap = []
    @sitemap  = []

    @current_url = ''
end

Dynamic Method Handling

This class handles dynamic methods through the method_missing method in the class Arachni::Mixins::Observable

Instance Attribute Details

#httpArachni::HTTP (readonly)



107
108
109
# File 'lib/arachni/framework.rb', line 107

def http
  @http
end

#modulesArachni::Module::Manager (readonly)



91
92
93
# File 'lib/arachni/framework.rb', line 91

def modules
  @modules
end

#optsOptions (readonly)

Instance options



81
82
83
# File 'lib/arachni/framework.rb', line 81

def opts
  @opts
end

#page_queue_total_sizeInteger (readonly)

Total number of pages added to their audit queue



121
122
123
# File 'lib/arachni/framework.rb', line 121

def page_queue_total_size
  @page_queue_total_size
end

#pluginsArachni::Plugin::Manager (readonly)



96
97
98
# File 'lib/arachni/framework.rb', line 96

def plugins
  @plugins
end

#reportsArachni::Report::Manager (readonly)



86
87
88
# File 'lib/arachni/framework.rb', line 86

def reports
  @reports
end

#sessionSession (readonly)



99
100
101
# File 'lib/arachni/framework.rb', line 99

def session
  @session
end

#sitemapArray (readonly)

URLs of all discovered pages



114
115
116
# File 'lib/arachni/framework.rb', line 114

def sitemap
  @sitemap
end

#spiderSpider (readonly)



102
103
104
# File 'lib/arachni/framework.rb', line 102

def spider
  @spider
end

#url_queue_total_sizeInteger (readonly)

Total number of urls added to their audit queue



128
129
130
# File 'lib/arachni/framework.rb', line 128

def url_queue_total_size
  @url_queue_total_size
end

Class Method Details

.resetObject

Resets everything and allows the framework to be re-used.

You should first update Options.



579
580
581
582
583
584
585
586
587
# File 'lib/arachni/framework.rb', line 579

def self.reset
    Module::Auditor.reset
    Module::ElementDB.reset
    Element::Capabilities::Auditable.reset
    Module::Manager.reset
    Plugin::Manager.reset
    Report::Manager.reset
    HTTP.reset
end

Instance Method Details

#audit_storeAuditStore Also known as: auditstore

Returns the results of the audit as an AuditStore instance

See Also:



372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
# File 'lib/arachni/framework.rb', line 372

def audit_store
    opts = @opts.to_hash.deep_clone

    # restore the original redundancy rules and their counters
    opts['redundant'] = @orig_redundant
    opts['mods'] = @modules.keys

    AuditStore.new(
        version:  version,
        revision: revision,
        options:  opts,
        sitemap:  auditstore_sitemap || [],
        issues:   @modules.results.deep_clone,
        plugins:  @plugins.results
    )
end

#clean_up(skip_audit_queue = false) ⇒ True Also known as: clean_up!

Cleans up the framework; should be called after running the audit or after canceling a running scan.

It stops the clock, waits for the plugins to finish up, registers their results and also refreshes the auditstore.

It also runs #audit_queue in case any new pages have been added by the plugins.



529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
# File 'lib/arachni/framework.rb', line 529

def clean_up( skip_audit_queue = false )
    @status = :cleanup

    @opts.finish_datetime  = Time.now
    @opts.start_datetime ||= Time.now

    @opts.delta_time = @opts.finish_datetime - @opts.start_datetime

    # make sure this is disabled or it'll break report output
    disable_only_positives

    @running = false

    # wait for the plugins to finish
    @plugins.block

    # a plug-in may have updated the page queue, rock it!
    audit_queue if !skip_audit_queue
    true
end

#lsmodArray<Hash>

Returns an array of hashes with information about all available modules



396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
# File 'lib/arachni/framework.rb', line 396

def lsmod
    loaded = @modules.loaded
    @modules.clear
    @modules.available.map do |name|
        path = @modules.name_to_path( name )
        next if !lsmod_match?( path )

        @modules[name].info.merge(
            mod_name: name,
            author:   [@modules[name].info[:author]].flatten.map { |a| a.strip },
            path:     path.strip
        )
    end.compact
ensure
    @modules.clear
    @modules.load( loaded )
end

#lsplugArray<Hash>

Returns an array of hashes with information about all available reports



444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
# File 'lib/arachni/framework.rb', line 444

def lsplug
    loaded = @plugins.loaded
    @plugins.clear
    @plugins.available.map do |plugin|
        path = @plugins.name_to_path( plugin )
        next if !lsplug_match?( path )

        @plugins[plugin].info.merge(
            plug_name: plugin,
            path:      path,
            author:    [@plugins[plugin].info[:author]].flatten.map { |a| a.strip }
        )
    end.compact
ensure
    @plugins.clear
    @plugins.load( loaded )
end

#lsrepArray<Hash>

Returns an array of hashes with information about all available reports



420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
# File 'lib/arachni/framework.rb', line 420

def lsrep
    loaded = @reports.loaded
    @reports.clear
    @reports.available.map do |report|
        path = @reports.name_to_path( report )
        next if !lsrep_match?( path )

        @reports[report].info.merge(
            rep_name: report,
            path:     path,
            author:   [@reports[report].info[:author]].flatten.map { |a| a.strip }
        )
    end.compact
ensure
    @reports.clear
    @reports.load( loaded )
end

#on_run_mods(&block) ⇒ Object



551
552
553
# File 'lib/arachni/framework.rb', line 551

def on_run_mods( &block )
    add_on_run_mods( &block )
end

#pauseTrueClass Also known as: pause!



480
481
482
483
484
# File 'lib/arachni/framework.rb', line 480

def pause
    spider.pause
    @paused << caller
    true
end

#paused?Bool



472
473
474
# File 'lib/arachni/framework.rb', line 472

def paused?
    !@paused.empty?
end

#push_to_page_queue(page) ⇒ Object

Pushes a page to the page audit queue and updates #page_queue_total_size



346
347
348
349
350
351
# File 'lib/arachni/framework.rb', line 346

def push_to_page_queue( page )
    @page_queue << page
    @page_queue_total_size += 1

    @sitemap |= [page.url]
end

#push_to_url_queue(url) ⇒ Object

Pushes a URL to the URL audit queue and updates #url_queue_total_size



356
357
358
359
360
361
362
363
# File 'lib/arachni/framework.rb', line 356

def push_to_url_queue( url )
    abs = to_absolute( url )

    @url_queue.push( abs ? abs : url )
    @url_queue_total_size += 1

    @sitemap |= [url]
end

#resetObject

Resets everything and allows the framework to be re-used.

You should first update Options.

Prefer this if you already have an instance.



566
567
568
569
570
571
572
# File 'lib/arachni/framework.rb', line 566

def reset
    reset_spider
    @modules.clear
    @reports.clear
    @plugins.clear
    self.class.reset
end

#reset_spiderObject



555
556
557
# File 'lib/arachni/framework.rb', line 555

def reset_spider
    @spider = Spider.new( @opts )
end

#resumeTrueClass Also known as: resume!



490
491
492
493
494
# File 'lib/arachni/framework.rb', line 490

def resume
    @paused.delete( caller )
    spider.resume
    true
end

#revisionString

Returns the revision of the Arachni::Framework (this) class



511
512
513
# File 'lib/arachni/framework.rb', line 511

def revision
    REVISION
end

#run(&block) ⇒ Object

Runs the system

It parses the instance options, #prepare, runs the #audit and #clean_up!.



183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
# File 'lib/arachni/framework.rb', line 183

def run( &block )
    prepare

    # catch exceptions so that if something breaks down or the user opted to
    # exit the reports will still run with whatever results Arachni managed to gather
    exception_jail( false ){ audit }

    clean_up
    exception_jail( false ){ block.call } if block_given?
    @status = :done

    # run reports
    @reports.run( audit_store ) if !@reports.empty?

    true
end

#running?Bool



465
466
467
# File 'lib/arachni/framework.rb', line 465

def running?
    @running
end

#stats(refresh_time = false, override_refresh = false) ⇒ Hash

Returns the following framework stats:

  • :requests – HTTP request count

  • :responses – HTTP response count

  • :time_out_count – Amount of timed-out requests

  • :time – Amount of running time

  • :avg – Average requests per second

  • :sitemap_size – Number of discovered pages

  • :auditmap_size – Number of audited pages

  • :progress – Progress percentage

  • :curr_res_time – Average response time for the current burst of requests

  • :curr_res_cnt – Amount of responses for the current burst

  • :curr_avg – Average requests per second for the current burst

  • :average_res_time – Average response time

  • :max_concurrency – Current maximum concurrency of HTTP requests

  • :current_page – URL of the currently audited page

  • :eta – Estimated time of arrival i.e. estimated remaining time



246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
# File 'lib/arachni/framework.rb', line 246

def stats( refresh_time = false, override_refresh = false )
    req_cnt = http.request_count
    res_cnt = http.response_count

    @opts.start_datetime = Time.now if !@opts.start_datetime

    sitemap_sz  = @sitemap.size
    auditmap_sz = @auditmap.size

    if( !refresh_time || auditmap_sz == sitemap_sz ) && !override_refresh
        @opts.delta_time ||= Time.now - @opts.start_datetime
    else
        @opts.delta_time = Time.now - @opts.start_datetime
    end

    avg = 0
    avg = (res_cnt / @opts.delta_time).to_i if res_cnt > 0

    # we need to remove URLs that lead to redirects from the sitemap
    # when calculating the progress %.
    #
    # this is because even though these URLs are valid webapp paths
    # they are not actual pages and thus can't be audited;
    # so the sitemap and auditmap will never match and the progress will
    # never get to 100% which may confuse users.
    #
    redir_sz = spider.redirects.size

    #
    # There are 2 audit phases:
    #  * regular analysis attacks
    #  * timing attacks
    #
    # When calculating the progress % we have to take both into account,
    # however each is calculated using different criteria.
    #
    # Progress of regular attacks is calculated as:
    #     amount of audited pages / amount of all discovered pages
    #
    # However, the progress of the timing attacks is calculated as:
    #     amount of called timeout blocks / amount of total blocks
    #
    # The timing attack modules are run with the regular ones however
    # their procedures are piled up into an array of Procs
    # which are called after the regular attacks.
    #
    # So when we reach the point of needing to include their progress in
    # the overall progress percentage we'll be working with accurate
    # data regarding the total blocks, etc.
    #

    #
    # If we have timing attacks then each phase must account for half
    # of the progress.
    #
    # This is not very granular but it's good enough for now...
    #
    multi = Module::Auditor.timeout_loaded_modules.size > 0 ? 50 : 100
    progress = (Float( auditmap_sz ) / ( sitemap_sz - redir_sz ) ) * multi

    if Module::Auditor.running_timeout_attacks?
        called_blocks = Module::Auditor.timeout_audit_operations_cnt -
            Module::Auditor.current_timeout_audit_operations_cnt

        progress += ( Float( called_blocks ) /
            Module::Auditor.timeout_audit_operations_cnt ) * multi
    end

    begin
        progress = Float( sprintf( "%.2f", progress ) )
    rescue
        progress = 0.0
    end

    # sometimes progress may slightly exceed 100%
    # which can cause a few strange stuff to happen
    progress = 100.0 if progress > 100.0
    pb = Mixins::ProgressBar.eta( progress, @opts.start_datetime )
    {
        requests:         req_cnt,
        responses:        res_cnt,
        time_out_count:   http.time_out_count,
        time:             audit_store.delta_time,
        avg:              avg,
        sitemap_size:     @sitemap.size,
        auditmap_size:    auditmap_sz,
        progress:         progress,
        curr_res_time:    http.curr_res_time,
        curr_res_cnt:     http.curr_res_cnt,
        curr_avg:         http.curr_res_per_second,
        average_res_time: http.average_res_time,
        max_concurrency:  http.max_concurrency,
        current_page:     @current_url,
        eta:              pb
    }
end

#statusString

Returns the status of the instance as a string.

Possible values are (in order):

  • ready – Just initialised and waiting for instructions

  • preparing – Getting ready to start (i.e. initing plugins etc.)

  • crawling – The instance is crawling the target webapp

  • auditing– The instance is currently auditing the webapp

  • paused – The instance has posed (if applicable)

  • cleanup – The scan has completed and the instance is cleaning up after itself (i.e. waiting for plugins to finish etc.).

  • done – The scan has completed



215
216
217
218
# File 'lib/arachni/framework.rb', line 215

def status
    return 'paused' if paused?
    @status.to_s
end

#versionString

Returns the version of the framework



502
503
504
# File 'lib/arachni/framework.rb', line 502

def version
    Arachni::VERSION
end