Class: Arachni::Browser::Javascript

Inherits:
Object
  • Object
show all
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.

Author:

Defined Under Namespace

Classes: DOMMonitor, Proxy, TaintTracer

Constant Summary collapse

SCRIPT_BASE_URL =

Returns URL to use when requesting our custom JS scripts.

Returns:

  • (String)

    URL to use when requesting our custom JS scripts.

'http://javascript.browser.arachni/'
SCRIPT_LIBRARY =

Returns Filesystem directory containing the JS scripts.

Returns:

  • (String)

    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
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

Class Method Summary collapse

Instance Method Summary collapse

Methods included from Utilities

#available_port, #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.

Parameters:



141
142
143
144
145
# File 'lib/arachni/browser/javascript.rb', line 141

def initialize( browser )
    @browser      = browser
    @taint_tracer = TaintTracer.new( self )
    @dom_monitor  = DOMMonitor.new( self )
end

Instance Attribute Details

#custom_codeString

Returns Inject custom JS code right after the initialization of the custom JS interfaces.

Returns:

  • (String)

    Inject custom JS code right after the initialization of the custom JS interfaces.



115
116
117
# File 'lib/arachni/browser/javascript.rb', line 115

def custom_code
  @custom_code
end

#dom_monitorDOMMonitor (readonly)

Returns Proxy for the ‘DOMMonitor` JS interface.

Returns:



119
120
121
# File 'lib/arachni/browser/javascript.rb', line 119

def dom_monitor
  @dom_monitor
end

#taintString

Returns Taints to look for and trace in the JS data flow.

Returns:

  • (String)

    Taints to look for and trace in the JS data flow.



110
111
112
# File 'lib/arachni/browser/javascript.rb', line 110

def taint
  @taint
end

#taint_tracerTaintTracer (readonly)

Returns Proxy for the ‘TaintTracer` JS interface.

Returns:



123
124
125
# File 'lib/arachni/browser/javascript.rb', line 123

def taint_tracer
  @taint_tracer
end

#tokenString

Returns Token used to namespace the injected JS code and avoid clashes.

Returns:

  • (String)

    Token used to namespace the injected JS code and avoid clashes.



106
107
108
# File 'lib/arachni/browser/javascript.rb', line 106

def token
  @token
end

Class Method Details

.eventsObject



125
126
127
# File 'lib/arachni/browser/javascript.rb', line 125

def self.events
    GLOBAL_EVENTS | EVENTS_PER_ELEMENT.values.flatten.uniq
end

.select_event_attributes(attributes = {}) ⇒ Hash

Returns ‘attributes` that include events.

Parameters:

  • attributes (Hash) (defaults to: {})

    Element attributes.

Returns:



134
135
136
137
138
# File 'lib/arachni/browser/javascript.rb', line 134

def self.select_event_attributes( attributes = {} )
    attributes = attributes.my_stringify
    Hash[(self.events.flatten.map(&:to_s) & attributes.keys).
        map { |event| [event.to_sym, attributes[event]] }]
end

Instance Method Details

#data_flow_sinksArray<Sink::DataFlow>

Returns JS data flow sink data.

Returns:

  • (Array<Sink::DataFlow>)

    JS data flow sink data.



240
241
242
243
# File 'lib/arachni/browser/javascript.rb', line 240

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.

Returns:

  • (String)

    JS code which will call the ‘TaintTracer.debug`, browser-side JS function.



191
192
193
# File 'lib/arachni/browser/javascript.rb', line 191

def debug_stub( *args )
    taint_tracer.stub.function( :debug, *args )
end

#debugging_dataObject



228
229
230
231
# File 'lib/arachni/browser/javascript.rb', line 228

def debugging_data
    return [] if !supported?
    taint_tracer.debugging_data
end

#dom_digestString

Returns Digest of the current DOM tree (i.e. node names and their attributes without text-nodes).

Returns:

  • (String)

    Digest of the current DOM tree (i.e. node names and their attributes without text-nodes).



266
267
268
269
# File 'lib/arachni/browser/javascript.rb', line 266

def dom_digest
    return '' if !supported?
    dom_monitor.digest
end

#dom_elements_with_eventsArray<Hash>

Returns Information about all DOM elements, including any registered event listeners.

Returns:

  • (Array<Hash>)

    Information about all DOM elements, including any registered event listeners.



273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
# File 'lib/arachni/browser/javascript.rb', line 273

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 { |event, fn| [event.to_sym, fn] } |
                (self.class.events.flatten.map(&:to_s) & attributes.keys).
                    map { |event| [event.to_sym, attributes[event]] }

        element
    end.compact
end

#execution_flow_sinksArray<Sink::ExecutionFlow>

Returns JS execution flow sink data.

Returns:

  • (Array<Sink::ExecutionFlow>)

    JS execution flow sink data.



234
235
236
237
# File 'lib/arachni/browser/javascript.rb', line 234

def execution_flow_sinks
    return [] if !supported?
    taint_tracer.execution_flow_sinks
end

#flush_data_flow_sinksArray<Sink::DataFlow>

Returns and clears #data_flow_sinks.

Returns:



252
253
254
255
# File 'lib/arachni/browser/javascript.rb', line 252

def flush_data_flow_sinks
    return [] if !supported?
    taint_tracer.flush_data_flow_sinks[@taint] || []
end

#flush_execution_flow_sinksArray<Sink::ExecutionFlow>

Returns and clears #execution_flow_sinks.

Returns:



246
247
248
249
# File 'lib/arachni/browser/javascript.rb', line 246

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.

Parameters:

Returns:



165
166
167
# File 'lib/arachni/browser/javascript.rb', line 165

def has_js_initializer?( response )
    response.body.include? js_initialization_signal
end

#inject(response) ⇒ Bool

Note:

Will update the ‘Content-Length` header field.

Returns ‘true` if injection was performed, `false` otherwise (in case our code is already present).

Parameters:

  • response (HTTP::Response)

    Installs our custom JS interfaces in the given ‘response`.

Returns:

  • (Bool)

    ‘true` if injection was performed, `false` otherwise (in case our code is already present).

See Also:



336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
# File 'lib/arachni/browser/javascript.rb', line 336

def inject( response )
    return false if has_js_initializer?( response )

    body = response.body.dup

    # Schedule a tracer update at the beginning of each script block in order
    # to put our hooks into any newly introduced functions.
    #
    # The fact that our update call seems to be taking place before any
    # functions get the chance to be defined doesn't seem to matter.
    body.gsub!(
        /<script(.*?)>/i,
        "\\0\n#{@taint_tracer.stub.function( :update_tracers )}; // Injected by #{self.class}\n"
    )

    # Also perform an update after each script block, this is for external
    # scripts.
    body.gsub!(
        /<\/script>/i,
        "\\0\n<script type=\"text/javascript\">#{@taint_tracer.stub.function( :update_tracers )}" <<
            "</script> <!-- Script injected by #{self.class} -->\n"
    )

    taints = [@taint]
    # Include cookie names and values in the trace so that the browser will
    # be able to infer if they're being used, to avoid unnecessary audits.
    if Options.audit.cookie_doms?
        taints |= HTTP::Client.cookies.map { |c| c.inputs.to_a }.flatten
    end
    taints = taints.flatten.reject { |v| v.to_s.empty? }

    response.body = <<-EOHTML
        <script src="#{script_url_for( :taint_tracer )}"></script> <!-- Script injected by #{self.class} -->
        <script> #{@taint_tracer.stub.function( :initialize, taints )} </script> <!-- Script injected by #{self.class} -->

        <script src="#{script_url_for( :dom_monitor )}"></script> <!-- Script injected by #{self.class} -->
        <script>
            #{@dom_monitor.stub.function( :initialize )};
            #{js_initialization_signal};

            #{custom_code}
        </script> <!-- Script injected by #{self.class} -->

        #{body}
    EOHTML

    response.headers['content-length'] = response.body.bytesize
    true
end

#intervalsArray<Array>

Returns Arguments for JS ‘setInterval` calls.

Returns:

  • (Array<Array>)

    Arguments for JS ‘setInterval` calls.



298
299
300
301
# File 'lib/arachni/browser/javascript.rb', line 298

def intervals
    return [] if !supported?
    dom_monitor.intervals
end

#log_data_flow_sink_stub(*args) ⇒ String

Returns JS code which will call the ‘TaintTracer.log_data_flow_sink`, browser-side, JS function.

Returns:

  • (String)

    JS code which will call the ‘TaintTracer.log_data_flow_sink`, browser-side, JS function.



185
186
187
# File 'lib/arachni/browser/javascript.rb', line 185

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.

Returns:

  • (String)

    JS code which will call the ‘TaintTracer.log_execution_flow_sink`, browser-side, JS function.



178
179
180
# File 'lib/arachni/browser/javascript.rb', line 178

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.

Returns:

  • (Bool)

    ‘true` if our custom JS environment has been initialized.



203
204
205
# File 'lib/arachni/browser/javascript.rb', line 203

def ready?
    !!run( "return window._#{token}" ) rescue false
end

#run(script) ⇒ Object

Returns Result of ‘script`.

Parameters:

  • script (String)

    JS code to execute.

Returns:

  • (Object)

    Result of ‘script`.



212
213
214
# File 'lib/arachni/browser/javascript.rb', line 212

def run( script )
    @browser.watir.execute_script script
end

#run_without_elements(script) ⇒ Object

Executes the given code but unwraps Watir elements.

Parameters:

  • script (String)

    JS code to execute.

Returns:

  • (Object)

    Result of ‘script`.



223
224
225
# File 'lib/arachni/browser/javascript.rb', line 223

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.

Parameters:

Returns:

  • (Bool)

    ‘true` if the request corresponded to a JS file and was served, `false` otherwise.

See Also:



314
315
316
317
318
319
320
321
322
323
# File 'lib/arachni/browser/javascript.rb', line 314

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_idsObject

Sets a custom ID attribute to elements with events but without a proper ID.



258
259
260
261
# File 'lib/arachni/browser/javascript.rb', line 258

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.

Returns:

  • (Bool)

    ‘true` if there is support for our JS environment in the current page, `false` otherwise.

See Also:



152
153
154
155
156
157
# File 'lib/arachni/browser/javascript.rb', line 152

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

#timeoutsArray<Array>

Returns Arguments for JS ‘setTimeout` calls.

Returns:

  • (Array<Array>)

    Arguments for JS ‘setTimeout` calls.



291
292
293
294
# File 'lib/arachni/browser/javascript.rb', line 291

def timeouts
    return [] if !supported?
    dom_monitor.timeouts
end

#wait_till_readyObject

Blocks until the browser page is ready.



196
197
198
199
# File 'lib/arachni/browser/javascript.rb', line 196

def wait_till_ready
    return if !supported?
    sleep 0.1 while !ready?
end