lib/arachni/element/capabilities/auditable/timeout.rb in arachni-0.4.2 vs lib/arachni/element/capabilities/auditable/timeout.rb in arachni-0.4.3
- old
+ new
@@ -12,12 +12,10 @@
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
=end
-require 'set'
-
module Arachni::Element::Capabilities
#
# Evaluates whether or not the injection of specific data affects the response
# time of the web application.
@@ -80,12 +78,13 @@
def @@parent.timeout_candidates
@@timeout_candidates
end
- # @return [Integer] amount of timeout-audit related operations
- # (audit blocks + candidate elements)
+ # @return [Integer]
+ # Amount of timeout-audit related operations
+ # (`audit blocks + candidate elements`).
def @@parent.current_timeout_audit_operations_cnt
@@timeout_candidates.size + @@timeout_candidates_phase3.size
end
def @@parent.add_timeout_candidate( elem )
@@ -179,11 +178,11 @@
if deduplicate?
next if @@timeout_candidate_phase3_ids.include?( elem.audit_id )
@@timeout_candidate_phase3_ids << elem.audit_id
end
- elem.print_info "Phase 2: Candidate can progress to Phase 3 --" +
+ elem.print_info 'Phase 2: Candidate can progress to Phase 3 --' <<
" #{elem.type.capitalize} input " +
"'#{elem.altered}' at #{elem.action}"
@@parent.add_timeout_phase3_candidate( elem )
end
@@ -248,21 +247,21 @@
@@parent.call_on_timing_blocks( res, elem )
end
@@timeout_audit_operations_cnt ||= 0
- # populated by timing attack phase 1 with
- # candidate elements to be verified by phase 2
+ # Populated by timing attack phase 1 with candidate elements to be
+ # verified by phase 2.
@@timeout_candidates ||= []
- @@timeout_candidate_ids ||= ::Arachni::BloomFilter.new
+ @@timeout_candidate_ids ||= ::Arachni::Support::LookUp::HashSet.new
@@timeout_candidates_phase3 ||= []
- @@timeout_candidate_phase3_ids ||= ::Arachni::BloomFilter.new
+ @@timeout_candidate_phase3_ids ||= ::Arachni::Support::LookUp::HashSet.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
+ # Modules which have called the timing attack audit method
+ # ({Arachni::Module::Auditor#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
@@ -297,39 +296,60 @@
end
#
# Performs timeout/time-delay analysis and logs an issue should there be one.
#
- # @param [Array] strings
- # Injection strings (`__TIME__` will be substituted with `timeout / timeout_divider`).
+ # @param [String, Array<String>, Hash{Symbol => <String, Array<String>>}] payloads
+ # 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.
+ #
+ # Delay placeholder `__TIME__` will be substituted with `timeout / timeout_divider`.
# @param [Hash] opts
- # Options as described in {Arachni::Element::Mutable::OPTIONS} with the
- # specified extras.
+ # Options as described in {Arachni::Element::Capabilities::Mutable::MUTATION_OPTIONS}
+ # with the specified extras.
# @option opts [Integer] :timeout
# Milliseconds to wait for the request to complete.
# @option opts [Integer] :timeout_divider
# `__TIME__ = timeout / timeout_divider`
#
- def timeout_analysis( strings, opts )
+ # @return [Bool]
+ # `true` if the audit was scheduled successfully, `false` otherwise (like
+ # if the resource is out of scope).
+ #
+ def timeout_analysis( payloads, opts )
+ if skip_path? self.action
+ print_debug "Element's action matches skip rule, bailing out."
+ return false
+ end
+
@@timeout_loaded_modules << @auditor.fancy_name
delay = opts[:timeout]
audit_timeout_debug_msg( 1, delay )
- timing_attack( strings, opts ) do |_, _, elem|
+ timing_attack( payloads, opts ) do |elem|
elem.auditor = @auditor
if deduplicate?
next if @@timeout_candidate_ids.include?( elem.audit_id )
@@timeout_candidate_ids << elem.audit_id
end
- print_info "Found a candidate for Phase 2 -- " +
+ print_info 'Found a candidate for Phase 2 -- ' <<
"#{elem.type.capitalize} input '#{elem.altered}' at #{elem.action}"
@@parent.add_timeout_candidate( elem ) if elem.responsive?
end
+
+ true
end
#
# Submits self with a high timeout value and blocks until it gets a response.
#
@@ -368,10 +388,11 @@
true
end
private
+
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 '---------------------------------------------'
@@ -381,31 +402,40 @@
# Audits elements using a timing attack.
#
# 'opts' needs to contain a :timeout value in milliseconds.</br>
# Optionally, you can add a :timeout_divider.
#
- # @param [Array] strings
+ # @param [String, Array, Hash{Symbol => String, Array<String>}] payloads
# Injection strings (`__TIME__` will be substituted with
# `timeout / timeout_divider`).
# @param [Hash] opts
# Options as described in {Arachni::Element::Mutable::OPTIONS}.
# @param [Block] block
# Block to call if a timeout occurs, it will be passed the
# {Typhoeus::Response response} and `opts`.
#
- def timing_attack( strings, opts, &block )
+ def timing_attack( payloads, opts, &block )
+ opts = opts.dup
opts[:timeout_divider] ||= 1
+ delay = opts[:timeout] / opts[:timeout_divider]
- [strings].flatten.each do |str|
+ # Intercept each element mutation prior to it being submitted and replace
+ # the '__TIME__' placeholder with the actual delay value.
+ each_mutation = proc do |mutation|
+ injected = mutation.altered_value
- opts[:timing_string] = str
- str = str.gsub( '__TIME__', ( opts[:timeout] / opts[:timeout_divider] ).to_s )
- opts[:skip_orig] = true
+ # Preserve the original because it's going to be needed for the
+ # verification phases.
+ mutation.opts[:timing_string] = injected
- audit( str, opts ) do |res, c_opts, elem|
- call_on_timing_blocks( res, elem )
- block.call( res, c_opts, elem ) if block && res.timed_out?
- end
+ mutation.altered_value = injected.gsub( '__TIME__', delay.to_s )
+ end
+
+ opts.merge!( each_mutation: each_mutation, skip_orig: true )
+
+ audit( payloads, opts ) do |res, _, elem|
+ call_on_timing_blocks( res, elem )
+ block.call( elem ) if block && res.timed_out?
end
end
end
end