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.
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, Element::UIInput::DOM, Element::UIForm::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_signature.
-
#audit_differential(opts = {}, &block) ⇒ Object
Audits elements using differential analysis and automatically logs results.
-
#audit_signature(payloads, opts = {}) ⇒ Object
Provides easy access to element auditing using simple signature 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)
225 226 227 |
# File 'lib/arachni/check/auditor.rb', line 225 def framework @framework end |
#page ⇒ Arachni::Page (readonly)
Returns Page object to be audited.
222 223 224 |
# File 'lib/arachni/check/auditor.rb', line 222 def page @page end |
Class Method Details
.has_timeout_candidates? ⇒ Boolean
43 44 45 |
# File 'lib/arachni/check/auditor.rb', line 43 def self.has_timeout_candidates? Element::Capabilities::Analyzable.has_timeout_candidates? end |
.included(m) ⇒ Object
69 70 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 174 175 176 177 |
# File 'lib/arachni/check/auditor.rb', line 69 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::UIInput => false, Element::UIInput::DOM => proc { audit.ui_inputs? && page.ui_inputs.any? }, Element::UIForm => false, Element::UIForm::DOM => proc { audit.ui_forms? && page.ui_forms.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
39 40 41 |
# File 'lib/arachni/check/auditor.rb', line 39 def self.reset audited.clear end |
.timeout_audit_run ⇒ Object
46 47 48 |
# File 'lib/arachni/check/auditor.rb', line 46 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_signature.
Uses #each_candidate_element to decide which elements to audit.
550 551 552 553 554 555 556 557 558 559 560 |
# File 'lib/arachni/check/auditor.rb', line 550 def audit( payloads, opts = {}, &block ) opts = OPTIONS.merge( opts ) if !block_given? audit_signature( 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.
583 584 585 586 587 588 589 |
# File 'lib/arachni/check/auditor.rb', line 583 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_signature(payloads, opts = {}) ⇒ Object
Provides easy access to element auditing using simple signature analysis and automatically logs results.
Uses #each_candidate_element to decide which elements to audit.
569 570 571 572 573 574 575 |
# File 'lib/arachni/check/auditor.rb', line 569 def audit_signature( payloads, opts = {} ) opts = OPTIONS.merge( opts ) each_candidate_element( opts[:elements] )do |e| e.signature_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.
597 598 599 600 601 602 603 |
# File 'lib/arachni/check/auditor.rb', line 597 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
54 55 56 |
# File 'lib/arachni/check/auditor.rb', line 54 def audited( id ) Auditor.audited << "#{self.class}-#{id}" end |
#audited?(id) ⇒ Bool
Returns ‘true` if audited, `false` otherwise.
65 66 67 |
# File 'lib/arachni/check/auditor.rb', line 65 def audited?( id ) Auditor.audited.include?( "#{self.class}-#{id}" ) end |
#each_candidate_dom_element(types = []) {|element| ... } ⇒ Object
508 509 510 511 512 513 514 515 516 517 518 519 520 521 522 523 524 525 526 527 528 529 530 531 532 533 534 535 536 537 538 539 540 |
# File 'lib/arachni/check/auditor.rb', line 508 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 ) when Element::UIInput::DOM.type prepare_each_dom_element( page.ui_inputs, &block ) when Element::UIForm::DOM.type prepare_each_dom_element( page.ui_forms, &block ) else fail ArgumentError, "Unknown DOM element: #{elem}" end end end |
#each_candidate_element(types = []) {|element| ... } ⇒ Object
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 489 490 491 492 |
# File 'lib/arachni/check/auditor.rb', line 457 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
235 236 237 |
# File 'lib/arachni/check/auditor.rb', line 235 def http HTTP::Client end |
#initialize(page, framework) ⇒ Object
229 230 231 232 |
# File 'lib/arachni/check/auditor.rb', line 229 def initialize( page, framework ) @page = page @framework = framework end |
#log(options) ⇒ Issue
Populates and logs an Issue.
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 343 344 345 346 |
# File 'lib/arachni/check/auditor.rb', line 283 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.
389 390 391 392 393 394 395 396 |
# File 'lib/arachni/check/auditor.rb', line 389 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.
365 366 367 368 369 370 371 372 373 374 375 376 377 378 |
# File 'lib/arachni/check/auditor.rb', line 365 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.
257 258 259 260 |
# File 'lib/arachni/check/auditor.rb', line 257 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.
272 273 274 275 |
# File 'lib/arachni/check/auditor.rb', line 272 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
179 180 181 |
# File 'lib/arachni/check/auditor.rb', line 179 def max_issues self.class.max_issues end |
#preferred ⇒ Object
401 402 403 |
# File 'lib/arachni/check/auditor.rb', line 401 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.
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 |
# File 'lib/arachni/check/auditor.rb', line 416 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`.
616 617 618 619 620 621 622 623 624 |
# File 'lib/arachni/check/auditor.rb', line 616 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.
640 641 642 643 |
# File 'lib/arachni/check/auditor.rb', line 640 def with_browser( &block ) with_browser_cluster { |cluster| cluster.with_browser( &block ) } true end |
#with_browser_cluster(&block) ⇒ Object
628 629 630 631 632 |
# File 'lib/arachni/check/auditor.rb', line 628 def with_browser_cluster( &block ) return if !browser_cluster block.call browser_cluster true end |