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

Includes:
RDiff, Taint, Timeout, Mutable, Utilities
Included in:
Base
Defined in:
lib/arachni/element/capabilities/auditable.rb,
lib/arachni/element/capabilities/auditable/rdiff.rb,
lib/arachni/element/capabilities/auditable/timeout.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,

    # Block to be passed each mutation to determine if it should be skipped.
    skip_like: nil
}

Constants included from RDiff

RDiff::RDIFF_OPTIONS

Constants included from Taint

Taint::REMARK, 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

#rdiff_analysis

Methods included from Timeout

add_timeout_candidate, add_timeout_phase3_candidate, call_on_timing_blocks, #call_on_timing_blocks, current_timeout_audit_operations_cnt, deduplicate?, #deduplicate?, disable_deduplication, #disable_deduplication, #enable_deduplication, enable_deduplication, included, on_timing_attacks, #responsive?, running_timeout_attacks?, #timeout_analysis, timeout_analysis_phase_2, timeout_analysis_phase_3, timeout_audit_operations_cnt, timeout_audit_run, timeout_candidates, timeout_loaded_modules

Methods included from Taint

#taint_analysis

Methods included from Mutable

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

Methods included from Utilities

#available_port, #cookie_encode, #cookies_from_document, #cookies_from_file, #cookies_from_response, #exception_jail, #exclude_path?, #extract_domain, #follow_protocol?, #form_decode, #form_encode, #form_parse_request_body, #forms_from_document, #forms_from_response, #generate_token, #get_path, #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?, #port_available?, #rand_port, #redundant_path?, #remove_constants, #seed, #skip_page?, #skip_resource?, #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.



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

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.



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

def opts
  @opts
end

#origHash (readonly) Also known as: original

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

Returns:



56
57
58
# File 'lib/arachni/element/capabilities/auditable.rb', line 56

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.



89
90
91
92
93
94
# File 'lib/arachni/element/capabilities/auditable.rb', line 89

def self.reset
    @@audited          = Support::LookUp::HashSet.new
    @@skip_like_blocks = []

    Timeout.reset
end

.reset_instance_scopeObject

Removes workload restrictions and allows all elements to be audited.



98
99
100
# File 'lib/arachni/element/capabilities/auditable.rb', line 98

def self.reset_instance_scope
    @@restrict_to_elements = Support::LookUp::HashSet.new( hasher: :to_i )
end

.restrict_to_elements(elements) ⇒ Object

Restricts the audit to a specific set of elements.

Caution: Each call overwrites the last.

Parameters:

See Also:



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

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

.skip_like(&block) ⇒ Auditable

Returns ‘self`.

Parameters:

  • block (Block)

    Block to decide whether an element should be skipped or not.

Returns:



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

def self.skip_like( &block )
    fail 'Missing block.' if !block_given?
    skip_like_blocks << block
    self
end

Instance Method Details

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



263
264
265
# File 'lib/arachni/element/capabilities/auditable.rb', line 263

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

#[](k) ⇒ String

Shorthand #auditable reader.

Parameters:

  • k (#to_s)

    key

Returns:



246
247
248
# File 'lib/arachni/element/capabilities/auditable.rb', line 246

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:



258
259
260
261
# File 'lib/arachni/element/capabilities/auditable.rb', line 258

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

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

Note:

Requires an #auditor, if none has been provided it will fallback to an anonymous one.

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

Parameters:

  • payloads (String, Array<String>, Hash{Symbol => <String, Array<String>>})

    Payloads to inject, if given:

    • String – Will inject the single payload.

    • Array – Will iterate over all payloads and inject them.

    • Hash – Expects Platform (as ‘Symbol`s ) for keys and Array of

      `payloads` for values. The applicable `payloads` will be
      {Platform#pick picked} from the hash based on
      {Element::Base#platforms applicable platforms} for the
      {Base#action resource} to be audited.
      
  • 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.

    • Audit options, as a hash.

    • Vulnerable element mutation.

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

Returns:

  • (Boolean, nil)
    • ‘true` when the audit was successful.

    • ‘false` when:

      * There are no {#auditable} inputs.
      * The {Element::Base#action} matches a {#skip_path? skip} rule.
      * The element has already been audited and the `:redundant` option
         is `false` -- the default.
      * The element matches a {.skip_like} block.
      
    • ‘nil` when:

      * An empty array/hash of `payloads` was given.
      * There are no `payloads` applicable to the element's platforms.
      

Raises:

  • ArgumentError On missing ‘block` or unsupported `payloads` type.

See Also:



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
453
454
455
456
457
458
459
460
461
# File 'lib/arachni/element/capabilities/auditable.rb', line 427

def audit( payloads, opts = { }, &block )
    fail ArgumentError, 'Missing block.' if !block_given?

    return false if self.auditable.empty?

    case payloads
        when String
            audit_single( payloads, opts, &block )
        when Array
            return if payloads.empty?

            payloads.each do |payload|
                audit_single( payload, opts, &block )
            end
        when Hash
            platform_payloads = platforms.any? ?
                platforms.pick( payloads ) : payloads

            return if platform_payloads.empty?

            payload_platforms = Set.new( payloads.keys )
            platform_payloads.each do |platform, payloads_for_platform|
                audit( [payloads_for_platform].flatten.compact,
                       opts.merge(
                           platform: platform,
                           payload_platforms: payload_platforms
                       ),
                       &block )
            end
        else
            raise ArgumentError,
                  "Unsupported payload type '#{payloads.class}'. " <<
                      'Expected one of: String, Array, Hash'
    end
end

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

Note:

Mostly used to keep track of what audits have been perform in order to prevent redundancies.

Returns an audit ID string used to identify the audit of ‘self` by its #auditor.

Parameters:

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

Returns:



496
497
498
499
500
501
502
503
504
505
506
507
# File 'lib/arachni/element/capabilities/auditable.rb', line 496

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 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:



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

def auditable
    @auditable.freeze
end

#auditable=(hash) ⇒ Object

Note:

Will convert keys and values to strings.

Parameters:

  • hash (Hash)

    Inputs/params.

See Also:



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

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

#changesHash

Returns changes make to the #auditable‘s inputs.

Returns:



230
231
232
233
234
235
236
237
# File 'lib/arachni/element/capabilities/auditable.rb', line 230

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)


532
533
534
# File 'lib/arachni/element/capabilities/auditable.rb', line 532

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 of inputs to check (also accepts var-args).

Returns:

  • (Bool)


206
207
208
209
210
211
212
213
# File 'lib/arachni/element/capabilities/auditable.rb', line 206

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



268
269
270
# File 'lib/arachni/element/capabilities/auditable.rb', line 268

def hash
    @hash ||= rehash
end

#httpArachni::HTTP

Returns:



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

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:



334
335
# File 'lib/arachni/element/capabilities/auditable.rb', line 334

def http_request( opts, &block )
end

#matches_skip_like_blocks?Boolean

Returns ‘true` if the element matches one or more skip_like_blocks, `false` otherwise.

Returns:

  • (Boolean)

    ‘true` if the element matches one or more skip_like_blocks, `false` otherwise.

See Also:

  • skip_like_blocks


524
525
526
# File 'lib/arachni/element/capabilities/auditable.rb', line 524

def matches_skip_like_blocks?
    Arachni::Element::Capabilities::Auditable.matches_skip_like_blocks?( self )
end

#orphan?Bool

Returns ‘true` if it has no auditor, `false` otherwise.

Returns:

  • (Bool)

    ‘true` if it has no auditor, `false` otherwise.



343
344
345
# File 'lib/arachni/element/capabilities/auditable.rb', line 343

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.



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

def override_instance_scope
    @override_instance_scope = true
end

#override_instance_scope?Boolean

Does this element override the instance scope?

Returns:

  • (Boolean)

See Also:



296
297
298
# File 'lib/arachni/element/capabilities/auditable.rb', line 296

def override_instance_scope?
    @override_instance_scope ||= false
end


556
557
558
# File 'lib/arachni/element/capabilities/auditable.rb', line 556

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


560
561
562
# File 'lib/arachni/element/capabilities/auditable.rb', line 560

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


564
565
566
# File 'lib/arachni/element/capabilities/auditable.rb', line 564

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


536
537
538
# File 'lib/arachni/element/capabilities/auditable.rb', line 536

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


568
569
570
# File 'lib/arachni/element/capabilities/auditable.rb', line 568

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


544
545
546
# File 'lib/arachni/element/capabilities/auditable.rb', line 544

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


548
549
550
# File 'lib/arachni/element/capabilities/auditable.rb', line 548

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


552
553
554
# File 'lib/arachni/element/capabilities/auditable.rb', line 552

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


540
541
542
# File 'lib/arachni/element/capabilities/auditable.rb', line 540

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

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

Note:

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

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

Returns:

  • (String)

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



515
516
517
# File 'lib/arachni/element/capabilities/auditable.rb', line 515

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

#remove_auditorObject

Removes the #auditor from this element.



353
354
355
# File 'lib/arachni/element/capabilities/auditable.rb', line 353

def remove_auditor
    @auditor = nil
end

#resetObject

Resets the auditable inputs to their original format/values.



348
349
350
# File 'lib/arachni/element/capabilities/auditable.rb', line 348

def reset
    self.auditable = @orig.dup
end

#reset_scope_overrideObject



287
288
289
# File 'lib/arachni/element/capabilities/auditable.rb', line 287

def reset_scope_override
    @override_instance_scope = false
end

#scope_audit_id(opts = {}) ⇒ Integer

Provides a more generalized audit ID which does not take into account the auditor’s name nor timeout value of injection string.

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

Parameters:

Returns:

  • (Integer)

    Hash ID.



311
312
313
314
315
316
317
318
# File 'lib/arachni/element/capabilities/auditable.rb', line 311

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

#skip?(elem) ⇒ Boolean

This method is abstract.
Note:

To be overridden by auditable element implementations for more fine-grained audit control.

Returns ‘true` if `self` should be audited, `false` otherwise.

Returns:

  • (Boolean)

    ‘true` if `self` should be audited, `false` otherwise.



470
471
472
# File 'lib/arachni/element/capabilities/auditable.rb', line 470

def skip?( elem )
    false
end

#status_stringString

Returns 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:

  • (String)

    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…).



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

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:



365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
# File 'lib/arachni/element/capabilities/auditable.rb', line 365

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]
    use_anonymous_auditor if !@auditor

    opts.delete( :auditor )

    http_request( opts, &block )
end

#update(hash) ⇒ Auditable

Returns self.

Parameters:

Returns:

See Also:



224
225
226
227
# File 'lib/arachni/element/capabilities/auditable.rb', line 224

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

#use_anonymous_auditorObject

Assigns an anonymous auditor as an #auditor.

Alleviates the need to assign a custom auditor for simple stuff when scripting.



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

def use_anonymous_auditor
    self.auditor = Class.new do
        include Arachni::Module::Auditor

        def initialize
            @framework = Arachni::Framework.new
        end
        #
        # @return   [Array<Issue>]  Unfiltered logged issues.
        #
        # @see Arachni::Module::Manager.results
        #
        def raw_issues
            Arachni::Module::Manager.results
        end

        #
        # @return   [Array<Issue>]  Deduplicated issues.
        #
        # @see AuditStore#issues
        #
        def issues
            auditstore.issues
        end

        # @return   [AuditStore]
        def auditstore
            AuditStore.new( options: Options.instance.to_h,
                            issues:  raw_issues )
        end
        alias :audit_store :auditstore

        def self.info
            { name: 'Anonymous auditor' }
        end
    end.new
end