Module: Arachni::Module::Auditor

Includes:
Output
Included in:
Base
Defined in:
lib/arachni/module/auditor.rb

Overview

Included by Base and provides helper audit methods to all modules.

There are 3 main types of audit and analysis techniques available:

It should be noted that actual analysis takes place at the element level, and to be more specific, the Element::Capabilities::Auditable element level.

It also provides:

Author:

Constant Summary collapse

Format =

Holds constant bitfields that describe the preferred formatting of injection strings.

Element::Capabilities::Mutable::Format
OPTIONS =

Default audit options.

{
    #
    # Elements to audit.
    #
    # If no elements have been passed to audit methods, candidates will be
    # determined by {#each_candidate_element}.
    #
    elements: [Element::LINK, Element::FORM,
               Element::COOKIE, Element::HEADER,
               Element::BODY],

    #
    # If set to `true` the HTTP response will be analyzed for new elements.
    # Be careful when enabling it, there'll be a performance penalty.
    #
    # If set to `false`, no training is going to occur.
    #
    # If set to `nil`, when the Auditor submits a form with original or
    # sample values this option will be overridden to `true`
    #
    train:    nil
}

Instance Attribute Summary collapse

Class Method Summary collapse

Instance Method Summary collapse

Methods included from Output

#fancy_name, #print_bad, #print_debug, #print_error, #print_info, #print_line, #print_ok, #print_status, #print_verbose

Methods included from UI::Output

#debug?, #debug_off, #debug_on, #disable_only_positives, #error_logfile, #flush_buffer, #log_error, #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, #set_error_logfile, #uncap_buffer, #unmute, #verbose, #verbose?

Instance Attribute Details

#frameworkArachni::Framework (readonly)

This method is abstract.

REQUIRED

Returns:



178
179
180
# File 'lib/arachni/module/auditor.rb', line 178

def framework
  @framework
end

#pageArachni::Page (readonly)

This method is abstract.

REQUIRED

Returns:



169
170
171
# File 'lib/arachni/module/auditor.rb', line 169

def page
  @page
end

Class Method Details

.current_timeout_audit_operations_cntObject



77
78
79
# File 'lib/arachni/module/auditor.rb', line 77

def self.current_timeout_audit_operations_cnt
    Element::Capabilities::Auditable.current_timeout_audit_operations_cnt
end

.included(m) ⇒ Object



101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
# File 'lib/arachni/module/auditor.rb', line 101

def self.included( m )
    m.class_eval do
        def self.issue_counter
            @issue_counter ||= 0
        end

        def self.issue_counter=( int )
            @issue_counter = int
        end

        def increment_issue_counter
            self.class.issue_counter += 1
        end

        def issue_limit_reached?( count = max_issues )
            self.class.issue_limit_reached?( count )
        end

        def self.issue_limit_reached?( count = max_issues )
            issue_counter >= count if !count.nil?
        end

        def self.max_issues
            info[:max_issues]
        end
    end
end

.on_timing_attacks(&block) ⇒ Object



65
66
67
# File 'lib/arachni/module/auditor.rb', line 65

def self.on_timing_attacks( &block )
    Element::Capabilities::Auditable.on_timing_attacks( &block )
end

.resetObject



51
52
53
54
# File 'lib/arachni/module/auditor.rb', line 51

def self.reset
    audited.clear
    Element::Capabilities::Auditable::Timeout.reset
end

.running_timeout_attacks?Boolean

Returns:

  • (Boolean)


68
69
70
# File 'lib/arachni/module/auditor.rb', line 68

def self.running_timeout_attacks?
    Element::Capabilities::Auditable.running_timeout_attacks?
end

.timeout_audit_blocksObject



56
57
58
# File 'lib/arachni/module/auditor.rb', line 56

def self.timeout_audit_blocks
    Element::Capabilities::Auditable.timeout_audit_blocks
end

.timeout_audit_operations_cntObject



74
75
76
# File 'lib/arachni/module/auditor.rb', line 74

def self.timeout_audit_operations_cnt
    Element::Capabilities::Auditable.timeout_audit_operations_cnt
end

.timeout_audit_runObject



71
72
73
# File 'lib/arachni/module/auditor.rb', line 71

def self.timeout_audit_run
    Element::Capabilities::Auditable.timeout_audit_run
end

.timeout_candidatesObject



59
60
61
# File 'lib/arachni/module/auditor.rb', line 59

def self.timeout_candidates
    Element::Capabilities::Auditable.timeout_candidates
end

.timeout_loaded_modulesObject



62
63
64
# File 'lib/arachni/module/auditor.rb', line 62

def self.timeout_loaded_modules
    Element::Capabilities::Auditable.timeout_loaded_modules
end

Instance Method Details

#audit(payloads, opts = {}, &block) ⇒ Object

If a block has been provided it calls Element::Capabilities::Auditable#audit for every element, otherwise, it defaults to #audit_taint.

Uses #each_candidate_element to decide which elements to audit.



551
552
553
554
555
556
557
558
# File 'lib/arachni/module/auditor.rb', line 551

def audit( payloads, opts = {}, &block )
    opts = OPTIONS.merge( opts )
    if !block_given?
        audit_taint( payloads, opts )
    else
        each_candidate_element( opts ) { |e| e.audit( payloads, opts, &block ) }
    end
end

#audit_rdiff(opts = {}, &block) ⇒ Object

Audits elements using differential analysis and automatically logs results.

Uses #each_candidate_element to decide which elements to audit.



582
583
584
585
# File 'lib/arachni/module/auditor.rb', line 582

def audit_rdiff( opts = {}, &block )
    opts = OPTIONS.merge( opts )
    each_candidate_element( opts ) { |e| e.rdiff_analysis( opts, &block ) }
end

#audit_taint(payloads, opts = {}) ⇒ Object

Provides easy access to element auditing using simple taint analysis and automatically logs results.

Uses #each_candidate_element to decide which elements to audit.



569
570
571
572
# File 'lib/arachni/module/auditor.rb', line 569

def audit_taint( payloads, opts = {} )
    opts = OPTIONS.merge( opts )
    each_candidate_element( opts ) { |e| e.taint_analysis( payloads, opts ) }
end

#audit_timeout(payloads, opts = {}) ⇒ Object

Audits elements using timing attacks and automatically logs results.

Uses #each_candidate_element to decide which elements to audit.



595
596
597
598
# File 'lib/arachni/module/auditor.rb', line 595

def audit_timeout( payloads, opts = {} )
    opts = OPTIONS.merge( opts )
    each_candidate_element( opts ) { |e| e.timeout_analysis( payloads, opts ) }
end

#audited(id) ⇒ Object

Parameters:

  • id (#to_s)

    Identifier of the object to be marked as audited.

See Also:



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

def audited( id )
    Auditor.audited << "#{self.class}-#{id}"
end

#audited?(id) ⇒ Bool

Returns ‘true` if audited, `false` otherwise.

Parameters:

  • id (#to_s)

    Identifier of the object to be checked.

Returns:

  • (Bool)

    ‘true` if audited, `false` otherwise.

See Also:



97
98
99
# File 'lib/arachni/module/auditor.rb', line 97

def audited?( id )
    Auditor.audited.include?( "#{self.class}-#{id}" )
end

#each_candidate_element(opts = {}) {|element| ... } ⇒ Object

Passes each element prepared for audit to the block.

If no element types have been specified in ‘opts`, it will use the elements from the module’s Base.info hash.

If no elements have been specified in ‘opts` or Base.info, it will use the elements in OPTIONS.

Parameters:

  • opts (Hash) (defaults to: {})

Options Hash (opts):

  • :elements (Array)

    Element types to audit (see OPTIONS‘[:elements]`).

Yields:

  • (element)

    Each candidate element.

Yield Parameters:



501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
# File 'lib/arachni/module/auditor.rb', line 501

def each_candidate_element( opts = {} )
    if !opts.include?( :elements) || !opts[:elements] || opts[:elements].empty?
        opts[:elements] = self.class.info[:elements]
    end

    if !opts.include?( :elements) || !opts[:elements] || opts[:elements].empty?
        opts[:elements] = OPTIONS[:elements]
    end

    elements = []
    opts[:elements].each do |elem|
        next if !Options.audit?( elem )

        elements |= case elem
            when Element::LINK
                page.links

            when Element::FORM
                page.forms

            when Element::COOKIE
                page.cookies

            when Element::HEADER
                page.headers

            when Element::BODY
            else
                fail ArgumentError, "Unknown element: #{elem}"
        end
    end

    while (e = elements.pop)
        next if e.auditable.empty?
        d = e.dup
        d.auditor = self
        yield d
    end
end

#httpArachni::HTTP

Returns:



195
196
197
# File 'lib/arachni/module/auditor.rb', line 195

def http
    HTTP
end

#log(opts, res = nil) ⇒ Object

Populates and logs an Issue based on data from ‘opts` and `res`.

Parameters:

  • opts (Hash)

    As passed to blocks by audit methods.

  • res (Typhoeus::Response) (defaults to: nil)

    Optional HTTP response, defaults to page data.



386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
# File 'lib/arachni/module/auditor.rb', line 386

def log( opts, res = nil )
    response_headers = {}
    request_headers  = {}
    response = nil
    method   = nil

    if page
        request_headers  = page.request_headers
        response_headers = page.response_headers
        response         = page.body
        url              = page.url
        method           = page.method.to_s.upcase if page.method
    end

    if res
        request_headers  = res.request.headers
        response_headers = res.headers_hash
        response         = res.body
        url              = opts[:action] || res.effective_url
        method           = res.request.method.to_s.upcase
    end

    if !response_headers['content-type'].to_s.include?( 'text' )
        response = nil
    end

    var     = opts[:altered] || opts[:var]
    element = opts[:element] || opts[:elem]

    msg = "In #{element}"
    msg << " input '#{var}'" if var
    print_ok "#{msg} ( #{url} )"

    print_verbose( "Injected string:\t" + opts[:injected] ) if opts[:injected]
    print_verbose( "Verified string:\t" + opts[:match].to_s ) if opts[:match]
    print_verbose( 'Matched regular expression: ' + opts[:regexp].to_s ) if opts[:regexp]
    print_debug( 'Request ID: ' + res.request.id.to_s ) if res
    print_verbose( '---------' ) if only_positives?

    # Platform identification by vulnerability.
    platform_type = nil
    if (platform = opts[:platform])
        Platform::Manager[url] << platform if Options.fingerprint?
        platform_type = Platform::Manager[url].find_type( platform )
    end

    log_issue(
        var:           var,
        url:           url,
        platform:      platform,
        platform_type: platform_type,
        injected:      opts[:injected],
        id:            opts[:id],
        regexp:        opts[:regexp],
        regexp_match:  opts[:match],
        elem:          element,
        verification:  !!opts[:verification],
        remarks:       opts[:remarks],
        method:        method,
        response:      response,
        opts:          opts,
        headers:       {
            request:   request_headers,
            response:  response_headers,
        }
    )
end

#log_issue(opts) ⇒ Object

Helper method for issue logging.

Parameters:

See Also:

  • Base#register_results


318
319
320
321
# File 'lib/arachni/module/auditor.rb', line 318

def log_issue( opts )
    # register the issue
    register_results( [ Issue.new( opts.merge( self.class.info ) ) ] )
end

#log_remote_file(res, silent = false) ⇒ Object Also known as: log_remote_directory

Logs the existence of a remote file as an issue.

Parameters:

  • res (Typhoeus::Response)
  • silent (Bool) (defaults to: false)

    If ‘false`, a message will be printed to stdout containing the status of the operation.

See Also:



291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
# File 'lib/arachni/module/auditor.rb', line 291

def log_remote_file( res, silent = false )
    url = res.effective_url
    filename = File.basename( URI( res.effective_url ).path )

    log_issue(
        url:      url,
        injected: filename,
        id:       filename,
        elem:     Element::PATH,
        response: res.body,
        headers:  {
            request:  res.request.headers,
            response: res.headers_hash,
        }
    )

    print_ok( "Found #{filename} at #{url}" ) if !silent
end

#log_remote_file_if_exists(url, silent = false, &block) ⇒ Object Also known as: log_remote_directory_if_exists

Note:

Ignores custom 404 responses.

Logs a remote file or directory if it exists.

Parameters:

  • url (String)

    Resource to check.

  • silent (Bool) (defaults to: false)

    If ‘false`, a message will be printed to stdout containing the status of the operation.

  • block (Proc)

    Called if the file exists, just before logging the issue, and is passed the HTTP response.

Returns:

  • (Object)
    • ‘nil` if no URL was provided.

    • ‘false` if the request couldn’t be fired.

    • ‘true` if everything went fine.

See Also:



233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
# File 'lib/arachni/module/auditor.rb', line 233

def log_remote_file_if_exists( url, silent = false, &block )
    return nil if !url

    print_status( "Checking for #{url}" ) if !silent
    remote_file_exist?( url ) do |bool, res|
        print_status( 'Analyzing response for: ' + url ) if !silent
        next if !bool

        block.call( res ) if block_given?
        log_remote_file( res )

        # If the file exists let the trainer parse it since it may contain
        # brand new data to audit.
        framework.trainer.push( res )
    end
     true
end

#match_and_log(regexps, string = page.body, &block) ⇒ Object

Matches an array of regular expressions against a string and logs the result as an issue.

Parameters:

  • regexps (Array<Regexp>)

    Array of regular expressions to be tested.

  • string (String) (defaults to: page.body)

    String against which the ‘regexps` will be matched. (If no string has been provided the #page body will be used and, for good measure, `regexps` will also be matched against Page#response_headers as well.)

  • block (Block)

    Block to verify matches before logging, must return ‘true`/`false`.



337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
# File 'lib/arachni/module/auditor.rb', line 337

def match_and_log( regexps, string = page.body, &block )
    # make sure that we're working with an array
    regexps = [regexps].flatten

    elems = self.class.info[:elements]
    elems = OPTIONS[:elements] if !elems || elems.empty?

    regexps.each do |regexp|
        string.scan( regexp ).flatten.uniq.each do |match|

            next if !match
            next if block && !block.call( match )

            log(
                regexp:  regexp,
                match:   match,
                element: Element::BODY
            )
        end if elems.include? Element::BODY

        next if string != page.body

        page.response_headers.each do |k,v|
            next if !v

            v.to_s.scan( regexp ).flatten.uniq.each do |match|
                next if !match
                next if block && !block.call( match )

                log(
                    var:     k,
                    regexp:  regexp,
                    match:   match,
                    element: Element::HEADER
                )
            end
        end if elems.include? Element::HEADER

    end
end

#max_issuesObject



129
130
131
# File 'lib/arachni/module/auditor.rb', line 129

def max_issues
    self.class.max_issues
end

#override_instance_scope?Bool

This method is abstract.

OPTIONAL

Allows modules to ignore multi-Instance scope restrictions in order to audit elements that are not on the sanctioned whitelist.

Returns:

  • (Bool)


190
191
192
# File 'lib/arachni/module/auditor.rb', line 190

def override_instance_scope?
    false
end

#preferredObject

This method is abstract.


457
458
459
# File 'lib/arachni/module/auditor.rb', line 457

def preferred
    []
end

#register_results(issues) ⇒ Object

Just a delegator, logs an array of issues.

Parameters:

See Also:



206
207
208
209
210
211
# File 'lib/arachni/module/auditor.rb', line 206

def register_results( issues )
    return if issue_limit_reached?
    self.class.issue_counter += issues.size

    framework.modules.register_results( issues )
end

#remote_file_exist?(url, &block) ⇒ Object Also known as: remote_file_exists?

Note:

Ignores custom 404 responses.

Checks whether or not a remote resource exists.

Parameters:

  • url (String)

    Resource to check.

  • block (Block)

    Block to be passed ‘true` if the resource exists, `false` otherwise.

Returns:

  • (Object)
    • ‘nil` if no URL was provided.

    • ‘false` if the request couldn’t be fired.

    • ‘true` if everything went fine.



266
267
268
269
270
271
272
273
274
275
276
277
278
# File 'lib/arachni/module/auditor.rb', line 266

def remote_file_exist?( url, &block )
    req  = http.get( url )
    return false if !req

    req.on_complete do |res|
        if res.code != 200
            block.call( false, res )
        else
            http.custom_404?( res ) { |bool| block.call( !bool, res ) }
        end
    end
    true
end

#skip?(elem) ⇒ Boolean

This is called right before an Element is audited and is used to determine whether to skip it or not.

Running modules can override this as they wish but at their own peril.

Parameters:

Returns:

  • (Boolean)

    ‘true` if the element should be skipped, `false` otherwise.



472
473
474
475
476
477
478
479
480
481
482
483
484
485
# File 'lib/arachni/module/auditor.rb', line 472

def skip?( elem )
    # Find out our own shortname.
    @modname ||= framework.modules.map { |k, v| k if v == self.class }.compact.first

    # Don't audit elements which have been already logged as vulnerable
    # either by us or preferred modules.
    (preferred | [@modname]).each do |mod|
        next if !framework.modules.include?( mod )
        issue_id = elem.provisioned_issue_id( framework.modules[mod].info[:name] )
        return true if framework.modules.issue_set.include?( issue_id )
    end

    false
end