class Arachni::Page::DOM
Static DOM
snapshot as computed by a real browser.
@author Tasos “Zapotek” Laskos <tasos.laskos@arachni-scanner.com>
Constants
- IGNORE_FROM_HASH
Ignore these elements when calculating a {#hash}.
Attributes
data_flow_sinks[RW]
@return [Array]
{Browser::Javascript::TaintTracer#data_flow_sinks} data.
digest[RW]
@return [Integer]
Digest of the DOM tree.
execution_flow_sinks[RW]
@return [Array]
{Browser::Javascript::TaintTracer#execution_flow_sinks} data.
page[RW]
@return [Page]
Page to which this DOM state is attached.
skip_states[RW]
@return [Support::LookUp::HashSet]
transitions[RW]
@return [Array<Transition>]
Transitions representing the steps required to convert a {DOM} snapshot to a live {Browser} page.
url[RW]
@return [String]
URL of the page as seen by the user-agent, fragments and all.
Public Class Methods
from_rpc_data( data )
click to toggle source
@param [Hash] data
{#to_rpc_data}
@return [DOM]
# File lib/arachni/page/dom.rb, line 258 def self.from_rpc_data( data ) instance = allocate data.each do |name, value| value = case name when 'transitions' value.map { |t| Transition.from_rpc_data t } when 'cookies' value.map { |c| Cookie.from_rpc_data c } when 'data_flow_sinks' value.map do |entry| Browser::Javascript::TaintTracer::Sink::DataFlow.from_rpc_data( entry ) end.to_a when 'execution_flow_sinks' value.map do |entry| Browser::Javascript::TaintTracer::Sink::ExecutionFlow.from_rpc_data( entry ) end.to_a when 'skip_states' skip_states = Support::LookUp::HashSet.new( hasher: :persistent_hash ) skip_states.collection.merge( value || [] ) skip_states else value end instance.instance_variable_set( "@#{name}", value ) end instance end
new( options )
click to toggle source
@param [Hash] options @option options [Page] :page @option options [Array<Hash>] :transitions
# File lib/arachni/page/dom.rb, line 60 def initialize( options ) @page = options[:page] self.url = options[:url] || @page.url self.digest = options[:digest] @cookies = options[:cookies] || [] @transitions = options[:transitions] || [] @data_flow_sinks = options[:data_flow_sinks] || [] @execution_flow_sinks = options[:execution_flow_sinks] || [] @skip_states = options[:skip_states] || Support::LookUp::HashSet.new( hasher: :persistent_hash ) end
Public Instance Methods
==( other )
click to toggle source
# File lib/arachni/page/dom.rb, line 299 def ==( other ) hash == other.hash end
depth()
click to toggle source
@return [Integer]
Depth of the current DOM -- sum of {#transitions} {Transition#depth}s that had to be triggered to reach the current state.
# File lib/arachni/page/dom.rb, line 85 def depth @transitions.map { |t| t.depth }.inject(&:+).to_i end
hash()
click to toggle source
Calls superclass method
# File lib/arachni/page/dom.rb, line 295 def hash digest || super end
marshal_dump()
click to toggle source
# File lib/arachni/page/dom.rb, line 243 def marshal_dump instance_variables.inject({}) do |h, iv| next h if iv == :@page h[iv] = instance_variable_get( iv ) h end end
marshal_load( h )
click to toggle source
# File lib/arachni/page/dom.rb, line 251 def marshal_load( h ) h.each { |k, v| instance_variable_set( k, v ) } end
playable_transitions()
click to toggle source
# File lib/arachni/page/dom.rb, line 89 def playable_transitions transitions.select { |t| t.playable? } end
print_transitions( printer, indent = '' )
click to toggle source
# File lib/arachni/page/dom.rb, line 93 def print_transitions( printer, indent = '' ) longest_event_size = 0 @transitions.each do |t| longest_event_size = [t.event.to_s.size, longest_event_size].max end @transitions.map do |t| padding = longest_event_size - t.event.to_s.size + 1 time = sprintf( '%.4f', t.time.to_f ) if t.event == :request printer.call "#{indent * 2}* [#{time}s] #{t.event}#{' ' * padding} => #{t.element}" else url = nil if t.options[:url] url = "(#{t.options[:url]})" end printer.call "#{indent}-- [#{time}s] #{t.event}#{' ' * padding} => #{t.element} #{url}" if t.options[:cookies] && t.options[:cookies].any? printer.call "#{indent * 2}-- Cookies:" t.options[:cookies].each do |name, value| printer.call "#{indent * 3}* #{name}\t=> #{value}\n" end end if t.options[:inputs] && t.options[:inputs].any? t.options[:inputs].each do |name, value| printer.call "#{indent * 2}* #{name}\t=> #{value}\n" end end end end end
push_transition( transition )
click to toggle source
@param [Transition] transition
Push the given transition to the {#transitions}.
# File lib/arachni/page/dom.rb, line 78 def push_transition( transition ) @transitions << transition end
restore( browser )
click to toggle source
Loads the page and restores it to its captured state.
@param [Browser] browser
Browser to use to restore the DOM.
@return [Browser, nil]
Live page in the `browser` if successful, `nil` otherwise.
# File lib/arachni/page/dom.rb, line 137 def restore( browser ) playables = self.playable_transitions # First transition will always be the page load and if that's all there # is then we're done. if playables.size == 1 surl = playables.first.options[:url] browser.print_debug "Only have a URL load transition: #{surl}" browser.goto surl return browser # Alternatively, try to load the page via its DOM#url in case it can # restore itself via its URL fragments and whatnot. else browser.goto url end # No transitions, nothing more to be done. return browser if playables.empty? browser_dom = browser.state # We were probably led to an out-of-scope page via a JS redirect, bail out. return if !browser_dom # Check to see if just loading the DOM URL was enough. # # Of course, this check will fail some of the time because even if the # page can restore itself via its URL (using fragment data most probably), # the document may still be different from when our snapshot was captured. # # However, it doesn't cost us anything so it's worth a shot. if browser_dom == self browser.print_debug "Loaded snapshot by URL: #{url}" return browser end browser.print_debug "Could not load snapshot by URL (#{url}), " << 'will load by replaying transitions.' # The URL restore failed, replay its transitions. playables.each do |transition| next if transition.play( browser ) browser.print_debug "Could not replay transition for: #{url}" playables.each do |t| browser.print_debug "-#{t == transition ? '>' : '-'} #{transition}" end return end browser end
state()
click to toggle source
# File lib/arachni/page/dom.rb, line 194 def state self.class.new( url: @url, digest: @digest, transitions: @transitions.dup, skip_states: @skip_states.dup ) end
to_h()
click to toggle source
@return [Hash]
# File lib/arachni/page/dom.rb, line 204 def to_h { url: url, transitions: transitions.map(&:to_hash), cookies: cookies.map(&:to_hash), digest: digest, skip_states: skip_states, data_flow_sinks: data_flow_sinks.map(&:to_hash), execution_flow_sinks: execution_flow_sinks.map(&:to_hash) } end
to_hash()
click to toggle source
# File lib/arachni/page/dom.rb, line 215 def to_hash to_h end
to_rpc_data()
click to toggle source
@return [Hash]
Data representing this instance that are suitable the RPC transmission.
# File lib/arachni/page/dom.rb, line 231 def to_rpc_data { 'url' => url, 'transitions' => transitions.map(&:to_rpc_data), 'cookies' => cookies.map(&:to_rpc_data), 'digest' => digest, 'skip_states' => skip_states ? skip_states.collection.to_a : [], 'data_flow_sinks' => data_flow_sinks.map(&:to_rpc_data), 'execution_flow_sinks' => execution_flow_sinks.map(&:to_rpc_data) } end
to_s()
click to toggle source
# File lib/arachni/page/dom.rb, line 219 def to_s s = "#<#{self.class}:#{object_id} " s << "@url=#{@url.inspect} " s << "@transitions=#{transitions.size} " s << "@data_flow_sinks=#{@data_flow_sinks.size} " s << "@execution_flow_sinks=#{@execution_flow_sinks.size}" s << '>' end
Also aliased as: inspect
url=( url )
click to toggle source
# File lib/arachni/page/dom.rb, line 72 def url=( url ) @url = url.freeze end