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
- ELEMENTS_WITH_INPUTS =
Non-DOM auditable elements.
[ Element::Link, Element::Form, Element::Cookie, Element::Header, Element::LinkTemplate, Element::JSON, Element::XML ]
- DOM_ELEMENTS_WITH_INPUTS =
Auditable DOM elements.
[ Element::Link::DOM, Element::Form::DOM, Element::Cookie::DOM, Element::LinkTemplate::DOM ]
- 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: ELEMENTS_WITH_INPUTS, dom_elements: DOM_ELEMENTS_WITH_INPUTS, # 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) ⇒ Issue
(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)
221 222 223 |
# File 'lib/arachni/check/auditor.rb', line 221 def framework @framework end |
#page ⇒ Arachni::Page (readonly)
Returns Page object to be audited.
218 219 220 |
# File 'lib/arachni/check/auditor.rb', line 218 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 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 |
# 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 # @param [Element::Base, Array<Element::Base>] restrict_to_elements # Element types to check for. # # @return [Bool] def self.check?( page, restrict_to_elements = nil, ignore_dom_depth = false ) return false if issue_limit_reached? return true if elements.empty? audit = Arachni::Options.audit restrict_to_elements = [restrict_to_elements].flatten.compact # We use procs to make the decisions to avoid loading the page # element caches unless it's absolutely necessary. # # Also, it's better to audit Form & Cookie DOM elements only # after the page has gone through the browser, because then # we'll have some context in the metadata which can help us # optimize DOM audits. { 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 { (ignore_dom_depth || page.dom.depth > 0) && audit.form_doms? && page.has_script? && !!page.forms.find(&:dom) }, Element::Cookie => proc { audit. && page..any? }, Element::Cookie::DOM => proc { (ignore_dom_depth || page.dom.depth > 0) && 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::JSON => proc { audit.jsons? && page.jsons.find { |e| e.inputs.any? } }, Element::XML => proc { audit.xmls? && page.xmls.find { |e| e.inputs.any? } }, Element::Body => !page.body.empty?, Element::GenericDOM => page.has_script?, Element::Path => true, Element::Server => true }.each do |type, decider| next if restrict_to_elements.any? && !restrict_to_elements.include?( type ) 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.
540 541 542 543 544 545 546 547 548 549 550 |
# File 'lib/arachni/check/auditor.rb', line 540 def audit( payloads, opts = {}, &block ) opts = OPTIONS.merge( opts ) if !block_given? audit_taint( payloads, opts ) else each_candidate_element( opts[:elements] ) do |e| e.audit( payloads, opts, &block ) audited( e.coverage_id ) end 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.
573 574 575 576 577 578 579 |
# File 'lib/arachni/check/auditor.rb', line 573 def audit_differential( opts = {}, &block ) opts = OPTIONS.merge( opts ) each_candidate_element( opts[:elements] ) do |e| e.differential_analysis( opts, &block ) audited( e.coverage_id ) end 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.
559 560 561 562 563 564 565 |
# File 'lib/arachni/check/auditor.rb', line 559 def audit_taint( payloads, opts = {} ) opts = OPTIONS.merge( opts ) each_candidate_element( opts[:elements] )do |e| e.taint_analysis( payloads, opts ) audited( e.coverage_id ) end 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.
587 588 589 590 591 592 593 |
# File 'lib/arachni/check/auditor.rb', line 587 def audit_timeout( payloads, opts = {} ) opts = OPTIONS.merge( opts ) each_candidate_element( opts[:elements] ) do |e| e.timeout_analysis( payloads, opts ) audited( e.coverage_id ) end 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
504 505 506 507 508 509 510 511 512 513 514 515 516 517 518 519 520 521 522 523 524 525 526 527 528 529 530 |
# File 'lib/arachni/check/auditor.rb', line 504 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
453 454 455 456 457 458 459 460 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 488 |
# File 'lib/arachni/check/auditor.rb', line 453 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 !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 ) when Element::JSON.type prepare_each_element( page.jsons, &block ) when Element::XML.type prepare_each_element( page.xmls, &block ) else fail ArgumentError, "Unknown element: #{elem}" end end end |
#http ⇒ HTTP::Client
231 232 233 |
# File 'lib/arachni/check/auditor.rb', line 231 def http HTTP::Client end |
#initialize(page, framework) ⇒ Object
225 226 227 228 |
# File 'lib/arachni/check/auditor.rb', line 225 def initialize( page, framework ) @page = page @framework = framework end |
#log(options) ⇒ Issue
Populates and logs an Issue.
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 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 |
# File 'lib/arachni/check/auditor.rb', line 279 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.
385 386 387 388 389 390 391 392 |
# File 'lib/arachni/check/auditor.rb', line 385 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) ⇒ Issue #log_remote_file(page, silent = false) ⇒ Issue Also known as: log_remote_directory
Logs the existence of a remote file as an issue.
361 362 363 364 365 366 367 368 369 370 371 372 373 374 |
# File 'lib/arachni/check/auditor.rb', line 361 def log_remote_file( page_or_response, silent = false ) page = page_or_response.is_a?( Page ) ? page_or_response : page_or_response.to_page issue = log_issue( vector: Element::Server.new( page.url ), proof: page.response.status_line, page: page ) print_ok( "Found #{page.url}" ) if !silent issue 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.
253 254 255 256 |
# File 'lib/arachni/check/auditor.rb', line 253 def log_remote_file_if_exists( url, silent = false, &block ) @server ||= Element::Server.new( page.url ).tap { |s| s.auditor = self } @server.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.
268 269 270 271 |
# File 'lib/arachni/check/auditor.rb', line 268 def match_and_log( patterns, &block ) @body ||= Element::Body.new( self.page.url ).tap { |b| b.auditor = self } @body.match_and_log( patterns, &block ) end |
#max_issues ⇒ Object
175 176 177 |
# File 'lib/arachni/check/auditor.rb', line 175 def max_issues self.class.max_issues end |
#preferred ⇒ Object
397 398 399 |
# File 'lib/arachni/check/auditor.rb', line 397 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.
412 413 414 415 416 417 418 419 420 421 422 423 424 425 426 427 428 429 430 431 432 433 434 435 436 437 |
# File 'lib/arachni/check/auditor.rb', line 412 def skip?( element ) # This method also gets called from Auditable#audit to check mutations, # don't touch these, we're filtering at a higher level here, otherwise # we might mess up the audit. return true if !element.mutation? && audited?( element.coverage_id ) 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) # No point in doing the following heavy deduplication check if there # are no issues logged to begin with. next if klass.issue_counter == 0 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`.
606 607 608 609 610 611 612 613 614 |
# File 'lib/arachni/check/auditor.rb', line 606 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.
630 631 632 633 |
# File 'lib/arachni/check/auditor.rb', line 630 def with_browser( &block ) with_browser_cluster { |cluster| cluster.with_browser( &block ) } true end |
#with_browser_cluster(&block) ⇒ Object
618 619 620 621 622 |
# File 'lib/arachni/check/auditor.rb', line 618 def with_browser_cluster( &block ) return if !browser_cluster block.call browser_cluster true end |