Class: Arachni::Browser::Javascript
- Includes:
- UI::Output, Utilities
- Defined in:
- lib/arachni/browser/javascript.rb,
lib/arachni/browser/javascript/proxy.rb,
lib/arachni/browser/javascript/dom_monitor.rb,
lib/arachni/browser/javascript/taint_tracer.rb,
lib/arachni/browser/javascript/taint_tracer/frame.rb,
lib/arachni/browser/javascript/taint_tracer/sink/base.rb,
lib/arachni/browser/javascript/taint_tracer/sink/data_flow.rb,
lib/arachni/browser/javascript/taint_tracer/sink/execution_flow.rb,
lib/arachni/browser/javascript/taint_tracer/frame/called_function.rb
Overview
Provides access to the Arachni::Browser‘s JavaScript environment, mainly helps group and organize functionality related to our custom Javascript interfaces.
Defined Under Namespace
Classes: DOMMonitor, Proxy, TaintTracer
Constant Summary collapse
- CACHE =
{ select_event_attributes: Support::Cache::LeastRecentlyPushed.new( 1_000 ) }
- TOKEN =
'arachni_js_namespace'
- SCRIPT_BASE_URL =
Returns URL to use when requesting our custom JS scripts.
'http://javascript.browser.arachni/'
- SCRIPT_LIBRARY =
Returns Filesystem directory containing the JS scripts.
"#{File.dirname( __FILE__ )}/javascript/scripts/"
- SCRIPT_SOURCES =
Dir.glob("#{SCRIPT_LIBRARY}*.js").inject({}) do |h, path| h.merge!( path => IO.read(path) ) end
- HTML_IDENTIFIERS =
['<!doctype html', '<html', '<head', '<body', '<title', '<script']
- NO_EVENTS_FOR_ELEMENTS =
Set.new([ :base, :bdo, :br, :head, :html, :iframe, :meta, :param, :script, :style, :title, :link ])
- GLOBAL_EVENTS =
Events that apply to all elements.
[ :onclick, :ondblclick, :onmousedown, :onmousemove, :onmouseout, :onmouseover, :onmouseup ]
- EVENTS_PER_ELEMENT =
Special events for each element.
{ body: [ :onload ], form: [ :onsubmit, :onreset ], # These need to be covered via Watir's API, #send_keys etc. input: [ :onselect, :onchange, :onfocus, :onblur, :onkeydown, :onkeypress, :onkeyup, :oninput ], # These need to be covered via Watir's API, #send_keys etc. textarea: [ :onselect, :onchange, :onfocus, :onblur, :onkeydown, :onkeypress, :onkeyup, :oninput ], select: [ :onchange, :onfocus, :onblur ], button: [ :onfocus, :onblur ], label: [ :onfocus, :onblur ] }
Instance Attribute Summary collapse
-
#custom_code ⇒ String
Inject custom JS code right after the initialization of the custom JS interfaces.
-
#dom_monitor ⇒ DOMMonitor
readonly
Proxy for the ‘DOMMonitor` JS interface.
-
#taint ⇒ String
Taints to look for and trace in the JS data flow.
-
#taint_tracer ⇒ TaintTracer
readonly
Proxy for the ‘TaintTracer` JS interface.
-
#token ⇒ String
Token used to namespace the injected JS code and avoid clashes.
Class Method Summary collapse
- .event_whitelist ⇒ Object
- .events ⇒ Object
-
.events_for(element) ⇒ Array<Symbol>
Events for ‘element`.
-
.select_event_attributes(attributes = {}) ⇒ Hash
‘attributes` that include Javascript.events.
Instance Method Summary collapse
-
#data_flow_sinks ⇒ Array<Sink::DataFlow>
JS data flow sink data.
-
#debug_stub(*args) ⇒ String
JS code which will call the ‘TaintTracer.debug`, browser-side JS function.
- #debugging_data ⇒ Object
-
#dom_digest ⇒ String
Digest of the current DOM tree (i.e. node names and their attributes without text-nodes).
-
#dom_elements_with_events ⇒ Array<Hash>
Information about all DOM elements, including any registered event listeners.
-
#execution_flow_sinks ⇒ Array<Sink::ExecutionFlow>
JS execution flow sink data.
-
#flush_data_flow_sinks ⇒ Array<Sink::DataFlow>
Returns and clears #data_flow_sinks.
-
#flush_execution_flow_sinks ⇒ Array<Sink::ExecutionFlow>
Returns and clears #execution_flow_sinks.
-
#has_js_initializer?(response) ⇒ Bool
‘true` if the response HTTP::Message#body contains the code for the JS environment.
- #html?(response) ⇒ Boolean
-
#initialize(browser) ⇒ Javascript
constructor
A new instance of Javascript.
- #inject(response) ⇒ Object
-
#intervals ⇒ Array<Array>
Arguments for JS ‘setInterval` calls.
- #javascript?(response) ⇒ Boolean
-
#log_data_flow_sink_stub(*args) ⇒ String
JS code which will call the ‘TaintTracer.log_data_flow_sink`, browser-side, JS function.
-
#log_execution_flow_sink_stub(*args) ⇒ String
JS code which will call the ‘TaintTracer.log_execution_flow_sink`, browser-side, JS function.
-
#ready? ⇒ Bool
‘true` if our custom JS environment has been initialized.
-
#run(script) ⇒ Object
Result of ‘script`.
-
#run_without_elements(script) ⇒ Object
Executes the given code but unwraps Watir elements.
-
#serve(request, response) ⇒ Bool
‘true` if the request corresponded to a JS file and was served, `false` otherwise.
-
#set_element_ids ⇒ Object
Sets a custom ID attribute to elements with events but without a proper ID.
-
#supported? ⇒ Bool
‘true` if there is support for our JS environment in the current page, `false` otherwise.
-
#timeouts ⇒ Array<Array>
Arguments for JS ‘setTimeout` calls.
-
#wait_till_ready ⇒ Object
Blocks until the browser page is ready.
Methods included from Utilities
#available_port, #bytes_to_kilobytes, #bytes_to_megabytes, #caller_name, #caller_path, #cookie_decode, #cookie_encode, #cookies_from_document, #cookies_from_file, #cookies_from_response, #exception_jail, #exclude_path?, #follow_protocol?, #form_decode, #form_encode, #forms_from_document, #forms_from_response, #full_and_absolute_url?, #generate_token, #get_path, #hms_to_seconds, #html_decode, #html_encode, #include_path?, #links_from_document, #links_from_response, #normalize_url, #page_from_response, #page_from_url, #parse_set_cookie, #path_in_domain?, #path_too_deep?, #port_available?, #rand_port, #random_seed, #redundant_path?, #regexp_array_match, #remove_constants, #request_parse_body, #seconds_to_hms, #skip_page?, #skip_path?, #skip_resource?, #skip_response?, #to_absolute, #uri_decode, #uri_encode, #uri_parse, #uri_parse_query, #uri_parser, #uri_rewrite
Methods included from UI::Output
#debug?, #debug_off, #debug_on, #disable_only_positives, #included, #mute, #muted?, #only_positives, #only_positives?, #print_bad, #print_debug, #print_debug_backtrace, #print_debug_level_1, #print_debug_level_2, #print_debug_level_3, #print_error, #print_error_backtrace, #print_exception, #print_info, #print_line, #print_ok, #print_status, #print_verbose, #reroute_to_file, #reroute_to_file?, reset_output_options, #unmute, #verbose?, #verbose_on
Constructor Details
#initialize(browser) ⇒ Javascript
Returns a new instance of Javascript.
163 164 165 166 167 |
# File 'lib/arachni/browser/javascript.rb', line 163 def initialize( browser ) @browser = browser @taint_tracer = TaintTracer.new( self ) @dom_monitor = DOMMonitor.new( self ) end |
Instance Attribute Details
#custom_code ⇒ String
Returns Inject custom JS code right after the initialization of the custom JS interfaces.
123 124 125 |
# File 'lib/arachni/browser/javascript.rb', line 123 def custom_code @custom_code end |
#dom_monitor ⇒ DOMMonitor (readonly)
Returns Proxy for the ‘DOMMonitor` JS interface.
127 128 129 |
# File 'lib/arachni/browser/javascript.rb', line 127 def dom_monitor @dom_monitor end |
#taint ⇒ String
Returns Taints to look for and trace in the JS data flow.
118 119 120 |
# File 'lib/arachni/browser/javascript.rb', line 118 def taint @taint end |
#taint_tracer ⇒ TaintTracer (readonly)
Returns Proxy for the ‘TaintTracer` JS interface.
131 132 133 |
# File 'lib/arachni/browser/javascript.rb', line 131 def taint_tracer @taint_tracer end |
#token ⇒ String
Returns Token used to namespace the injected JS code and avoid clashes.
114 115 116 |
# File 'lib/arachni/browser/javascript.rb', line 114 def token @token end |
Class Method Details
.event_whitelist ⇒ Object
137 138 139 |
# File 'lib/arachni/browser/javascript.rb', line 137 def self.event_whitelist @event_whitelist ||= Set.new( events.flatten.map(&:to_s) ) end |
.events ⇒ Object
133 134 135 |
# File 'lib/arachni/browser/javascript.rb', line 133 def self.events GLOBAL_EVENTS | EVENTS_PER_ELEMENT.values.flatten.uniq end |
.events_for(element) ⇒ Array<Symbol>
Returns Events for ‘element`.
145 146 147 |
# File 'lib/arachni/browser/javascript.rb', line 145 def self.events_for( element ) GLOBAL_EVENTS | EVENTS_PER_ELEMENT[element.to_sym] end |
.select_event_attributes(attributes = {}) ⇒ Hash
Returns ‘attributes` that include events.
154 155 156 157 158 159 160 |
# File 'lib/arachni/browser/javascript.rb', line 154 def self.select_event_attributes( attributes = {} ) CACHE[:select_event_attributes][attributes] ||= attributes.inject({}) do |h, (event, handler)| next h if !event_whitelist.include?( event.to_s ) h.merge!( event.to_sym => handler ) end end |
Instance Method Details
#data_flow_sinks ⇒ Array<Sink::DataFlow>
Returns JS data flow sink data.
262 263 264 265 |
# File 'lib/arachni/browser/javascript.rb', line 262 def data_flow_sinks return [] if !supported? taint_tracer.data_flow_sinks[@taint] || [] end |
#debug_stub(*args) ⇒ String
Returns JS code which will call the ‘TaintTracer.debug`, browser-side JS function.
213 214 215 |
# File 'lib/arachni/browser/javascript.rb', line 213 def debug_stub( *args ) taint_tracer.stub.function( :debug, *args ) end |
#debugging_data ⇒ Object
250 251 252 253 |
# File 'lib/arachni/browser/javascript.rb', line 250 def debugging_data return [] if !supported? taint_tracer.debugging_data end |
#dom_digest ⇒ String
Returns Digest of the current DOM tree (i.e. node names and their attributes without text-nodes).
288 289 290 291 |
# File 'lib/arachni/browser/javascript.rb', line 288 def dom_digest return '' if !supported? dom_monitor.digest end |
#dom_elements_with_events ⇒ Array<Hash>
Will not include custom events.
Returns Information about all DOM elements, including any registered event listeners.
297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 |
# File 'lib/arachni/browser/javascript.rb', line 297 def dom_elements_with_events return [] if !supported? dom_monitor.elements_with_events.map do |element| next if NO_EVENTS_FOR_ELEMENTS.include? element['tag_name'].to_sym attributes = element['attributes'] element['events'] = (element['events'].map do |event, fn| next if !(self.class.event_whitelist.include?( event ) || self.class.event_whitelist.include?( "on#{event}" )) [event.to_sym, fn] end.compact) element['events'] |= self.class.select_event_attributes( attributes ).to_a element end.compact end |
#execution_flow_sinks ⇒ Array<Sink::ExecutionFlow>
Returns JS execution flow sink data.
256 257 258 259 |
# File 'lib/arachni/browser/javascript.rb', line 256 def execution_flow_sinks return [] if !supported? taint_tracer.execution_flow_sinks end |
#flush_data_flow_sinks ⇒ Array<Sink::DataFlow>
Returns and clears #data_flow_sinks.
274 275 276 277 |
# File 'lib/arachni/browser/javascript.rb', line 274 def flush_data_flow_sinks return [] if !supported? taint_tracer.flush_data_flow_sinks[@taint] || [] end |
#flush_execution_flow_sinks ⇒ Array<Sink::ExecutionFlow>
Returns and clears #execution_flow_sinks.
268 269 270 271 |
# File 'lib/arachni/browser/javascript.rb', line 268 def flush_execution_flow_sinks return [] if !supported? taint_tracer.flush_execution_flow_sinks end |
#has_js_initializer?(response) ⇒ Bool
Returns ‘true` if the response HTTP::Message#body contains the code for the JS environment.
187 188 189 |
# File 'lib/arachni/browser/javascript.rb', line 187 def has_js_initializer?( response ) response.body.include? js_initialization_signal end |
#html?(response) ⇒ Boolean
435 436 437 438 439 440 441 442 443 444 445 446 447 448 449 450 451 452 453 454 455 456 457 458 459 460 461 462 463 464 465 466 467 468 |
# File 'lib/arachni/browser/javascript.rb', line 435 def html?( response ) return false if response.body.empty? # We only care about HTML. return false if !response.headers.content_type.to_s.downcase.start_with?( 'text/html' ) # Let's check that the response at least looks like it contains HTML # code of interest. body = response.body.downcase return false if !HTML_IDENTIFIERS.find { |tag| body.include? tag.downcase } # The last check isn't fool-proof, so don't do it when loading the page # for the first time, but only when the page loads stuff via AJAX and whatnot. # # Well, we can be pretty sure that the root page will be HTML anyways. return true if @browser.last_url == response.url # Finally, verify that we're really working with markup (hopefully HTML) # and that the previous checks weren't just flukes matching some other # kind of document. # # For example, it may have been JSON with the wrong content-type that # includes HTML -- it happens. begin return false if Nokogiri::XML( response.body ).children.empty? rescue => e print_debug "Does not look like HTML: #{response.url}" print_debug "\n#{response.body}" print_debug_exception e return false end true end |
#inject(response) ⇒ Object
Will update the ‘Content-Length` header field.
361 362 363 364 365 366 367 368 369 370 371 372 373 374 375 376 377 378 379 380 381 382 383 384 385 386 387 388 389 390 391 392 393 394 395 396 397 398 399 400 401 402 403 404 405 406 407 408 409 410 411 412 413 414 415 416 417 418 419 420 421 422 423 424 425 426 427 428 429 |
# File 'lib/arachni/browser/javascript.rb', line 361 def inject( response ) # Don't intercept our own stuff! return if response.url.start_with?( SCRIPT_BASE_URL ) # If it's a JS file, update our JS interfaces in case it has stuff that # can be tracked. # # This is necessary because new files can be required dynamically. if javascript?( response ) response.body = <<-EOCODE #{js_comment} #{taint_tracer.stub.function( :update_tracers )}; #{dom_monitor.stub.function( :update_trackers )}; #{response.body}; EOCODE # Already has the JS initializer, so it's an HTML response; just update # taints and custom code. elsif has_js_initializer?( response ) body = response.body.dup update_taints( body ) update_custom_code( body ) response.body = body elsif html?( response ) body = response.body.dup # Perform an update before each script. body.gsub!( /<script.*?>/i, "\\0\n #{js_comment} #{@taint_tracer.stub.function( :update_tracers )}; #{@dom_monitor.stub.function( :update_trackers )};\n\n" ) # Perform an update after each script. body.gsub!( /<\/script>/i, "\\0\n<script type=\"text/javascript\">" << "#{@taint_tracer.stub.function( :update_tracers )};" << "#{@dom_monitor.stub.function( :update_trackers )};" << "</script> #{html_comment}\n" ) # Include and initialize our JS interfaces. response.body = <<-EOHTML <script src="#{script_url_for( :taint_tracer )}"></script> #{html_comment} <script src="#{script_url_for( :dom_monitor )}"></script> #{html_comment} <script> #{wrapped_taint_tracer_initializer} #{js_initialization_signal}; #{wrapped_custom_code} </script> #{html_comment} #{body} EOHTML end response.headers['content-length'] = response.body.size true end |
#intervals ⇒ Array<Array>
Returns Arguments for JS ‘setInterval` calls.
327 328 329 330 |
# File 'lib/arachni/browser/javascript.rb', line 327 def intervals return [] if !supported? dom_monitor.intervals end |
#javascript?(response) ⇒ Boolean
431 432 433 |
# File 'lib/arachni/browser/javascript.rb', line 431 def javascript?( response ) response.headers.content_type.to_s.downcase.include?( 'javascript' ) end |
#log_data_flow_sink_stub(*args) ⇒ String
Returns JS code which will call the ‘TaintTracer.log_data_flow_sink`, browser-side, JS function.
207 208 209 |
# File 'lib/arachni/browser/javascript.rb', line 207 def log_data_flow_sink_stub( *args ) taint_tracer.stub.function( :log_data_flow_sink, *args ) end |
#log_execution_flow_sink_stub(*args) ⇒ String
Returns JS code which will call the ‘TaintTracer.log_execution_flow_sink`, browser-side, JS function.
200 201 202 |
# File 'lib/arachni/browser/javascript.rb', line 200 def log_execution_flow_sink_stub( *args ) taint_tracer.stub.function( :log_execution_flow_sink, *args ) end |
#ready? ⇒ Bool
Returns ‘true` if our custom JS environment has been initialized.
225 226 227 |
# File 'lib/arachni/browser/javascript.rb', line 225 def ready? !!run( "return window._#{token}" ) rescue false end |
#run(script) ⇒ Object
Returns Result of ‘script`.
234 235 236 |
# File 'lib/arachni/browser/javascript.rb', line 234 def run( script ) @browser.watir.execute_script script end |
#run_without_elements(script) ⇒ Object
Executes the given code but unwraps Watir elements.
245 246 247 |
# File 'lib/arachni/browser/javascript.rb', line 245 def run_without_elements( script ) unwrap_elements run( script ) end |
#serve(request, response) ⇒ Bool
Returns ‘true` if the request corresponded to a JS file and was served, `false` otherwise.
343 344 345 346 347 348 349 350 351 352 |
# File 'lib/arachni/browser/javascript.rb', line 343 def serve( request, response ) return false if !request.url.start_with?( SCRIPT_BASE_URL ) || !(script = read_script( request.parsed_url.path )) response.code = 200 response.body = script response.headers['content-type'] = 'text/javascript' response.headers['content-length'] = script.bytesize true end |
#set_element_ids ⇒ Object
Sets a custom ID attribute to elements with events but without a proper ID.
280 281 282 283 |
# File 'lib/arachni/browser/javascript.rb', line 280 def set_element_ids return '' if !supported? dom_monitor.setElementIds end |
#supported? ⇒ Bool
Returns ‘true` if there is support for our JS environment in the current page, `false` otherwise.
174 175 176 177 178 179 |
# File 'lib/arachni/browser/javascript.rb', line 174 def supported? # We won't have a response if the browser was steered towards an # out-of-scope resource. response = @browser.response response && has_js_initializer?( response ) end |