lib/arachni/audit_store.rb in arachni-0.4.0.4 vs lib/arachni/audit_store.rb in arachni-0.4.1

- old
+ new

@@ -1,31 +1,33 @@ =begin - Arachni - Copyright (c) 2010-2012 Tasos "Zapotek" Laskos <tasos.laskos@gmail.com> + Copyright 2010-2012 Tasos Laskos <tasos.laskos@gmail.com> - This is free software; you can copy and distribute and modify - this program under the term of the GPL v2.0 License - (See LICENSE file for details) + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + 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 'digest/md5' module Arachni -require Options.instance.dir['lib'] + 'issue' +require Options.dir['lib'] + 'issue' # -# Arachni::AuditStore class +# Represents a finished audit session. # -# Represents a finished audit session.<br/> # It holds information about the runtime environment, # the results of the audit etc... # -# @author: Tasos "Zapotek" Laskos -# <tasos.laskos@gmail.com> -# <zapotek@segfault.gr> -# @version: 0.1.2 +# @author Tasos "Zapotek" Laskos <tasos.laskos@gmail.com> # class AuditStore # # @return [String] the version of the framework @@ -73,38 +75,39 @@ attr_reader :delta_time MODULE_NAMESPACE = ::Arachni::Modules ORDER = [ - ::Arachni::Issue::Severity::HIGH, - ::Arachni::Issue::Severity::MEDIUM, - ::Arachni::Issue::Severity::LOW, - ::Arachni::Issue::Severity::INFORMATIONAL + Severity::HIGH, + Severity::MEDIUM, + Severity::LOW, + Severity::INFORMATIONAL ] - def initialize( audit = {} ) + def initialize( opts = {} ) @plugins = {} @sitemap = [] + @issues ||= [] + @options ||= Options + # set instance variables from audit opts - audit.each { - |k, v| - self.instance_variable_set( '@' + k.to_s, v ) - } + opts.each { |k, v| self.instance_variable_set( '@' + k.to_s, v ) } - @options = prepare_options( @options ) - @issues = sort( prepare_variations( @issues ) ) - if @options['start_datetime'] - @start_datetime = @options['start_datetime'].asctime + @options = prepare_options( @options ) + @issues = sort( prepare_variations( @issues.deep_clone ) ) + + @start_datetime = if @options['start_datetime'] + @options['start_datetime'].asctime else - @start_datetime = Time.now.asctime + Time.now.asctime end - if @options['finish_datetime'] - @finish_datetime = @options['finish_datetime'].asctime + @finish_datetime = if @options['finish_datetime'] + @options['finish_datetime'].asctime else - @finish_datetime = Time.now.asctime + Time.now.asctime end @delta_time = secs_to_hms( @options['delta_time'] ) end @@ -113,11 +116,11 @@ # # @param [String] file the file to load # # @return [AuditStore] # - def AuditStore.load( file ) + def self.load( file ) begin r = YAML.load( IO.read( file ) ) r.version r rescue Exception => e @@ -130,60 +133,58 @@ # # @param [String] file # def save( file ) begin - File.open( file, 'w' ) { - |f| - f.write( YAML.dump( self ) ) - } + File.open( file, 'w' ) { |f| f.write( YAML.dump( self ) ) } rescue - File.open( file, 'wb' ) { - |f| - f.write( Marshal.dump( self ) ) - } + File.open( file, 'wb' ) { |f| f.write( Marshal.dump( self ) ) } end end # # Returns 'self' and all objects in its instance vars as hashes # # @return [Hash] # - def to_h - hash = obj_to_hash( self ).dup + def to_hash + hash = obj_to_hash( self ).deep_clone - hash['issues'] = hash['issues'].map { - |issue| + hash['issues'] = hash['issues'].map do |issue| issue.variations = issue.variations.map { |var| obj_to_hash( var ) } obj_to_hash( issue ) - } + end - hash['plugins'].each { - |plugin, results| + hash['plugins'].each do |plugin, results| next if !results[:options] - hash['plugins'][plugin][:options] = hash['plugins'][plugin][:options].map { - |opt| - opt.to_h - } - } + hash['plugins'][plugin][:options] = + hash['plugins'][plugin][:options].map { |opt| opt.to_h } + end - return hash + hash end + alias :to_h :to_hash + def ==( other ) + to_hash == other.to_hash + end + + def hash + to_hash.hash + end + private def sort( issues ) sorted = [] - issues.each { - |issue| - sorted[ORDER.rindex( issue.severity )] ||= [] - sorted[ORDER.rindex( issue.severity )] << issue - } - - return sorted.flatten.reject{ |issue| issue.nil? } + issues.each do |issue| + order = ORDER.rindex( issue.severity ) || ORDER.size + sorted[order] ||= [] + sorted[order] << issue + end + sorted.flatten.compact end # # Converts obj to hash @@ -192,61 +193,53 @@ # # @return [Hash] # def obj_to_hash( obj ) hash = {} - obj.instance_variables.each { - |var| - key = var.to_s.gsub( /@/, '' ) - hash[key] = obj.instance_variable_get( var ) - } - return hash + obj.instance_variables.each do |var| + hash[var.to_s.gsub( /@/, '' )] = obj.instance_variable_get( var ) + end + hash end # # Prepares the hash to be stored in {AuditStore#options} # - # The 'options' dimention of the array that initializes AuditObjects<br/> + # The value of the 'options' key of the hash that initializes AuditObjects # needs some more processing before being saved in {AuditStore#options}. # - # @param [Hash] + # @param [Hash] options # # @return [Hash] # def prepare_options( options ) - options['url'] = options['url'].to_s + new_options = {} - new_options = Hash.new - options.each_pair { - |key, val| - - new_options[key.to_s] = val - + options = options.to_hash + options['url'] = options['url'].to_s + options.each_pair do |key, val| case key + when 'redundant' + new_options[key.to_s] = {} + val.each do |regexp, counter| + new_options[key.to_s].merge!( regexp.to_s => counter ) + end - when 'redundant' - new_options[key.to_s] = [] - val.each { - |red| - new_options[key.to_s] << { - 'regexp' => red['regexp'].to_s, - 'count' => red['count'] - } - } + when 'exclude', 'include' + new_options[key.to_s] = [] + val.each { |regexp| new_options[key.to_s] << regexp.to_s } - when 'exclude', 'include' - new_options[key.to_s] = [] - val.each { - |regexp| - new_options[key.to_s] << regexp.to_s - } + when 'cookies' + next if !val + new_options[key.to_s] = val.inject( {} ){ |h, c| h.merge!( c.simple ) } + else + new_options[key.to_s] = val end + end - } - - return new_options + new_options end # # Parses the issues in "issue" and aggregates them # creating variations of the same attacks. @@ -257,97 +250,72 @@ # # @return [Array<Issue>] new array of Issue instances # with populated {Issue#variations} # def prepare_variations( issues ) + variation_keys = %w(injected id regexp regexp_match headers response opts) - variation_keys = [ - 'injected', - 'id', - 'regexp', - 'regexp_match', - 'headers', - 'response', - 'opts' - ] - new_issues = {} - issues.each { - |issue| + issues.each do |issue| + __id = issue.hash + new_issues[__id] ||= issue + new_issues[__id].variations ||= [] - var = issue.var || '' - - __id = issue.mod_name + - '::' + issue.elem + '::' + - var + '::' + - issue.url.split( /\?/ )[0].gsub( '//', '/' ) - - orig_url = issue.url - issue.url = issue.url.split( /\?/ )[0] - - new_issues[__id] = issue if !new_issues[__id] - new_issues[__id].variations = [] if !new_issues[__id].variations - issue.headers ||= {} issue.headers['request'] ||= {} - (issue.headers[:request] || {}).each { - |k, v| + (issue.headers[:request] || {}).each do |k, v| issue.headers['request'][k] = v.dup if v - } + end issue.headers['response'] ||= {} issue.headers['response'] = (issue.headers[:response] || '').dup issue.headers.delete( :request ) issue.headers.delete( :response ) - new_issues[__id]._hash = Digest::MD5.hexdigest( __id ) - new_issues[__id].internal_modname = + new_issues[__id].internal_modname ||= get_internal_module_name( new_issues[__id].mod_name ) new_issues[__id].variations << issue.deep_clone - variation_keys.each { - |key| - if( new_issues[__id].instance_variable_defined?( '@' + key ) ) + variation_keys.each do |key| + if new_issues[__id].instance_variable_defined?( '@' + key ) new_issues[__id].remove_instance_var( '@' + key ) end - } + end + end - } + new_issues.values.each do |i| + next if !i.variations || !i.injected + i.variations.each do |v| + v.remove_instance_var( :@variations ) rescue next + end + end issue_keys = new_issues.keys new_issues = new_issues.to_a.flatten - issue_keys.each { - |key| - new_issues.delete( key ) - } - + issue_keys.each { |key| new_issues.delete( key ) } new_issues end def get_internal_module_name( modname ) - MODULE_NAMESPACE.constants.each { - |mod| + MODULE_NAMESPACE.constants.each do |mod| klass = MODULE_NAMESPACE.const_get( mod ) return mod.to_s if klass.info[:name] == modname - } + end end # # Converts seconds to a (00:00:00) (hours:minutes:seconds) string # - # @param [String,Float,Integer] seconds + # @param [String,Float,Integer] secs seconds # # @return [String] hours:minutes:seconds # def secs_to_hms( secs ) secs = secs.to_i - return [secs/3600, secs/60 % 60, secs % 60].map { - |t| - t.to_s.rjust( 2, '0' ) - }.join(':') + [secs/3600, secs/60 % 60, secs % 60].map { |t| t.to_s.rjust( 2, '0' ) }.join( ':' ) end end end