Module: Arachni::Module::Auditor

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

Overview

Auditor module

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.

The module also provides:

  • discovery helpers for checking and logging the existence of remote files

  • pattern matching helpers for checking and logging the existence of strings in responses or in the body of the page that’s being audited

  • general Issue logging helpers

Constant Summary collapse

Format =

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

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

Holds constants that describe Issue severities.

Severity = Issue::Severity

{
    #
    # Elements to audit.
    #
    # If no elements have been passed to audit candidates will be
    # determined by {#candidate_elements}.
    #
    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, #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?

Instance Attribute Details

#frameworkArachni::Framework (readonly)

This method is abstract.

REQUIRED

Must return the Framework



151
152
153
# File 'lib/arachni/module/auditor.rb', line 151

def framework
  @framework
end

#pageArachni::Page (readonly)

This method is abstract.

REQUIRED

Must return the Page object you wish to be audited.



140
141
142
# File 'lib/arachni/module/auditor.rb', line 140

def page
  @page
end

Class Method Details

.current_timeout_audit_operations_cntObject



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

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

.on_timing_attacks(&block) ⇒ Object



54
55
56
# File 'lib/arachni/module/auditor.rb', line 54

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

.resetObject



44
45
46
# File 'lib/arachni/module/auditor.rb', line 44

def self.reset
    audited.clear
end

.running_timeout_attacks?Boolean



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

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

.timeout_audit_blocksObject



48
49
50
# File 'lib/arachni/module/auditor.rb', line 48

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

.timeout_audit_operations_cntObject



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

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

.timeout_audit_runObject



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

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

.timeout_loaded_modulesObject



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

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

Instance Method Details

#audit(injection_str, 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 #candidate_elements to decide which elements to audit.



477
478
479
480
481
482
483
484
# File 'lib/arachni/module/auditor.rb', line 477

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

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

Audits elements using differential analysis attacks.

Uses #candidate_elements to decide which elements to audit.

See Also:



507
508
509
510
# File 'lib/arachni/module/auditor.rb', line 507

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

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

Provides easy access to element auditing using simple taint analysis.

Uses #candidate_elements to decide which elements to audit.

See Also:



494
495
496
497
# File 'lib/arachni/module/auditor.rb', line 494

def audit_taint( taint, opts = {} )
    opts = OPTIONS.merge( opts )
    candidate_elements( opts ).each { |e| e.taint_analysis( taint, opts ) }
end

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

Audits elements using timing attacks and automatically logs results.

Uses #candidate_elements to decide which elements to audit.

See Also:

  • OPTIONS
  • Element::Analysis::Timeout


520
521
522
523
# File 'lib/arachni/module/auditor.rb', line 520

def audit_timeout( strings, opts = {} )
    opts = OPTIONS.merge( opts )
    candidate_elements( opts ).each { |e| e.timeout_analysis( strings, opts ) }
end

#audited(id) ⇒ Object

See Also:



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

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

#audited?(id) ⇒ Bool

Returns true if audited, false otherwise.

See Also:



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

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

#candidate_elements(opts = {}) ⇒ Array<Arachni::Element::Capabilities::Auditable] array of auditable elements

Returns a list of prepared elements to be audited.

If no element types have been specified in ‘opts’ it will use the elements from the module’s “self.info()” hash.

If no elements have been specified in ‘opts’ or “self.info()” it will use the elements in OPTIONS.



432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
# File 'lib/arachni/module/auditor.rb', line 432

def candidate_elements( 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
                failt "Unknown element to audit: #{elem}"
        end
    end

    elements.map { |e| d = e.dup; d.auditor = self; d }
end

#httpArachni::HTTP



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

def http
    HTTP
end

#log(opts, res = nil) ⇒ Object

Populates and logs an Issue based on data from “opts” and “res”.



335
336
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
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
# File 'lib/arachni/module/auditor.rb', line 335

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 << " var '#{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?

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

#log_issue(opts) ⇒ Object

Helper method for issue logging.

See Also:

  • Base#register_results


272
273
274
275
# File 'lib/arachni/module/auditor.rb', line 272

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.

See Also:



245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
# File 'lib/arachni/module/auditor.rb', line 245

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

Logs a remote file or directory if it exists.



198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
# File 'lib/arachni/module/auditor.rb', line 198

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

        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
            http.trainer.push( res )
        end
    end
     true
end

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

Matches the “string” (default string is the HTML code in page.body) to an array of regular expressions and logs the results.

For good measure, regexps will also be run against the page headers (page.response_headers).



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
# File 'lib/arachni/module/auditor.rb', line 288

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

#override_instance_scope?Bool

This method is abstract.

OPTIONAL

Allows modules to ignore HPG scope restrictions

This way they can audit elements that are not on the Grid sanctioned whitelist.



164
165
166
# File 'lib/arachni/module/auditor.rb', line 164

def override_instance_scope?
    false
end

#preferredObject

See Also:



394
395
396
# File 'lib/arachni/module/auditor.rb', line 394

def preferred
    []
end

#register_results(issues) ⇒ Object

Just a delegator logs an array of issues.



180
181
182
# File 'lib/arachni/module/auditor.rb', line 180

def register_results( issues )
    Module::Manager.register_results( issues )
end

#remote_file_exist?(url, &block) ⇒ Boolean

Checks that the response points to an existing file/page and not an error or custom 404 response.



224
225
226
227
228
229
230
231
232
233
234
235
236
# File 'lib/arachni/module/auditor.rb', line 224

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 [Arachni::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.



406
407
408
409
410
411
412
413
414
415
416
417
# File 'lib/arachni/module/auditor.rb', line 406

def skip?( elem )
    if framework
        @modname ||= framework.modules.map { |k, v| k if v == self.class }.compact.first
        (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
    end

    false
end