Module: Arachni::Check::Auditor
- Included in:
- Base
- Defined in:
- lib/arachni/check/auditor.rb
Overview
Included by Base and provides helper audit methods to all checks.
There are 3 main types of audit and analysis techniques available:
It should be noted that actual analysis takes place at the element level, and to be more specific, the Element::Capabilities::Auditable element level.
It also provides:
-
Discovery helpers for checking and logging the existence of remote files.
-
Pattern matching helpers for checking and logging the existence of strings in responses or in the body of the page that’s being audited.
-
General Issue logging helpers.
Constant Summary collapse
- Format =
Holds constant bitfields that describe the preferred formatting of injection strings.
Element::Capabilities::Mutable::Format
- OPTIONS =
Default audit options.
{ # Elements to audit. # # If no elements have been passed to audit methods, candidates will be # determined by {#each_candidate_element}. elements: [Element::Link, Element::Form, Element::Cookie, Element::Header, Element::Body, Element::LinkTemplate], dom_elements: [Element::Link::DOM, Element::Form::DOM, Element::Cookie::DOM, Element::LinkTemplate::DOM], # If set to `true` the HTTP response will be analyzed for new elements. # Be careful when enabling it, there'll be a performance penalty. # # If set to `false`, no training is going to occur. # # If set to `nil`, when the Auditor submits a form with original or # sample values this option will be overridden to `true` train: nil }
Instance Attribute Summary collapse
- #framework ⇒ Arachni::Framework readonly
-
#page ⇒ Arachni::Page
readonly
Page object to be audited.
Class Method Summary collapse
- .has_timeout_candidates? ⇒ Boolean
- .included(m) ⇒ Object
- .reset ⇒ Object
- .timeout_audit_run ⇒ Object
Instance Method Summary collapse
-
#audit(payloads, opts = {}, &block) ⇒ Object
If a block has been provided it calls Element::Capabilities::Auditable#audit for every element, otherwise, it defaults to #audit_taint.
-
#audit_differential(opts = {}, &block) ⇒ Object
Audits elements using differential analysis and automatically logs results.
-
#audit_taint(payloads, opts = {}) ⇒ Object
Provides easy access to element auditing using simple taint analysis and automatically logs results.
-
#audit_timeout(payloads, opts = {}) ⇒ Object
Audits elements using timing attacks and automatically logs results.
- #audited(id) ⇒ Object
-
#audited?(id) ⇒ Bool
‘true` if audited, `false` otherwise.
-
#each_candidate_dom_element(types = []) {|element| ... } ⇒ Object
Passes each element prepared for audit to the block.
-
#each_candidate_element(types = []) {|element| ... } ⇒ Object
Passes each element prepared for audit to the block.
- #http ⇒ HTTP::Client
- #initialize(page, framework) ⇒ Object
-
#log(options) ⇒ Issue
Populates and logs an Issue.
-
#log_issue(options) ⇒ Issue
Helper method for issue logging.
-
#log_remote_file(page_or_response, silent = false) ⇒ Object
(also: #log_remote_directory)
Logs the existence of a remote file as an issue.
-
#log_remote_file_if_exists(url, silent = false, &block) ⇒ Object
(also: #log_remote_directory_if_exists)
Logs a remote file or directory if it exists.
-
#match_and_log(patterns, &block) ⇒ Object
Matches an array of regular expressions against a string and logs the result as an issue.
- #max_issues ⇒ Object
- #preferred ⇒ Object abstract
-
#skip?(element) ⇒ Boolean
This is called right before an Element is audited and is used to determine whether to skip it or not.
-
#trace_taint(resource, options = {}, &block) ⇒ Object
Traces the taint in the given ‘resource` and passes each page to the `block`.
- #with_browser(&block) ⇒ Object
- #with_browser_cluster(&block) ⇒ Object
Instance Attribute Details
#framework ⇒ Arachni::Framework (readonly)
196 197 198 |
# File 'lib/arachni/check/auditor.rb', line 196 def framework @framework end |
#page ⇒ Arachni::Page (readonly)
Returns Page object to be audited.
193 194 195 |
# File 'lib/arachni/check/auditor.rb', line 193 def page @page end |
Class Method Details
.has_timeout_candidates? ⇒ Boolean
45 46 47 |
# File 'lib/arachni/check/auditor.rb', line 45 def self.has_timeout_candidates? Element::Capabilities::Analyzable.has_timeout_candidates? end |
.included(m) ⇒ Object
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 |
# File 'lib/arachni/check/auditor.rb', line 71 def self.included( m ) m.class_eval do # Determines whether or not to run the check against the given page # depending on which elements exist in the page, which elements the # check is configured to audit and user options. # # @param [Page] page # # @return [Bool] def self.check?( page ) return false if issue_limit_reached? return true if elements.empty? audit = Arachni::Options.audit { # We use procs to make the decision, to avoid loading the page # element caches unless it's absolutely necessary. Element::Link => proc { audit.links? && !!page.links.find { |e| e.inputs.any? } }, Element::Link::DOM => proc { audit.link_doms? && !!page.links.find(&:dom) }, Element::Form => proc { audit.forms? && !!page.forms.find { |e| e.inputs.any? } }, Element::Form::DOM => proc { audit.form_doms? && page.has_script? && !!page.forms.find(&:dom) }, Element::Cookie => proc { audit. && page..any? }, Element::Cookie::DOM => proc { audit. && page.has_script? && page..any? }, Element::Header => proc { audit.headers? && page.headers.any? }, Element::LinkTemplate => proc { audit.link_templates? && page.link_templates.find { |e| e.inputs.any? } }, Element::LinkTemplate::DOM => proc { audit.link_template_doms? && !!page.link_templates.find(&:dom) }, Element::Body => !page.body.empty?, Element::GenericDOM => page.has_script?, Element::Path => true, Element::Server => true }.each do |type, decider| return true if elements.include?( type ) && (decider.is_a?( Proc ) ? decider.call : decider) end false end def self.issue_counter @issue_counter ||= 0 end def self.issue_counter=( int ) @issue_counter = int end def increment_issue_counter self.class.issue_counter += 1 end def issue_limit_reached?( count = max_issues ) self.class.issue_limit_reached?( count ) end def self.issue_limit_reached?( count = max_issues ) issue_counter >= count if !count.nil? end def self.max_issues info[:max_issues] end # Helper method for creating an issue. # # @param [Hash] options # {Issue} options. def self.create_issue( ) check_info = self.info.dup check_info.delete( :issue ) check_info[:shortname] = self.shortname issue_data = self.info[:issue].merge( check: check_info ).merge( ) Issue.new( issue_data ) end end end |
.reset ⇒ Object
41 42 43 |
# File 'lib/arachni/check/auditor.rb', line 41 def self.reset audited.clear end |
.timeout_audit_run ⇒ Object
48 49 50 |
# File 'lib/arachni/check/auditor.rb', line 48 def self.timeout_audit_run Element::Capabilities::Analyzable.timeout_audit_run end |
Instance Method Details
#audit(payloads, opts = {}, &block) ⇒ Object
If a block has been provided it calls Element::Capabilities::Auditable#audit for every element, otherwise, it defaults to #audit_taint.
Uses #each_candidate_element to decide which elements to audit.
497 498 499 500 501 502 503 504 |
# File 'lib/arachni/check/auditor.rb', line 497 def audit( payloads, opts = {}, &block ) opts = OPTIONS.merge( opts ) if !block_given? audit_taint( payloads, opts ) else each_candidate_element( opts[:elements] ) { |e| e.audit( payloads, opts, &block ) } end end |
#audit_differential(opts = {}, &block) ⇒ Object
Audits elements using differential analysis and automatically logs results.
Uses #each_candidate_element to decide which elements to audit.
524 525 526 527 |
# File 'lib/arachni/check/auditor.rb', line 524 def audit_differential( opts = {}, &block ) opts = OPTIONS.merge( opts ) each_candidate_element( opts[:elements] ) { |e| e.differential_analysis( opts, &block ) } end |
#audit_taint(payloads, opts = {}) ⇒ Object
Provides easy access to element auditing using simple taint analysis and automatically logs results.
Uses #each_candidate_element to decide which elements to audit.
513 514 515 516 |
# File 'lib/arachni/check/auditor.rb', line 513 def audit_taint( payloads, opts = {} ) opts = OPTIONS.merge( opts ) each_candidate_element( opts[:elements] ) { |e| e.taint_analysis( payloads, opts ) } end |
#audit_timeout(payloads, opts = {}) ⇒ Object
Audits elements using timing attacks and automatically logs results.
Uses #each_candidate_element to decide which elements to audit.
535 536 537 538 |
# File 'lib/arachni/check/auditor.rb', line 535 def audit_timeout( payloads, opts = {} ) opts = OPTIONS.merge( opts ) each_candidate_element( opts[:elements] ) { |e| e.timeout_analysis( payloads, opts ) } end |
#audited(id) ⇒ Object
56 57 58 |
# File 'lib/arachni/check/auditor.rb', line 56 def audited( id ) Auditor.audited << "#{self.class}-#{id}" end |
#audited?(id) ⇒ Bool
Returns ‘true` if audited, `false` otherwise.
67 68 69 |
# File 'lib/arachni/check/auditor.rb', line 67 def audited?( id ) Auditor.audited.include?( "#{self.class}-#{id}" ) end |
#each_candidate_dom_element(types = []) {|element| ... } ⇒ Object
461 462 463 464 465 466 467 468 469 470 471 472 473 474 475 476 477 478 479 480 481 482 483 484 485 486 487 |
# File 'lib/arachni/check/auditor.rb', line 461 def each_candidate_dom_element( types = [], &block ) types = self.class.info[:elements] if types.empty? types = OPTIONS[:dom_elements] if types.empty? types.each do |elem| elem = elem.type next if !Options.audit.elements?( elem.to_s.gsub( '_dom', '' ) ) case elem when Element::Link::DOM.type prepare_each_dom_element( page.links, &block ) when Element::Form::DOM.type prepare_each_dom_element( page.forms, &block ) when Element::Cookie::DOM.type prepare_each_dom_element( page., &block ) when Element::LinkTemplate::DOM.type prepare_each_dom_element( page.link_templates, &block ) else fail ArgumentError, "Unknown DOM element: #{elem}" end end end |
#each_candidate_element(types = []) {|element| ... } ⇒ Object
415 416 417 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 |
# File 'lib/arachni/check/auditor.rb', line 415 def each_candidate_element( types = [], &block ) types = self.class.info[:elements] if types.empty? types = OPTIONS[:elements] if types.empty? types.each do |elem| elem = elem.type next if elem == Element::Body.type next if !Options.audit.elements?( elem ) case elem when Element::Link.type prepare_each_element( page.links, &block ) when Element::Form.type prepare_each_element( page.forms, &block ) when Element::Cookie.type prepare_each_element(page., &block ) when Element::Header.type prepare_each_element( page.headers, &block ) when Element::LinkTemplate.type prepare_each_element( page.link_templates, &block ) else fail ArgumentError, "Unknown element: #{elem}" end end end |
#http ⇒ HTTP::Client
206 207 208 |
# File 'lib/arachni/check/auditor.rb', line 206 def http HTTP::Client end |
#initialize(page, framework) ⇒ Object
200 201 202 203 |
# File 'lib/arachni/check/auditor.rb', line 200 def initialize( page, framework ) @page = page @framework = framework end |
#log(options) ⇒ Issue
Populates and logs an Issue.
254 255 256 257 258 259 260 261 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 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 |
# File 'lib/arachni/check/auditor.rb', line 254 def log( ) = .dup vector = [:vector] = vector.respond_to?( :audit_options ) ? vector. : {} if [:response] page = .delete(:response).to_page elsif [:page] page = .delete(:page) else page = self.page end msg = "In #{vector.type}" active = vector.respond_to?( :affected_input_name ) && vector.affected_input_name if active msg << " input '#{vector.affected_input_name}'" elsif vector.respond_to?( :inputs ) msg << " with inputs '#{vector.inputs.keys.join(', ')}'" end print_ok "#{msg} with action #{vector.action}" if verbose? if active print_verbose "Injected: #{vector.affected_input_value.inspect}" end if [:signature] print_verbose "Signature: #{[:signature]}" end if [:proof] print_verbose "Proof: #{[:proof]}" end if page.dom.transitions.any? print_verbose 'DOM transitions:' page.dom.print_transitions( method(:print_verbose), ' ' ) end if !(request_dump = page.request.to_s).empty? print_verbose "Request: \n#{request_dump}" end print_verbose( '---------' ) if only_positives? end # Platform identification by vulnerability. platform_type = nil if (platform = (.delete(:platform) || [:platform])) Platform::Manager[vector.action] << platform if Options.fingerprint? platform_type = Platform::Manager[vector.action].find_type( platform ) end log_issue(.merge( platform_name: platform, platform_type: platform_type, page: page )) end |
#log_issue(options) ⇒ Issue
Helper method for issue logging.
355 356 357 358 359 360 361 362 |
# File 'lib/arachni/check/auditor.rb', line 355 def log_issue( ) return if issue_limit_reached? self.class.issue_counter += 1 issue = self.class.create_issue( .merge( referring_page: self.page ) ) Data.issues << issue issue end |
#log_remote_file(response, silent = false) ⇒ Object #log_remote_file(page, silent = false) ⇒ Object Also known as: log_remote_directory
Logs the existence of a remote file as an issue.
334 335 336 337 338 339 340 341 342 343 344 |
# File 'lib/arachni/check/auditor.rb', line 334 def log_remote_file( page_or_response, silent = false ) page = page_or_response.is_a?( Page ) ? page_or_response : page_or_response.to_page log_issue( vector: Element::Server.new( page.url ), page: page ) print_ok( "Found #{page.url}" ) if !silent end |
#log_remote_file_if_exists(url, silent = false, &block) ⇒ Object Also known as: log_remote_directory_if_exists
Ignores custom 404 responses.
Logs a remote file or directory if it exists.
228 229 230 231 |
# File 'lib/arachni/check/auditor.rb', line 228 def log_remote_file_if_exists( url, silent = false, &block ) Element::Server.new( page.url ).tap { |s| s.auditor = self }. log_remote_file_if_exists( url, silent, &block ) end |
#match_and_log(patterns, &block) ⇒ Object
Matches an array of regular expressions against a string and logs the result as an issue.
243 244 245 246 |
# File 'lib/arachni/check/auditor.rb', line 243 def match_and_log( patterns, &block ) Element::Body.new( self.page.url ).tap { |b| b.auditor = self }. match_and_log( patterns, &block ) end |
#max_issues ⇒ Object
159 160 161 |
# File 'lib/arachni/check/auditor.rb', line 159 def max_issues self.class.max_issues end |
#preferred ⇒ Object
367 368 369 |
# File 'lib/arachni/check/auditor.rb', line 367 def preferred [] end |
#skip?(element) ⇒ Boolean
This is called right before an Element is audited and is used to determine whether to skip it or not.
Running checks can override this as they wish but at their own peril.
382 383 384 385 386 387 388 389 390 391 392 393 394 395 396 397 398 399 |
# File 'lib/arachni/check/auditor.rb', line 382 def skip?( element ) return true if !page.audit_element?( element ) # Don't audit elements which have been already logged as vulnerable # either by us or preferred checks. (preferred | [shortname]).each do |check| next if !framework.checks.include?( check ) klass = framework.checks[check] next if !klass.info.include?(:issue) if Data.issues.include?( klass.create_issue( vector: element ) ) return true end end false end |
#trace_taint(resource, options = {}, &block) ⇒ Object
Traces the taint in the given ‘resource` and passes each page to the `block`.
551 552 553 554 555 556 557 558 559 |
# File 'lib/arachni/check/auditor.rb', line 551 def trace_taint( resource, = {}, &block ) with_browser_cluster do |cluster| cluster.trace_taint( resource, ) do |result| # Mark the job as done and abort further analysis if the block # returns true. cluster.job_done( result.job ) if block.call( result.page ) end end end |
#with_browser(&block) ⇒ Object
Operates in non-blocking mode.
575 576 577 578 |
# File 'lib/arachni/check/auditor.rb', line 575 def with_browser( &block ) with_browser_cluster { |cluster| cluster.with_browser( &block ) } true end |
#with_browser_cluster(&block) ⇒ Object
563 564 565 566 567 |
# File 'lib/arachni/check/auditor.rb', line 563 def with_browser_cluster( &block ) return if !browser_cluster block.call browser_cluster true end |