Module: Arachni::Element::Capabilities::Auditable

Includes:
RDiff, Taint, Timeout, Mutable, Utilities
Included in:
Base
Defined in:
lib/arachni/element/capabilities/auditable.rb

Overview

Provides audit functionality to Mutable elements.

Author:

Defined Under Namespace

Modules: RDiff, Taint, Timeout

Constant Summary collapse

OPTIONS =

Default audit options.

{
    #
    # Enable skipping of already audited inputs
    #
    redundant: false,

    #
    # Make requests asynchronously
    #
    async:     true,

    #
    # Block to be passed each mutation right before being submitted.
    #
    # Allows for last minute changes.
    #
    each_mutation:  nil
}

Constants included from RDiff

RDiff::RDIFF_OPTIONS

Constants included from Taint

Taint::TAINT_OPTIONS

Constants included from Mutable

Mutable::MUTATION_OPTIONS

Instance Attribute Summary collapse

Attributes included from Mutable

#altered

Class Method Summary collapse

Instance Method Summary collapse

Methods included from RDiff

included, #rdiff_analysis

Methods included from Timeout

add_timeout_audit_block, add_timeout_candidate, #call_on_timing_blocks, call_on_timing_blocks, current_timeout_audit_operations_cnt, included, on_timing_attacks, #responsive?, running_timeout_attacks?, #timeout_analysis, timeout_analysis_phase_2, timeout_audit_blocks, timeout_audit_operations_cnt, timeout_audit_run, timeout_loaded_modules

Methods included from Taint

#taint_analysis

Methods included from Mutable

#altered_value, #altered_value=, #immutables, #mutated?, #mutations, #mutations_for, #original?, #permutations, #permutations_for

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, #to_absolute, #uri_decode, #uri_encode, #uri_parse, #uri_parser, #url_sanitize

Instance Attribute Details

#auditorArachni::Module::Auditor

Sets the auditor for this element.

The auditor provides its output, HTTP and issue logging interfaces.



62
63
64
# File 'lib/arachni/element/capabilities/auditable.rb', line 62

def auditor
  @auditor
end

#optsHash (readonly)

Returns audit and general options for convenience’s sake.

Returns:

  • (Hash)

    audit and general options for convenience’s sake



75
76
77
# File 'lib/arachni/element/capabilities/auditable.rb', line 75

def opts
  @opts
end

#origHash (readonly) Also known as: original

Frozen version of #auditable, has all the original name/values

Returns:

  • (Hash)


69
70
71
# File 'lib/arachni/element/capabilities/auditable.rb', line 69

def orig
  @orig
end

Class Method Details

.resetObject

Empties the de-duplication/uniqueness look-up table.

Unless you’re sure you need this, set the :redundant flag to true when calling audit methods to bypass it.



50
51
52
# File 'lib/arachni/element/capabilities/auditable.rb', line 50

def self.reset
    @@audited = BloomFilter.new
end

.reset_instance_scopeObject



234
235
236
# File 'lib/arachni/element/capabilities/auditable.rb', line 234

def self.reset_instance_scope
    @@restrict_to_elements = BloomFilter.new
end

.restrict_to_elements(elements) ⇒ Object

Restricts the audit to a specific set of elements.

Caution: Each call overwrites the last.

Parameters:

See Also:



265
266
267
268
# File 'lib/arachni/element/capabilities/auditable.rb', line 265

def self.restrict_to_elements( elements )
    self.reset_instance_scope
    elements.each { |elem| @@restrict_to_elements << elem }
end

Instance Method Details

#==(e) ⇒ Object Also known as: eql?



198
199
200
# File 'lib/arachni/element/capabilities/auditable.rb', line 198

def ==( e )
    hash == e.hash
end

#[](k) ⇒ String

Shorthand #auditable reader

Parameters:

  • k (#to_s)

    key

Returns:



181
182
183
# File 'lib/arachni/element/capabilities/auditable.rb', line 181

def []( k )
    self.auditable[k.to_s]
end

#[]=(k, v) ⇒ Object

Shorthand #auditable writer

Parameters:

  • k (#to_s)

    key

  • v (#to_s)

    value

See Also:



193
194
195
196
# File 'lib/arachni/element/capabilities/auditable.rb', line 193

def []=( k, v )
    update( { k => v } )
    [k]
end

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

Submits mutations of self and calls the block to handle the responses.

Requires an #auditor.

Parameters:

  • injection_str (String)

    the string to be injected

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

    options as described in OPTIONS

  • block (Block)

    block to be used for analysis of responses; will be passed the following:

    • HTTP response

    • options

    • element

    The block will be called as soon as the HTTP response is received.

See Also:



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
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
# File 'lib/arachni/element/capabilities/auditable.rb', line 359

def audit( injection_str, opts = { }, &block )
    fail 'Block required.' if !block_given?

    if skip_path?( self.action )
        print_debug "Element's action matches skip rule, bailing out (#{self.action})."
        return false
    end

    opts[:injected_orig] = injection_str

    # if we don't have any auditable elements just return
    if auditable.empty?
        print_debug "The element has no auditable inputs, returning."
        return false
    end

    @auditor ||= opts[:auditor]
    opts[:auditor] ||= @auditor

    audit_id = audit_id( injection_str, opts )
    return false if !opts[:redundant] && audited?( audit_id )

    # iterate through all variation and audit each one
    mutations( injection_str, opts ).each do |elem|

        if Options.exclude_vectors.include?( elem.altered )
            print_info "Skipping audit of '#{elem.altered}' #{type} vector."
            next
        end

        if !orphan? && @auditor.skip?( elem )
            mid = elem.audit_id( injection_str, opts )
            print_debug "Auditor's #skip? method returned true for mutation, skipping: #{mid}"
            next
        end
        if skip?( elem )
            mid = elem.audit_id( injection_str, opts )
            print_debug "Self's #skip? method returned true for mutation, skipping: #{mid}"
            next
        end

        opts[:altered] = elem.altered.dup
        opts[:element] = type

        # inform the user about what we're auditing
        print_status( elem.status_string ) if !opts[:silent]

        if opts[:each_mutation]
            if elements = opts[:each_mutation].call( elem )
                [elements].flatten.compact.each do |e|
                    on_complete( e.submit( opts ), e, &block ) if e.is_a?( self.class )
                end
            end
        end

        # submit the element with the injection values
        on_complete( elem.submit( opts ), elem, &block )
    end

    audited( audit_id )
    true
end

#audit_id(injection_str = '', opts = {}) ⇒ String

Returns an audit ID string used to avoid redundant audits or identify the element.

Parameters:

  • injection_str (String) (defaults to: '')
  • opts (Hash) (defaults to: {})

Returns:



446
447
448
449
450
451
452
453
454
455
456
457
# File 'lib/arachni/element/capabilities/auditable.rb', line 446

def audit_id( injection_str = '', opts = {} )
    vars = auditable.keys.sort.to_s

    str = ''
    str << "#{@auditor.fancy_name}:" if !opts[:no_auditor] && !orphan?

    str << "#{@action}:#{type}:#{vars}"
    str << "=#{injection_str}" if !opts[:no_injection_str]
    str << ":timeout=#{opts[:timeout]}" if !opts[:no_timeout]

    str
end

#auditableHash

Frozen Key=>value pairs of inputs.

If you want to change it you’ll either have to use #update or the #auditable= attr_writer and pass a new hash – the new hash will also be frozen.

Returns:

  • (Hash)


114
115
116
# File 'lib/arachni/element/capabilities/auditable.rb', line 114

def auditable
    @auditable.freeze
end

#auditable=(hash) ⇒ Object

Parameters:

  • hash (Hash)

    key=>value pair of inputs/params. Will convert keys and values to string.

See Also:



124
125
126
127
128
# File 'lib/arachni/element/capabilities/auditable.rb', line 124

def auditable=( hash )
    @auditable = (hash || {}).inject({}) { |h, (k, v)| h[k.to_s] = v.to_s.freeze; h}
    rehash
    self.auditable
end

#changesObject

Returns changes make to the auditable’s inputs.

Parameters:

  • hash (Hash)

    key=>value pair of updated inputs/params



165
166
167
168
169
170
171
172
# File 'lib/arachni/element/capabilities/auditable.rb', line 165

def changes
    (self.orig.keys | self.auditable.keys).inject( {} ) do |h, k|
        if self.orig[k] != self.auditable[k]
            h[k] = self.auditable[k]
        end
        h
    end
end

#debug?Boolean

Delegate output related methods to the auditor

Returns:

  • (Boolean)


481
482
483
# File 'lib/arachni/element/capabilities/auditable.rb', line 481

def debug?
    @auditor.debug? rescue false
end

#has_inputs?(*args) ⇒ Bool

Checks whether or not the given inputs match the auditable ones.

Parameters:

  • args (Hash, Array, String, Symbol)

    names to check (also accepts var-args)

Returns:

  • (Bool)


137
138
139
140
141
142
143
144
# File 'lib/arachni/element/capabilities/auditable.rb', line 137

def has_inputs?( *args )
    if (h = args.first).is_a?( Hash )
        h.each { |k, v| return false if self[k] != v }
    else
        keys = args.flatten.compact.map { |a| [a].map( &:to_s ) }.flatten
        (self.auditable.keys & keys).size == keys.size
    end
end

#hashObject



203
204
205
# File 'lib/arachni/element/capabilities/auditable.rb', line 203

def hash
    @hash ||= rehash
end

#httpArachni::HTTP

Returns the #auditor‘s HTTP interface or reverts to Arachni::HTTP.instance

Returns:



292
293
294
# File 'lib/arachni/element/capabilities/auditable.rb', line 292

def http
    HTTP
end

#http_request(opts, &block) ⇒ Typhoeus::Request

This method is abstract.

Must be implemented by the including class and perform the appropriate HTTP request (get/post/whatever) for the current element.

Invoked by #submit to submit the object.

Parameters:

  • opts (Hash)
  • block (Block)

    callback to be passed the HTTP response

Returns:

See Also:



284
285
# File 'lib/arachni/element/capabilities/auditable.rb', line 284

def http_request( opts, &block )
end

#infoObject

impersonate the auditor to the output methods



473
474
475
# File 'lib/arachni/element/capabilities/auditable.rb', line 473

def info
    !orphan? ? @auditor.class.info : { name: '' }
end

#orphan?Bool

Returns true if it has no auditor.

Returns:

  • (Bool)

    true if it has no auditor



299
300
301
# File 'lib/arachni/element/capabilities/auditable.rb', line 299

def orphan?
    !@auditor
end

#override_instance_scopeObject

When working in High Performance Grid mode the instances have a very specific list of elements which they are allowed to audit.

Elements which do not fit the scope are ignored.

When called, the element will override the scope and be audited no-matter what.

This is mainly used on elements discovered during audit-time by the trainer.



217
218
219
# File 'lib/arachni/element/capabilities/auditable.rb', line 217

def override_instance_scope
    @override_instance_scope = true
end

#override_instance_scope?Boolean

Does this element override the instance scope?

Returns:

  • (Boolean)

See Also:



230
231
232
# File 'lib/arachni/element/capabilities/auditable.rb', line 230

def override_instance_scope?
    @override_instance_scope ||= false
end


505
506
507
# File 'lib/arachni/element/capabilities/auditable.rb', line 505

def print_bad( str = '' )
    @auditor.print_bad( str ) if !orphan?
end


509
510
511
# File 'lib/arachni/element/capabilities/auditable.rb', line 509

def print_debug( str = '' )
    @auditor.print_debug( str ) if !orphan?
end


513
514
515
# File 'lib/arachni/element/capabilities/auditable.rb', line 513

def print_debug_backtrace( str = '' )
    @auditor.print_debug_backtrace( str ) if !orphan?
end


485
486
487
# File 'lib/arachni/element/capabilities/auditable.rb', line 485

def print_error( str = '' )
    @auditor.print_error( str ) if !orphan?
end


517
518
519
# File 'lib/arachni/element/capabilities/auditable.rb', line 517

def print_error_backtrace( str = '' )
    @auditor.print_error_backtrace( str ) if !orphan?
end


493
494
495
# File 'lib/arachni/element/capabilities/auditable.rb', line 493

def print_info( str = '' )
    @auditor.print_info( str ) if !orphan?
end


497
498
499
# File 'lib/arachni/element/capabilities/auditable.rb', line 497

def print_line( str = '' )
    @auditor.print_line( str ) if !orphan?
end


501
502
503
# File 'lib/arachni/element/capabilities/auditable.rb', line 501

def print_ok( str = '' )
    @auditor.print_ok( str ) if !orphan?
end


489
490
491
# File 'lib/arachni/element/capabilities/auditable.rb', line 489

def print_status( str = '' )
    @auditor.print_status( str ) if !orphan?
end

#provisioned_issue_id(auditor_fanxy_name = @auditor.fancy_name) ⇒ String

Predicts what the Issue#unique_id of an issue would look like, should self be vulnerable.

Mainly used by Module::Auditor#skip? to prevent redundant audits for elements/issues which have already been logged as vulnerable.

Returns:



468
469
470
# File 'lib/arachni/element/capabilities/auditable.rb', line 468

def provisioned_issue_id( auditor_fanxy_name = @auditor.fancy_name )
    "#{auditor_fanxy_name}::#{type}::#{altered}::#{self.action.split( '?' ).first}"
end

#remove_auditorObject



310
311
312
# File 'lib/arachni/element/capabilities/auditable.rb', line 310

def remove_auditor
    @auditor = nil
end

#resetObject

Resets the auditable inputs to their original format/values.



306
307
308
# File 'lib/arachni/element/capabilities/auditable.rb', line 306

def reset
    self.auditable = @orig.dup
end

#reset_scope_overrideObject



221
222
223
# File 'lib/arachni/element/capabilities/auditable.rb', line 221

def reset_scope_override
    @override_instance_scope = false
end

#scope_audit_id(opts = {}) ⇒ Object

Provides a more generalized audit ID which does not contain the auditor’s name, timeout value of injection string.

Right now only used when in HPG mode to generate a white-list of element IDs that are allowed to be audited.

Parameters:

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

    #audit opts



247
248
249
250
251
252
253
254
# File 'lib/arachni/element/capabilities/auditable.rb', line 247

def scope_audit_id( opts = {} )
    opts = {} if !opts
    audit_id( nil, opts.merge(
        no_auditor:       true,
        no_timeout:       true,
        no_injection_str: true
    ))
end

#skip?(elem) ⇒ Boolean

Returns:

  • (Boolean)


422
423
424
# File 'lib/arachni/element/capabilities/auditable.rb', line 422

def skip?( elem )
    false
end

#skip_path?(url) ⇒ Boolean

Returns:

  • (Boolean)


339
340
341
# File 'lib/arachni/element/capabilities/auditable.rb', line 339

def skip_path?( url )
    super || Options.redundant?( url )
end

#status_stringString

Returns a status string explaining what’s being audited.

The string contains the name of the input that is being audited, the url and the type of the input (form, link, cookie…).

Returns:



434
435
436
# File 'lib/arachni/element/capabilities/auditable.rb', line 434

def status_string
    "Auditing #{self.type} variable '#{self.altered}' with action '#{self.action}'."
end

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

Submits self using #http_request.

Parameters:

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

    callback to be passed the HTTP response

See Also:



322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
# File 'lib/arachni/element/capabilities/auditable.rb', line 322

def submit( opts = {}, &block )
    opts = OPTIONS.merge( opts )
    opts[:params]  = @auditable.dup
    opts[:follow_location] = true if !opts.include?( :follow_location )

    @opts ||= {}

    opts = @opts.merge( opts )
    @opts = opts

    @auditor ||= opts[:auditor] if opts[:auditor]

    opts.delete( :auditor )

    http_request( opts, &block )
end

#update(hash) ⇒ Auditable

Returns self.

Parameters:

  • hash (Hash)

    key=>value pair of inputs/params with which to update the #auditable inputs

Returns:

See Also:



155
156
157
158
# File 'lib/arachni/element/capabilities/auditable.rb', line 155

def update( hash )
    self.auditable = self.auditable.merge( hash )
    self
end