Module: Arachni::Module::Auditor

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

Overview

Auditor module

Included by Base and provides abstract audit methods.

There are 3 main types of audit techniques available:

@author: Tasos “Zapotek” Laskos

<[email protected]>
<[email protected]>

@version: 0.3.1

Defined Under Namespace

Modules: Element, Format

Constant Summary collapse

RDIFF_OPTIONS =
{
    # append our seeds to the default values
    :format      => [ Format::APPEND ],

    # allow duplicate requests
    :redundant   => true,

    # amount of rdiff iterations
    :precision   => 2
}
OPTIONS =

Default audit options.

{

    #
    # Elements to audit.
    #
    # Only required when calling {#audit}.<br/>
    # If no elements have been passed to audit it will
    # use the elements in {#self.info}.
    #
    :elements => [ Element::LINK, Element::FORM,
                   Element::COOKIE, Element::HEADER,
                   Issue::Element::BODY ],

    #
    # The regular expression to match against the response body.
    #
    :regexp   => nil,

    #
    # Verify the matched string with this value.
    #
    :match    => nil,

    #
    # Formatting of the injection strings.
    #
    # A new set of audit inputs will be generated
    # for each value in the array.
    #
    # Values can be OR'ed bitfields of all available constants
    # of {Auditor::Format}.
    #
    # @see  Auditor::Format
    #
    :format   => [ Format::STRAIGHT, Format::APPEND,
                   Format::NULL, Format::APPEND | Format::NULL ],

    #
    # If 'train' is set to true the HTTP response will be
    # analyzed for new elements. <br/>
    # Be careful when enabling it, there'll be a performance penalty.
    #
    # When the Auditor submits a form with original or sample values
    # this option will be overridden to true.
    #
    :train     => false,

    #
    # Enable skipping of already audited inputs
    #
    :redundant => false,

    #
    # Make requests asynchronously
    #
    :async     => true
}

Class Method Summary collapse

Instance Method Summary collapse

Dynamic Method Handling

This class handles dynamic methods through the method_missing method

#method_missing(sym, *args, &block) ⇒ Object

Provides the following methods:

  • audit_links()

  • audit_forms()

  • audit_cookies()

  • audit_headers()

Metaprogrammed to avoid redundant code while maintaining compatibility and method shortcuts.

Raises:

  • (NoMethodError)

See Also:



1024
1025
1026
1027
1028
1029
1030
1031
1032
1033
1034
1035
1036
1037
1038
# File 'lib/arachni/module/auditor.rb', line 1024

def method_missing( sym, *args, &block )

    elem = sym.to_s.gsub!( 'audit_', '@' )
    raise NoMethodError.new( "Undefined method '#{sym.to_s}'.", sym, args ) if !elem

    elems = @page.instance_variable_get( elem )

    if( elems && elem )
        raise ArgumentError.new( "Missing required argument 'injection_str'" +
            " for audit_#{elem.gsub( '@', '' )}()." ) if( !args[0] )
        audit_elems( elems, args[0], args[1] ? args[1]: {}, &block )
    else
        raise NoMethodError.new( "Undefined method '#{sym.to_s}'.", sym, args )
    end
end

Class Method Details

.add_timeout_audit_block(&block) ⇒ Object



82
83
84
85
# File 'lib/arachni/module/auditor.rb', line 82

def self.add_timeout_audit_block( &block )
    @@__timeout_audit_operations_cnt += 1
    @@__timeout_audit_blocks << block
end

.add_timeout_candidate(elem) ⇒ Object



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

def Auditor.add_timeout_candidate( elem )
    @@__timeout_audit_operations_cnt += 1
    @@__timeout_candidates << elem
end

.audit_timeout_phase_2(elem) ⇒ Object

Runs phase 2 of the timing attack auditing an individual element (which passed phase 1) with a higher delay and timeout



626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
# File 'lib/arachni/module/auditor.rb', line 626

def self.audit_timeout_phase_2( elem )

    # reset the audited list since we're going to re-audit the elements
    # @@__timeout_audited = Set.new

    opts = elem.opts
    opts[:timeout] *= 2
    # opts[:async]    = false
    # self.audit_timeout_debug_msg( 2, opts[:timeout] )

    str = opts[:timing_string].gsub( '__TIME__',
        ( opts[:timeout] / opts[:timeout_divider] ).to_s )

    opts[:timeout] *= 0.7

    elem.auditable = elem.orig

    # this is the control; request the URL of the element to make sure
    # that the web page is alive i.e won't time-out by default
    elem.get_auditor.http.get( elem.action ).on_complete {
        |res|

        self.call_on_timing_blocks( res, elem )

        if !res.timed_out?

            elem.get_auditor.print_info( 'Liveness check was successful, progressing to verification...' )

            elem.audit( str, opts ) {
                |c_res, c_opts|

                if c_res.timed_out?

                    # all issues logged by timing attacks need manual verification.
                    # end of story.
                    # c_opts[:verification] = true
                    elem.get_auditor.log( c_opts, c_res )

                    self.audit_timeout_stabilize( elem )

                else
                    elem.get_auditor.print_info( 'Verification failed.' )
                end
            }
        else
            elem.get_auditor.print_info( 'Liveness check failed, bailing out...' )
        end
    }

    elem.get_auditor.http.run
end

.audit_timeout_stabilize(elem) ⇒ Object

Submits an element which has just been audited using a timing attack with a high timeout in order to determine when the effects of a timing attack has worn off in order to safely continue the audit.

Parameters:



685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
# File 'lib/arachni/module/auditor.rb', line 685

def self.audit_timeout_stabilize( elem )

    d_opts = {
        :skip_orig => true,
        :redundant => true,
        :timeout   => 120000,
        :silent    => true,
        :async     => false
    }

    orig_opts = elem.opts

    elem.get_auditor.print_info( 'Waiting for the effects of the timing attack to wear off.' )
    elem.get_auditor.print_info( 'Max waiting time: ' + ( d_opts[:timeout] /1000 ).to_s + ' seconds.' )

    elem.auditable = elem.orig
    res = elem.submit( d_opts ).response

    if !res.timed_out?
        elem.get_auditor.print_info( 'Server seems responsive again.' )
    else
        elem.get_auditor.print_error( 'Max waiting time exceeded, the server may be dead.' )
    end

    elem.opts.merge!( orig_opts )
end

.call_on_timing_blocks(res, elem) ⇒ Object



105
106
107
108
109
110
# File 'lib/arachni/module/auditor.rb', line 105

def Auditor.call_on_timing_blocks( res, elem )
    @@__on_timing_attacks.each {
        |block|
        block.call( res, elem )
    }
end

.current_timeout_audit_operations_cntObject



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

def self.current_timeout_audit_operations_cnt
    @@__timeout_audit_blocks.size + @@__timeout_candidates.size
end

.included(mod) ⇒ Object



32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
# File 'lib/arachni/module/auditor.rb', line 32

def self.included( mod )
    # @@__timeout_audited      ||= Set.new

    # holds timing-attack performing Procs to be run after all
    # non-timing-attack modules have finished.
    @@__timeout_audit_blocks   ||= Queue.new

    @@__timeout_audit_operations_cnt ||= 0

    # populated by timing attack phase 1 with
    # candidate elements to be verified by phase 2
    @@__timeout_candidates     ||= Queue.new

    # modules which have called the timing attack audit method (audit_timeout)
    # we're interested in the amount, not the names, and is used to
    # determine scan progress
    @@__timeout_loaded_modules ||= Set.new

    @@__on_timing_attacks      ||= []

    @@__running_timeout_attacks ||= false

    # the rdiff attack performs it own redundancy checks so we need this to
    # keep track audited elements
    @@__rdiff_audited ||= Set.new
end

.on_timing_attacks(&block) ⇒ Object



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

def self.on_timing_attacks( &block )
    @@__on_timing_attacks << block
end

.running_timeout_attacks?Boolean

Returns:

  • (Boolean)


93
94
95
# File 'lib/arachni/module/auditor.rb', line 93

def self.running_timeout_attacks?
    @@__running_timeout_attacks
end

.timeout_audit_blocksQueue

Holds timing-attack performing Procs to be run after all non-timing-attack modules have finished.

Returns:

  • (Queue)


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

def self.timeout_audit_blocks
    @@__timeout_audit_blocks
end

.timeout_audit_operations_cntObject



101
102
103
# File 'lib/arachni/module/auditor.rb', line 101

def self.timeout_audit_operations_cnt
    @@__timeout_audit_operations_cnt
end

.timeout_audit_runObject

Runs all blocks in timeout_audit_blocks and verifies and logs the candidate elements.



610
611
612
613
614
615
616
617
618
619
620
# File 'lib/arachni/module/auditor.rb', line 610

def self.timeout_audit_run
    @@__running_timeout_attacks = true

    while( !@@__timeout_audit_blocks.empty? )
        @@__timeout_audit_blocks.pop.call
    end

    while( !@@__timeout_candidates.empty? )
        self.audit_timeout_phase_2( @@__timeout_candidates.pop )
    end
end

.timeout_loaded_modulesSet

Returns the names of all loaded modules that use timing attacks.

Returns:

  • (Set)


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

def self.timeout_loaded_modules
    @@__timeout_loaded_modules
end

Instance Method Details

#__rdiff_audit_id(elem) ⇒ Object



1008
1009
1010
# File 'lib/arachni/module/auditor.rb', line 1008

def __rdiff_audit_id( elem )
    elem.action + elem.auditable.keys.to_s
end

#__rdiff_audited!(elem) ⇒ Object



1000
1001
1002
# File 'lib/arachni/module/auditor.rb', line 1000

def __rdiff_audited!( elem )
    @@__rdiff_audited << __rdiff_audit_id( elem )
end

#__rdiff_audited?(elem) ⇒ Boolean

Returns:

  • (Boolean)


1004
1005
1006
# File 'lib/arachni/module/auditor.rb', line 1004

def __rdiff_audited?( elem )
    @@__rdiff_audited.include?( __rdiff_audit_id( elem ) )
end

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

Provides easy access to element auditing using simple injection and pattern matching.

If a block has been provided analysis and logging will be delegated to it, otherwise, if a match is found it will be automatically logged.

If no elements have been specified in ‘opts’ it will use the elements from the module’s “self.info()” hash. <br/> If no elements have been specified in ‘opts’ or “self.info()” it will use the elements in OPTIONS. <br/>

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 custom 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.



494
495
496
497
498
499
500
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
# File 'lib/arachni/module/auditor.rb', line 494

def audit( injection_str, opts = { }, &block )

    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

    opts  = OPTIONS.merge( opts )

    opts[:elements].each {
        |elem|

        case elem

            when  Element::LINK
                audit_links( injection_str, opts, &block )

            when  Element::FORM
                audit_forms( injection_str, opts, &block )

            when  Element::COOKIE
                audit_cookies( injection_str, opts, &block )

            when  Element::HEADER
                audit_headers( injection_str, opts, &block )
            when  Element::BODY
            else
                raise( 'Unknown element to audit:  ' + elem.to_s )
        end

    }
end

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

Audits Auditable HTML/HTTP elements

Parameters:

See Also:



1050
1051
1052
1053
1054
1055
1056
1057
1058
1059
1060
1061
1062
# File 'lib/arachni/module/auditor.rb', line 1050

def audit_elems( elements, injection_str, opts = { }, &block )

    opts = OPTIONS.merge( opts )
    url  = @page.url

    opts[:injected_orig] = injection_str

    elements.deep_clone.each {
        |elem|
        elem.auditor( self )
        elem.audit( injection_str, opts, &block )
    }
end

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

Audits all elements types in opts (or self.class.info if there are none in opts) using differential analysis attacks.

opts = {
    :precision => 3,
    :faults    => [ 'fault injections' ],
    :bools     => [ 'boolean injections' ]
}

audit_rdiff( opts )

Here’s how it goes:

let default be the default/original response
let fault   be the response of the fault injection
let bool    be the response of the boolean injection

a vulnerability is logged if default == bool AND bool.code == 200 AND fault != bool

The “bool” response is also checked in order to determine if it’s a custom 404, if it is it’ll be skipped.

If a block has been provided analysis and logging will be delegated to it.

Parameters:

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

    available options:

    • :format – as seen in OPTIONS

    • :elements – as seen in OPTIONS

    • :train – as seen in OPTIONS

    • :precision – amount of rdiff iterations

    • :faults – array of fault injection strings (these are supposed to force erroneous conditions when interpreted)

    • :bools – array of boolean injection strings (these are supposed to not alter the webapp behavior when interpreted)

  • &block (Block)

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

    • injected string

    • audited element

    • default response body

    • boolean response

    • fault injection response body



790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
# File 'lib/arachni/module/auditor.rb', line 790

def audit_rdiff( opts = {}, &block )

    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

    opts[:elements].each {
        |elem|

        case elem

            when  Element::LINK
                next if !Options.instance.audit_links
                @page.links.each {
                    |c_elem|
                    audit_rdiff_elem( c_elem, opts, &block )
                }

            when  Element::FORM
                next if !Options.instance.audit_forms
                @page.forms.each {
                    |c_elem|
                    audit_rdiff_elem( c_elem, opts, &block )
                }

            when  Element::COOKIE
                next if !Options.instance.audit_cookies
                @page.cookies.each {
                    |c_elem|
                    audit_rdiff_elem( c_elem, opts, &block )
                }

            when  Element::HEADER
                next if !Options.instance.audit_headers
                @page.headers.each {
                    |c_elem|
                    audit_rdiff_elem( c_elem, opts, &block )
                }
            when  Element::BODY
            else
                raise( 'Unknown element to audit:  ' + elem.to_s )

        end

    }
end

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

Audits a single element using an rdiff attack.

Parameters:



848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
# File 'lib/arachni/module/auditor.rb', line 848

def audit_rdiff_elem( elem, opts = {}, &block )

    opts = RDIFF_OPTIONS.merge( opts )

    # don't continue if there's a missing value
    elem.auditable.values.each {
        |val|
        return if !val || val.empty?
    }

    return if __rdiff_audited?( elem )
    __rdiff_audited!( elem )

    responses = {
        :orig => nil,
        :good => {},
        :bad  => {},
        :bad_total  => 0,
        :good_total => 0
    }

    elem.auditor( self )
    opts[:precision].times {
        # get the default responses
        elem.audit( '', opts ) {
            |res|
            responses[:orig] ||= res.body
            # remove context-irrelevant dynamic content like banners and such
            # from the error page
            responses[:orig] = responses[:orig].rdiff( res.body )
        }
    }

    opts[:precision].times {
        opts[:faults].each {
            |str|

            # get injection variations that will hopefully cause an internal/silent
            # SQL error
            variations = elem.injection_sets( str, opts )

            responses[:bad_total] =  variations.size

            variations.each {
                |c_elem|

                print_status( c_elem.get_status_str( c_elem.altered ) )

                # register us as the auditor
                c_elem.auditor( self )
                # submit the link and get the response
                c_elem.submit( opts ).on_complete {
                    |res|

                    responses[:bad][c_elem.altered] ||= res.body.clone

                    # remove context-irrelevant dynamic content like banners and such
                    # from the error page
                    responses[:bad][c_elem.altered] =
                        responses[:bad][c_elem.altered].rdiff( res.body.clone )
                }
            }
        }
    }

    opts[:bools].each {
        |str|

        # get injection variations that will not affect the outcome of the query
        variations = elem.injection_sets( str, opts )

        responses[:good_total] =  variations.size

        variations.each {
            |c_elem|

            print_status( c_elem.get_status_str( c_elem.altered ) )

            # register us as the auditor
            c_elem.auditor( self )
            # submit the link and get the response
            c_elem.submit( opts ).on_complete {
                |res|

                responses[:good][c_elem.altered] ||= []

                # save the response for later analysis
                responses[:good][c_elem.altered] << {
                    'str'  => str,
                    'res'  => res,
                    'elem' => c_elem
                }
            }
        }
    }

    # when this runs the 'responses' hash will have been populated
    @http.after_run {

        responses[:good].keys.each {
            |key|

            responses[:good][key].each {
                |res|

                if block
                    block.call( res['str'], res['elem'], responses[:orig], res['res'], responses[:bad][key] )
                elsif( responses[:orig] == res['res'].body &&
                    responses[:bad][key] != res['res'].body &&
                    !@http.custom_404?( res['res'] ) && res['res'].code == 200 )

                    url = res['res'].effective_url

                    # since we bypassed the auditor completely we need to create
                    # our own opts hash and pass it to the Vulnerability class.
                    #
                    # this is only required for Metasploitable vulnerabilities
                    opts = {
                        :injected_orig => res['str'],
                        :combo         => res['elem'].auditable
                    }

                    issue = Issue.new( {
                            :var          => key,
                            :url          => url,
                            :method       => res['res'].request.method.to_s,
                            :opts         => opts,
                            :injected     => res['str'],
                            :id           => res['str'],
                            :regexp       => 'n/a',
                            :regexp_match => 'n/a',
                            :elem         => res['elem'].type,
                            :response     => res['res'].body,
                            # :verification => true,
                            :headers      => {
                                :request    => res['res'].request.headers,
                                :response   => res['res'].headers,
                            }
                        }.merge( self.class.info )
                    )

                    print_ok( "In #{res['elem'].type} var '#{key}' ( #{url} )" )

                    # register our results with the system
                    register_results( [ issue ] )
                end

            }
        }
    }
end

#audit_timeout(strings, opts) ⇒ Object

Audits elements using timing attacks and automatically logs results.

Here’s how it works:

  • Loop 1 – Populates the candidate queue. We’re picking the low hanging fruit here so we can run this in larger concurrent bursts which cause lots of noise.

    • Initial probing for candidates – Any element that times out is added to a queue.

    • Stabilization – The candidate is submitted with its default values in order to wait until the effects of the timing attack have worn off.

  • Loop 2 – Verifies the candidates. This is much more delicate so the concurrent requests are lowered to pairs.

    • Liveness test – Ensures that stabilization was successful before moving on.

    • Verification using an increased timeout – Any elements that time out again are logged.

    • Stabilization

Ideally, all requests involved with timing attacks would be run in sync mode but the performance penalties are too high, thus we compromise and make the best of it by running as little an amount of concurrent requests as possible for any given phase.

opts = {
    :format  => [ Format::STRAIGHT ],
    :timeout => 4000,
    :timeout_divider => 1000
}

audit_timeout( [ 'sleep( __TIME__ );' ], opts )

Parameters:

  • strings (Array)

    injection strings __TIME__ will be substituted with (timeout / timeout_divider)

  • opts (Hash)

    options as described in OPTIONS with the following extra:

    • :timeout – milliseconds to wait for the request to complete

    • :timeout_divider – __TIME__ = timeout / timeout_divider



585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
# File 'lib/arachni/module/auditor.rb', line 585

def audit_timeout( strings, opts )
    @@__timeout_loaded_modules << self.class.info[:name]

    Auditor.add_timeout_audit_block {
        delay = opts[:timeout]

        audit_timeout_debug_msg( 1, delay )
        timing_attack( strings, opts ) {
            |res, c_opts, elem|

            elem.auditor( self )

            print_info( "Found a candidate -- #{elem.type.capitalize} input '#{elem.altered}' at #{elem.action}" )

            Arachni::Module::Auditor.audit_timeout_stabilize( elem )

            Auditor.add_timeout_candidate( elem )
        }
    }
end

#audit_timeout_debug_msg(phase, delay) ⇒ Object



712
713
714
715
716
717
# File 'lib/arachni/module/auditor.rb', line 712

def audit_timeout_debug_msg( phase, delay )
    print_debug( '---------------------------------------------' )
    print_debug( "Running phase #{phase.to_s} of timing attack." )
    print_debug( "Delay set to: #{delay.to_s} milliseconds" )
    print_debug( '---------------------------------------------' )
end

#call_on_timing_blocks(res, elem) ⇒ Object



112
113
114
# File 'lib/arachni/module/auditor.rb', line 112

def call_on_timing_blocks( res, elem )
    Auditor.call_on_timing_blocks( res, elem )
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)

    defaults to @page data



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
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
# File 'lib/arachni/module/auditor.rb', line 418

def log( opts, res = nil )

    method = nil

    request_headers  = nil
    response_headers = @page.response_headers
    response         = @page.html
    url              = @page.url
    method           = @page.method.to_s.upcase if @page.method

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

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

    begin
        print_ok( "In #{opts[:element]} var '#{opts[:altered]}' ( #{url} )" )
    rescue
    end

    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 )
    print_debug( 'Request ID: ' + res.request.id.to_s ) if res
    print_verbose( '---------' ) if only_positives?

    # Instantiate a new Issue class and append it to the results array
    log_issue(
        :var          => opts[:altered],
        :url          => url,
        :injected     => opts[:injected],
        :id           => opts[:id],
        :regexp       => opts[:regexp],
        :regexp_match => opts[:match],
        :elem         => opts[:element],
        :verification => opts[:verification] || false,
        :method       => method,
        :response     => response,
        :opts         => opts,
        :headers      => {
            :request    => request_headers,
            :response   => response_headers,
        }
    )
end

#log_issue(opts) ⇒ Object

Helper method for issue logging.

Parameters:

  • opts (Hash)

    issue options (Issue)

  • include_class_info (Bool)

    merge opts with module.info?

See Also:

  • Base#register_results


348
349
350
351
# File 'lib/arachni/module/auditor.rb', line 348

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

#log_remote_file(res) ⇒ Object Also known as: log_remote_directory

Logs the existence of a remote file as an issue.

Parameters:

See Also:



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

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

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

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.

Parameters:

  • url (String)
  • silent (Bool) (defaults to: false)

    if false, a message will be sent to stdout containing the status of the operation.

  • &block (Proc)

    called if the file exists, just before logging

Returns:

  • (Object)
    • nil if no URL was provided

    • false if the request couldn’t be fired

    • true if everything went fine

See Also:



280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
# File 'lib/arachni/module/auditor.rb', line 280

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

    req  = @http.get( url )
    return false if !req
    req.on_complete {
        |res|

        print_status( 'Analyzing response for: ' + url ) if !silent

        if remote_file_exist?( res )
            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.add_response( res )
        end
    }

    return true
end

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

Matches the “string” (default string is the HTML code in @page.html) 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).

Parameters:

  • regexps (Array<Regexp>)

    array of regular expressions to be tested

  • string (String) (defaults to: @page.html)

    string to

  • block (Block)

    block to verify matches before logging, must return true/false



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

def match_and_log( regexps, string = @page.html, &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 {
        |regexp|

        string.scan( regexp ).flatten.uniq.each {
            |match|

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

            log(
                :regexp  => regexp,
                :match   => match,
                :element => Issue::Element::BODY
            )
        } if elems.include? Issue::Element::BODY

        next if string == @page.html

        @page.response_headers.each {
            |k,v|
            next if !v

            v.to_s.scan( regexp ).flatten.uniq.each {
                |match|

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

                log(
                    :var => k,
                    :regexp  => regexp,
                    :match   => match,
                    :element => Issue::Element::HEADER
                )
            }
        } if elems.include? Issue::Element::HEADER

    }
end

#override_instance_scope?Bool

ABSTRACT - OPTIONAL

Allows modules to ignore HPG scope restrictions

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

Returns:

  • (Bool)


251
252
253
# File 'lib/arachni/module/auditor.rb', line 251

def override_instance_scope?
    false
end

#redundantArray

ABSTRACT - OPTIONAL

Prevents auditing elements that have been previously logged by any of the modules returned by this method.

Returns:

  • (Array)

    module names



237
238
239
240
# File 'lib/arachni/module/auditor.rb', line 237

def redundant
    # [ 'sqli', 'sqli_blind_rdiff' ]
    []
end

#register_results(issues) ⇒ Object

Just a delegator logs an array of issues.

Parameters:

See Also:



262
263
264
# File 'lib/arachni/module/auditor.rb', line 262

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

#remote_file_exist?(res) ⇒ Boolean

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

Parameters:

Returns:

  • (Boolean)


310
311
312
# File 'lib/arachni/module/auditor.rb', line 310

def remote_file_exist?( res )
    res.code == 200 && !@http.custom_404?( res )
end

#skip?(elem) ⇒ Boolean

This is called right before an [Arachni::Parser::Element] is submitted/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)


538
539
540
541
542
543
544
545
546
547
548
549
# File 'lib/arachni/module/auditor.rb', line 538

def skip?( elem )
    redundant.map {
        |mod|

        mod_name = @framework.modules[mod].info[:name]

        set_id = @framework.modules.class.issue_set_id_from_elem( mod_name, elem )
        return true if @framework.modules.issue_set.include?( set_id )
    } if @framework

    return false
end

#timing_attack(strings, opts, &block) ⇒ Object

Audits elements using a timing attack.

‘opts’ needs to contain a :timeout value in milliseconds.</br> Optionally, you can add a :timeout_divider.

Parameters:

  • strings (Array)

    injection strings ‘__TIME__’ will be substituted with (timeout / timeout_divider)

  • opts (Hash)

    options as described in OPTIONS

  • &block (Block)

    block to call if a timeout occurs, it will be passed the response and opts



731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
# File 'lib/arachni/module/auditor.rb', line 731

def timing_attack( strings, opts, &block )

    opts[:timeout_divider] ||= 1

    [strings].flatten.each {
        |str|

        opts[:timing_string] = str
        str = str.gsub( '__TIME__', ( (opts[:timeout] + 3 * opts[:timeout_divider]) / opts[:timeout_divider] ).to_s )
        opts[:skip_orig] = true

        audit( str, opts ) {
            |res, c_opts, elem|

            call_on_timing_blocks( res, elem )
            block.call( res, c_opts, elem ) if block && res.timed_out?
        }
    }

    @http.run
end