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

Included in:
Arachni::Element::Capabilities::Auditable
Defined in:
lib/arachni/element/capabilities/auditable/timeout.rb

Overview

Evaluates whether or not the injection of specific data affects the response time of the web application.

It takes into account unstable network conditions and server-side failures and verifies the results before logging.

Methodology

Here’s how it works:

  • Loop 1 (#timeout_analysis) – 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 – If element times out it is added to a queue.

    • Stabilization (#responsive?) – The element is submitted with its default values in order to wait until the effects of the timing attack have worn off.

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

    • Liveness test – Ensures that the webapp is alive and not just timing-out by default

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

    • Stabilization (#responsive?)

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.

Usage

Call #timeout_analysis to schedule a timeout audit and execute Timeout.timeout_audit_run to run the scheduled operations.

This deviates from the normal framework structure because it is preferable to run timeout audits separately in order to avoid interference by other audit operations.

If you want to be notified every time a timeout audit is performed you can pass callback block to Timeout.on_timing_attacks.

Author:

Class Method Summary collapse

Instance Method Summary collapse

Class Method Details

.add_timeout_audit_block(&block) ⇒ Object



92
93
94
95
# File 'lib/arachni/element/capabilities/auditable/timeout.rb', line 92

def @@parent.add_timeout_audit_block( &block )
    @@timeout_audit_operations_cnt += 1
    @@timeout_audit_blocks << block
end

.add_timeout_candidate(elem) ⇒ Object



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

def @@parent.add_timeout_candidate( elem )
    @@timeout_audit_operations_cnt += 1
    @@timeout_candidates << elem
end

.call_on_timing_blocks(res, elem) ⇒ Object



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

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

.current_timeout_audit_operations_cntInteger

Returns amount of timeout-audit related operations (audit blocks + candidate elements).

Returns:

  • (Integer)

    amount of timeout-audit related operations (audit blocks + candidate elements)



88
89
90
# File 'lib/arachni/element/capabilities/auditable/timeout.rb', line 88

def @@parent.current_timeout_audit_operations_cnt
    @@timeout_audit_blocks.size + @@timeout_candidates.size
end

.included(mod) ⇒ Object



62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
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
128
129
130
131
132
133
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
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
# File 'lib/arachni/element/capabilities/auditable/timeout.rb', line 62

def self.included( mod )
    @@parent = mod

    #
    # Returns the names of all loaded modules that use timing attacks.
    #
    # @return   [Set]
    #
    def @@parent.timeout_loaded_modules
        @@timeout_loaded_modules
    end

    #
    # Holds timing-attack performing Procs to be run after all
    # non-timing-attack modules have finished.
    #
    # @return   [Queue]
    #
    def @@parent.timeout_audit_blocks
        @@timeout_audit_blocks
    end

    #
    # @return   [Integer]    amount of timeout-audit related operations
    #                           (audit blocks + candidate elements)
    #
    def @@parent.current_timeout_audit_operations_cnt
        @@timeout_audit_blocks.size + @@timeout_candidates.size
    end

    def @@parent.add_timeout_audit_block( &block )
        @@timeout_audit_operations_cnt += 1
        @@timeout_audit_blocks << block
    end

    def @@parent.add_timeout_candidate( elem )
        @@timeout_audit_operations_cnt += 1
        @@timeout_candidates << elem
    end

    #
    # @return   [Bool]  true if timeout attacks are currently running
    #
    def @@parent.running_timeout_attacks?
        @@running_timeout_attacks
    end

    #
    # Adds a block to be executed every time a timing attack is performed
    #
    def @@parent.on_timing_attacks( &block )
        @@on_timing_attacks << block
    end

    #
    # @return   [Integer]    amount of timeout-audit operations
    #
    def @@parent.timeout_audit_operations_cnt
        @@timeout_audit_operations_cnt
    end

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

    #
    # Runs all blocks in {timeout_audit_blocks} and verifies
    # and logs the candidate elements.
    #
    def @@parent.timeout_audit_run
        @@running_timeout_attacks = true

        while !@@timeout_audit_blocks.empty?
            @@timeout_audit_blocks.pop.call
        end

        while !@@timeout_candidates.empty?
            self.timeout_analysis_phase_2( @@timeout_candidates.pop )
        end
    end

    #
    # (Called by {timeout_audit_run}, do *NOT* call manually.)
    #
    # Runs phase 2 of the timing attack auditing an individual element
    # (which passed phase 1) with a higher delay and timeout.
    #
    # * Liveness check: Element is submitted as is to make sure that the page is alive and responsive
    #   * If liveness check fails then phase 2 is aborted
    #   * If liveness check succeeds it progresses to verification
    # * Verification: Element is submitted with an increased delay to verify the vulnerability
    #   * If verification fails it aborts
    #   * If verification succeeds the issue is logged
    # * Stabilize responsiveness: Wait for the effects of the timing attack to wear off
    #
    def @@parent.timeout_analysis_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.submit do |res|
            self.call_on_timing_blocks( res, elem )

            if res.timed_out?
                elem.print_info 'Liveness check failed, bailing out...'
                next
            end

            elem.print_info 'Liveness check was successful, progressing to verification...'
            elem.audit( str, opts ) do |c_res, c_opts|
                if !c_res.timed_out?
                    elem.print_info 'Verification failed.'
                    next
                end

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

        end

        elem.http.run
    end

    def call_on_timing_blocks( res, elem )
        @@parent.call_on_timing_blocks( res, elem )
    end

    # 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
end

.on_timing_attacks(&block) ⇒ Object

Adds a block to be executed every time a timing attack is performed



112
113
114
# File 'lib/arachni/element/capabilities/auditable/timeout.rb', line 112

def @@parent.on_timing_attacks( &block )
    @@on_timing_attacks << block
end

.running_timeout_attacks?Bool

Returns true if timeout attacks are currently running.

Returns:

  • (Bool)

    true if timeout attacks are currently running



105
106
107
# File 'lib/arachni/element/capabilities/auditable/timeout.rb', line 105

def @@parent.running_timeout_attacks?
    @@running_timeout_attacks
end

.timeout_analysis_phase_2(elem) ⇒ Object

(Called by timeout_audit_run, do NOT call manually.)

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

  • Liveness check: Element is submitted as is to make sure that the page is alive and responsive

    • If liveness check fails then phase 2 is aborted

    • If liveness check succeeds it progresses to verification

  • Verification: Element is submitted with an increased delay to verify the vulnerability

    • If verification fails it aborts

    • If verification succeeds the issue is logged

  • Stabilize responsiveness: Wait for the effects of the timing attack to wear off



157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
# File 'lib/arachni/element/capabilities/auditable/timeout.rb', line 157

def @@parent.timeout_analysis_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.submit do |res|
        self.call_on_timing_blocks( res, elem )

        if res.timed_out?
            elem.print_info 'Liveness check failed, bailing out...'
            next
        end

        elem.print_info 'Liveness check was successful, progressing to verification...'
        elem.audit( str, opts ) do |c_res, c_opts|
            if !c_res.timed_out?
                elem.print_info 'Verification failed.'
                next
            end

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

    end

    elem.http.run
end

.timeout_audit_blocksQueue

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

Returns:

  • (Queue)


80
81
82
# File 'lib/arachni/element/capabilities/auditable/timeout.rb', line 80

def @@parent.timeout_audit_blocks
    @@timeout_audit_blocks
end

.timeout_audit_operations_cntInteger

Returns amount of timeout-audit operations.

Returns:

  • (Integer)

    amount of timeout-audit operations



119
120
121
# File 'lib/arachni/element/capabilities/auditable/timeout.rb', line 119

def @@parent.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.



131
132
133
134
135
136
137
138
139
140
141
# File 'lib/arachni/element/capabilities/auditable/timeout.rb', line 131

def @@parent.timeout_audit_run
    @@running_timeout_attacks = true

    while !@@timeout_audit_blocks.empty?
        @@timeout_audit_blocks.pop.call
    end

    while !@@timeout_candidates.empty?
        self.timeout_analysis_phase_2( @@timeout_candidates.pop )
    end
end

.timeout_loaded_modulesSet

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

Returns:

  • (Set)


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

def @@parent.timeout_loaded_modules
    @@timeout_loaded_modules
end

Instance Method Details

#call_on_timing_blocks(res, elem) ⇒ Object



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

def call_on_timing_blocks( res, elem )
    @@parent.call_on_timing_blocks( res, elem )
end

#responsive?(limit = 120.0) ⇒ Bool

Submits self with a high timeout value and blocks until it gets a response.

That is to make sure that responsiveness has been restored before progressing further.

Parameters:

  • limit (Float) (defaults to: 120.0)

    how much time to afford the server to respond

Returns:

  • (Bool)

    true if server responds within the given time limit, false otherwise



262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
# File 'lib/arachni/element/capabilities/auditable/timeout.rb', line 262

def responsive?( limit = 120.0 )
    d_opts = {
        skip_orig: true,
        redundant: true,
        timeout:   limit * 1000,
        silent:    true,
        async:     false
    }

    orig_opts = opts

    print_info 'Waiting for the effects of the timing attack to wear off.'
    print_info "Max waiting time: #{limit} seconds."

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

    @opts.merge!( orig_opts )

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

    true
end

#timeout_analysis(strings, opts) ⇒ Object

Performs timeout/time-delay analysis and logs an issue should there be one.

Parameters:

  • strings (Array)

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

  • opts (Hash)

    options as described in Mutable::OPTIONS with the following extra:

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

    • :timeout_divider – __TIME__ = timeout / timeout_divider



236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
# File 'lib/arachni/element/capabilities/auditable/timeout.rb', line 236

def timeout_analysis( strings, opts )
    @@timeout_loaded_modules << @auditor.fancy_name

    @@parent.add_timeout_audit_block {
        delay = opts[:timeout]

        audit_timeout_debug_msg( 1, delay )
        timing_attack( strings, opts ) do |_, _, elem|
            elem.auditor = @auditor

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

            @@parent.add_timeout_candidate( elem ) if elem.responsive?
        end
    }
end