lib/arachni/element/capabilities/auditable/rdiff.rb in arachni-0.4.3.2 vs lib/arachni/element/capabilities/auditable/rdiff.rb in arachni-0.4.4

- old
+ new

@@ -50,52 +50,43 @@ # # Performs differential analysis and logs an issue should there be one. # # opts = { - # :precision => 3, - # :faults => [ 'fault injections' ], - # :bools => [ 'boolean injections' ] + # pairs: [ + # { 'true expression' => 'false expression' } + # ] # } # # element.rdiff_analysis( 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 + # * let `control` be the control/control response + # * let `true_response` be the response of the injection of 'true expression' + # * let `false_response` be the response of the injection of 'false expression' # # A vulnerability is logged if: # - # default == bool AND bool.code == 200 AND fault != bool + # control == true_response AND true_response.code == 200 AND false_response != true_response # # The `bool` response is also checked in order to determine if it's a custom - # 404, if it is it'll be skipped. + # 404, if it is then it'll be skipped. # # If a block has been provided analysis and logging will be delegated to it. # # @param [Hash] opts # @option opts [Integer] :format # As seen in {Arachni::Element::Capabilities::Mutable::Format}. # @option opts [Integer] :precision # Amount of {String#rdiff refinement} iterations to perform. - # @option opts [Array<String>] :faults - # Array of fault injection strings (these are supposed to force erroneous - # conditions when interpreted). - # @option opts [Array<String>] :bools - # Array of boolean injection strings (these are supposed to not alter the - # webapp behavior when interpreted). + # @option opts [Array<Hash>] :pairs + # Pair of strings that should yield different results when interpreted. + # Keys should be the `true` expressions. # @param [Block] block - # To be used for custom analysis of responses; will be passed the following: + # To be used for custom analysis of gathered data. # - # * injected string - # * audited element - # * default response body - # * boolean response - # * fault injection response body - # # @return [Bool] # `true` if the audit was scheduled successfully, `false` otherwise (like # if the resource is out of scope or already audited). # def rdiff_analysis( opts = {}, &block ) @@ -104,131 +95,123 @@ return false end opts = self.class::MUTATION_OPTIONS.merge( RDIFF_OPTIONS.merge( opts ) ) - # don't continue if there's a missing value - auditable.values.each { |val| return if !val || val.empty? } + return false if auditable.empty? + # Don't continue if there's a missing value. + auditable.values.each { |val| return if val.to_s.empty? } + return false if rdiff_audited? rdiff_audited - responses = { - # will hold the original, default, response that results from submitting - orig: nil, + responses = {} + control = nil + opts[:precision].times do + # Get the default response. + submit do |res| + if control + print_status 'Got default/control response.' + end - # will hold responses of boolean injections - good: {}, - - # will hold responses of fault injections - bad: {} - } - - # submit the element, as is, opts[:precision] amount of times and - # rdiff the responses in order to arrive to a refined response without - # any superfluous dynamic content - opts[:precision].times { - # get the default responses - audit( '', opts ) do |res| - responses[:orig] ||= res.body - # remove context-irrelevant dynamic content like banners and such - responses[:orig] = responses[:orig].rdiff( res.body ) + # Remove context-irrelevant dynamic content like banners and such. + control = (control ? control.rdiff( res.body ) : res.body) end - } + end - # perform fault injection opts[:precision] amount of times and - # rdiff the responses in order to arrive to a refined response without - # any superfluous dynamic content - opts[:precision].times { - opts[:faults].each do |str| - # get mutations of self using the fault seed, which will - # cause an internal/silent error when evaluated - mutations( str, opts ).each do |elem| + opts[:pairs].each do |pair| + responses[pair] ||= {} + true_expr, false_expr = pair.to_a.first + + opts[:precision].times do + mutations( true_expr, opts ).each do |elem| print_status elem.status_string - # submit the mutation and store the response + # Submit the mutation and store the response. elem.submit( opts ) do |res| - responses[:bad][elem.altered] ||= res.body.clone - # remove context-irrelevant dynamic content like banners and such - # from the error page - responses[:bad][elem.altered] = - responses[:bad][elem.altered].rdiff( res.body.clone ) + if responses[pair][elem.altered][:true] + elem.print_status "Gathering data for '#{elem.altered}' " << + "#{type} input -- Got true response:" << + " #{true_expr}" + end + + responses[pair][elem.altered] ||= {} + responses[pair][elem.altered][:mutation] = elem + + # Keep the latest response for the {Arachni::Issue}. + responses[pair][elem.altered][:response] = res + responses[pair][elem.altered][:injected_string] = true_expr + + responses[pair][elem.altered][:true] ||= res.body.clone + # Remove context-irrelevant dynamic content like banners + # and such from the error page. + responses[pair][elem.altered][:true] = + responses[pair][elem.altered][:true].rdiff( res.body.clone ) end end - end - } - # get injection variations that will not affect the outcome of the query - opts[:bools].each do |str| + mutations( false_expr, opts ).each do |elem| + responses[pair][elem.altered] ||= {} - # get mutations of self using the boolean seed, which will not - # alter the execution flow - mutations( str, opts ).each do |elem| - print_status elem.status_string + # Submit the mutation and store the response. + elem.submit( opts ) do |res| + if responses[pair][elem.altered][:false] + elem.print_status "Gathering data for '#{elem.altered}'" << + " #{type} input -- Got false " << + "response: #{false_expr}" + end - # submit the mutation and store the response - elem.submit( opts ) do |res| - responses[:good][elem.altered] ||= [] - # save the response and some data for analysis - responses[:good][elem.altered] << { - 'str' => str, - 'res' => res, - 'elem' => elem - } + responses[pair][elem.altered][:false] ||= res.body.clone + + # Remove context-irrelevant dynamic content like banners + # and such from the error page. + responses[pair][elem.altered][:false] = + responses[pair][elem.altered][:false].rdiff( res.body.clone ) + end end end end - # when this runs the "responses" hash will have been populated and we - # can continue with analysis - http.after_run { - responses[:good].keys.each do |key| - responses[:good][key].each do |res| + # When this runs the "responses" hash will have been populated and we + # can continue with analysis. + http.after_run do + responses.each do |pair, data| + if block + exception_jail( false ){ block.call( pair, data ) } + next + end - # if there's a block passed then delegate analysis to it - if block - exception_jail( false ){ - block.call( res['str'], res['elem'], responses[:orig], - res['res'], responses[:bad][key] ) - } + data.each do |input_name, result| + # if default_response_body == true_response_body AND + # false_response_body != true_response_code AND + # true_response_code == 200 + if control == result[:true] && + result[:false] != result[:true] && + result[:response].code == 200 - # if default_response_body == bool_response_body AND - # bool_response_code == 200 AND - # fault_response_body != bool_response_body - elsif responses[:orig] == res['res'].body && - responses[:bad][key] != res['res'].body && - res['res'].code == 200 + # Check to see if the `true` response we're analyzing + # is a custom 404 page. + http.custom_404?( result[:response] ) do |custom_404| + # If this is a custom 404 page bail out. + next if custom_404 - # check to see if the current boolean response we're analyzing - # is a custom 404 page - http.custom_404?( res['res'] ) do |bool| - # if this is a custom 404 page bail out - next if bool - - # if this isn't a custom 404 page then it means that - # the element is vulnerable, so go ahead and log the issue - - # information for the Metareport report - opts = { - injected_orig: res['str'], - combo: res['elem'].auditable - } - @auditor.log({ - var: key, - opts: opts, - injected: res['str'], - id: res['str'], - elem: res['elem'].type, - }, res['res'] + var: input_name, + opts: { + injected_orig: result[:injected_string], + combo: result[:mutation].auditable + }, + injected: result[:mutation].altered_value, + elem: type + }, result[:response] ) end end - end end - } + end true end private