module Arachni::Framework::Parts::State

Provides access to {Arachni::State::Framework} and helpers.

@author Tasos “Zapotek” Laskos <tasos.laskos@arachni-scanner.com>

Public Class Methods

included( base ) click to toggle source
# File lib/arachni/framework/parts/state.rb, line 18
def self.included( base )
    base.extend ClassMethods
end
new() click to toggle source
Calls superclass method
# File lib/arachni/framework/parts/state.rb, line 65
def initialize
    super

    Element::Capabilities::Auditable.skip_like do |element|
        if pause?
            print_debug "Blocking on element audit: #{element.audit_id}"
        end

        wait_if_paused
    end

    state.status = :ready
end

Public Instance Methods

abort( wait = true ) click to toggle source

Aborts the framework {#run} on a best effort basis.

@param [Bool] wait

Wait until the system has been aborted.
# File lib/arachni/framework/parts/state.rb, line 292
def abort( wait = true )
    state.abort wait
end
abort?() click to toggle source

@return [Bool]

`true` if the framework has been instructed to abort (i.e. is in the
process of being aborted or has been aborted), `false` otherwise.
# File lib/arachni/framework/parts/state.rb, line 278
def abort?
    state.abort?
end
aborted?() click to toggle source

@return [Bool]

`true` if the framework {#run} has been aborted, `false` otherwise.
# File lib/arachni/framework/parts/state.rb, line 271
def aborted?
    state.aborted?
end
aborting?() click to toggle source

@return [Bool]

`true` if the framework is in the process of aborting, `false` otherwise.
# File lib/arachni/framework/parts/state.rb, line 284
def aborting?
    state.aborting?
end
clean_up( shutdown_browsers = true ) click to toggle source

Cleans up the framework; should be called after running the audit or after canceling a running scan.

It stops the clock and waits for the plugins to finish up.

# File lib/arachni/framework/parts/state.rb, line 103
def clean_up( shutdown_browsers = true )
    return if @cleaned_up
    @cleaned_up = true

    state.force_resume

    state.status = :cleanup

    if shutdown_browsers
        state.set_status_message :browser_cluster_shutdown
        shutdown_browser_cluster
    end

    state.set_status_message :clearing_queues
    page_queue.clear
    url_queue.clear

    @finish_datetime  = Time.now
    @start_datetime ||= Time.now

    # Make sure this is disabled or it'll break reporter output.
    disable_only_positives

    state.running = false

    state.set_status_message :waiting_for_plugins
    @plugins.block

    # Plugins may need the session right till the very end so save it for last.
    @session.clean_up
    @session = nil

    true
end
done?() click to toggle source

@return (see Arachni::State::Framework#done?)

# File lib/arachni/framework/parts/state.rb, line 248
def done?
    state.done?
end
pause( wait = true ) click to toggle source

@note Each call from a unique caller is counted as a pause request

and in order for the system to resume **all** pause callers need to
{#resume} it.

Pauses the framework on a best effort basis.

@param [Bool] wait

Wait until the system has been paused.

@return [Integer]

ID identifying this pause request.
# File lib/arachni/framework/parts/state.rb, line 263
def pause( wait = true )
    id = generate_token.hash
    state.pause id, wait
    id
end
pause?() click to toggle source

@return [Bool]

`true` if the framework has been instructed to pause (i.e. is in the
process of being paused or has been paused), `false` otherwise.
# File lib/arachni/framework/parts/state.rb, line 237
def pause?
    state.pause?
end
paused?() click to toggle source

@return [Bool]

`true` if the framework is paused, `false` otherwise.
# File lib/arachni/framework/parts/state.rb, line 230
def paused?
    state.paused?
end
pausing?() click to toggle source

@return [Bool]

`true` if the framework is in the process of pausing, `false` otherwise.
# File lib/arachni/framework/parts/state.rb, line 243
def pausing?
    state.pausing?
end
reset() click to toggle source

@note Prefer this from {.reset} if you already have an instance. @note You should first reset {Arachni::Options}.

Resets everything and allows the framework to be re-used.

# File lib/arachni/framework/parts/state.rb, line 147
def reset
    @cleaned_up  = false
    @browser_job = nil

    @failures.clear
    @retries.clear

    # This needs to happen before resetting the other components so they
    # will be able to put in their hooks.
    self.class.reset

    clear_observers
    reset_trainer
    reset_session

    @checks.clear
    @reporters.clear
    @plugins.clear
end
reset_trainer() click to toggle source

@private

# File lib/arachni/framework/parts/state.rb, line 139
def reset_trainer
    @trainer = Trainer.new( self )
end
restore( afs ) click to toggle source

@param [String] afs

Path to an `.afs.` (Arachni Framework Snapshot) file created by {#suspend}.

@return [Framework]

Restored instance.
# File lib/arachni/framework/parts/state.rb, line 177
def restore( afs )
    Snapshot.load afs

    browser_job_update_skip_states state.browser_skip_states

    checks.load  Options.checks
    plugins.load Options.plugins.keys

    nil
end
resume( id ) click to toggle source

@note Each call from a unique caller is counted as a pause request

and in order for the system to resume **all** pause callers need to
{#resume} it.

Removes a {#pause} request for the current caller.

@param [Integer] id

ID of the {#pause} request.
# File lib/arachni/framework/parts/state.rb, line 304
def resume( id )
    state.resume id
end
running?() click to toggle source

@return [Bool]

`true` if the framework is running, `false` otherwise. This is `true`
even if the scan is {#paused?}.
# File lib/arachni/framework/parts/state.rb, line 218
def running?
    state.running?
end
scanning?() click to toggle source

@return [Bool]

`true` if the system is scanning, `false` otherwise.
# File lib/arachni/framework/parts/state.rb, line 224
def scanning?
    state.scanning?
end
snapshot_path() click to toggle source

@return [String]

Provisioned {#suspend} dump file for this instance.
# File lib/arachni/framework/parts/state.rb, line 81
def snapshot_path
    return @state_archive if @state_archive

    default_filename =
        "#{URI(options.url).host} #{Time.now.to_s.gsub( ':', '_' )} " <<
            "#{generate_token}.afs"

    location = options.snapshot.save_path

    if !location
        location = default_filename
    elsif File.directory? location
        location += "/#{default_filename}"
    end

    @state_archive ||= File.expand_path( location )
end
state() click to toggle source

@return [State::Framework]

# File lib/arachni/framework/parts/state.rb, line 168
def state
    Arachni::State.framework
end
status() click to toggle source

@return [Symbol]

Status of the instance, possible values are (in order):

* `:ready` -- {#initialize Initialised} and waiting for instructions.
* `:preparing` -- Getting ready to start (i.e. initializing plugins etc.).
* `:scanning` -- The instance is currently {#run auditing} the webapp.
* `:pausing` -- The instance is being {#pause paused} (if applicable).
* `:paused` -- The instance has been {#pause paused} (if applicable).
* `:suspending` -- The instance is being {#suspend suspended} (if applicable).
* `:suspended` -- The instance has being {#suspend suspended} (if applicable).
* `:cleanup` -- The scan has completed and the instance is
    {Framework::Parts::State#clean_up cleaning up} after itself (i.e. waiting for
    plugins to finish etc.).
* `:aborted` -- The scan has been {Framework::Parts::State#abort}, you can grab the
    report and shutdown.
* `:done` -- The scan has completed, you can grab the report and shutdown.
# File lib/arachni/framework/parts/state.rb, line 211
def status
    state.status
end
status_messages() click to toggle source

@return [Array<String>]

Messages providing more information about the current {#status} of
the framework.
# File lib/arachni/framework/parts/state.rb, line 191
def status_messages
    state.status_messages
end
suspend( wait = true ) click to toggle source

Writes a {Snapshot.dump} to disk and aborts the scan.

@param [Bool] wait

Wait for the system to write it state to disk.

@return [String,nil]

Path to the state file `wait` is `true`, `nil` otherwise.
# File lib/arachni/framework/parts/state.rb, line 315
def suspend( wait = true )
    state.suspend( wait )
    return snapshot_path if wait
    nil
end
suspend?() click to toggle source

@return [Bool]

`true` if the system is in the process of being suspended, `false`
otherwise.
# File lib/arachni/framework/parts/state.rb, line 324
def suspend?
    state.suspend?
end
suspended?() click to toggle source

@return [Bool]

`true` if the system has been suspended, `false` otherwise.
# File lib/arachni/framework/parts/state.rb, line 330
def suspended?
    state.suspended?
end

Private Instance Methods

abort_if_signaled() click to toggle source
# File lib/arachni/framework/parts/state.rb, line 412
def abort_if_signaled
    return if !abort?
    clean_up
    state.aborted
end
handle_signals() click to toggle source
# File lib/arachni/framework/parts/state.rb, line 401
def handle_signals
    wait_if_paused
    abort_if_signaled
    suspend_if_signaled
end
pre_audit_element_filter( page ) click to toggle source

Small but (sometimes) important optimization:

Keep track of page elements which have already been passed to checks, in order to filter them out and hopefully even avoid running checks against pages with no new elements.

It’s not like there were going to be redundant audits anyways, because each layer of the audit performs its own redundancy checks, but those redundancy checks can introduce significant latencies when dealing with pages with lots of elements.

# File lib/arachni/framework/parts/state.rb, line 366
def pre_audit_element_filter( page )
    unique_elements  = {}
    page.elements.each do |e|
        next if !Options.audit.element?( e.type )
        next if e.is_a?( Cookie ) || e.is_a?( Header )

        new_element               = false
        unique_elements[e.type] ||= []

        if !state.element_checked?( e )
            state.element_checked e
            new_element = true
        end

        if page.dom.depth > 0 && e.respond_to?( :dom ) && e.dom
            if !state.element_checked?( e.dom )
                state.element_checked e.dom
                new_element = true
            end
        end

        next if !new_element

        unique_elements[e.type] << e
    end

    # Remove redundant elements from the page cache, if there are thousands
    # of them then just skipping them during the audit will introduce latency.
    unique_elements.each do |type, elements|
        page.send( "#{type}s=", elements )
    end

    page
end
prepare() click to toggle source

@note Must be called before calling any audit methods.

Prepares the framework for the audit.

  • Sets the status to ‘:preparing`.

  • Starts the clock.

  • Runs the plugins.

# File lib/arachni/framework/parts/state.rb, line 343
def prepare
    state.status  = :preparing
    state.running = true
    @start_datetime = Time.now

    Snapshot.restored? ? @plugins.restore : @plugins.run
end
reset_session() click to toggle source
# File lib/arachni/framework/parts/state.rb, line 351
def reset_session
    @session.clean_up if @session
    @session = Session.new
end
suspend_if_signaled() click to toggle source
# File lib/arachni/framework/parts/state.rb, line 418
def suspend_if_signaled
    return if !suspend?
    suspend_to_disk
end
suspend_to_disk() click to toggle source
# File lib/arachni/framework/parts/state.rb, line 423
def suspend_to_disk
    while wait_for_browser_cluster?
        last_pending_jobs ||= 0
        pending_jobs = browser_cluster.pending_job_counter

        if pending_jobs != last_pending_jobs
            state.set_status_message :waiting_for_browser_cluster_jobs, pending_jobs
            print_info "Suspending: #{status_messages.first}"
        end

        last_pending_jobs = pending_jobs
        sleep 0.1
    end

    # Make sure the component options are up to date with what's actually
    # happening.
    options.checks  = checks.loaded
    options.plugins = plugins.loaded.
        inject({}) { |h, name| h[name.to_s] = Options.plugins[name.to_s] || {}; h }

    if browser_cluster_job_skip_states
        state.browser_skip_states.merge browser_cluster_job_skip_states
    end

    state.set_status_message :suspending_plugins
    @plugins.suspend

    state.set_status_message :saving_snapshot, snapshot_path
    Snapshot.dump( snapshot_path )
    state.clear_status_messages

    clean_up

    state.set_status_message :snapshot_location, snapshot_path
    print_info status_messages.first
    state.suspended
end
wait_if_paused() click to toggle source
# File lib/arachni/framework/parts/state.rb, line 407
def wait_if_paused
    state.paused if pause?
    sleep 0.2 while pause? && !abort?
end