Class: Arachni::Session

Inherits:
Object show all
Includes:
UI::Output, Utilities
Defined in:
lib/arachni/session.rb

Overview

Session management class.

Handles logins, provided log-out detection, stores and executes login sequences and provided general webapp session related helpers.

Author:

Defined Under Namespace

Classes: Error

Constant Summary collapse

LOGIN_TRIES =
5
LOGIN_RETRY_WAIT =
5

Instance Attribute 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, #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?, #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

Instance Attribute Details

#browserBrowser (readonly)

Returns:



52
53
54
# File 'lib/arachni/session.rb', line 52

def browser
  @browser
end

Instance Method Details

#can_login?Bool

Returns ‘true` if there is log-in capability, `false` otherwise.

Returns:

  • (Bool)

    ‘true` if there is log-in capability, `false` otherwise.



173
174
175
# File 'lib/arachni/session.rb', line 173

def can_login?
    configured? && 
end

#clean_upObject



54
55
56
57
# File 'lib/arachni/session.rb', line 54

def clean_up
    configuration.clear
    shutdown_browser
end

#configurationObject



94
95
96
# File 'lib/arachni/session.rb', line 94

def configuration
    Data.session.configuration
end

#configure(options) ⇒ Object

Parameters:

Options Hash (options):

  • :url (String)

    URL containing the login form.

  • :inputs (Hash{String=>String})

    Hash containing inputs with which to locate and fill-in the form.



89
90
91
92
# File 'lib/arachni/session.rb', line 89

def configure( options )
    configuration.clear
    configuration.merge! options
end

#configured?Bool

Returns ‘true` if configured, `false` otherwise.

Returns:

  • (Bool)

    ‘true` if configured, `false` otherwise.



100
101
102
# File 'lib/arachni/session.rb', line 100

def configured?
    configuration.any?
end

Tries to find the main session (login/ID) cookie.

Parameters:

  • block (Block)

    Block to be passed the cookie.

Raises:



72
73
74
75
76
77
78
79
80
81
82
# File 'lib/arachni/session.rb', line 72

def cookie( &block )
    return block.call( @session_cookie ) if @session_cookie
    fail Error::NoLoginCheck, 'No login-check has been configured.' if !

    cookies.each do |cookie|
        logged_in?( cookies: { cookie.name => '' } ) do |bool|
            next if bool
            block.call( @session_cookie = cookie )
        end
    end
end

#cookiesArray<Element::Cookie>

Returns Session cookies.

Returns:



61
62
63
# File 'lib/arachni/session.rb', line 61

def cookies
    http.cookies.select(&:session?)
end

#ensure_logged_inBool?

Returns ‘true` if logged-in, `false` otherwise, `nil` if there’s no log-in capability.

Returns:

  • (Bool, nil)

    ‘true` if logged-in, `false` otherwise, `nil` if there’s no log-in capability.



180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
# File 'lib/arachni/session.rb', line 180

def ensure_logged_in
    return if !can_login?
    return true if logged_in?

    print_bad 'The scanner has been logged out.'
    print_info 'Trying to re-login...'

    LOGIN_TRIES.times do |i|
        break if !.response.timed_out? rescue Error

        print_bad "Login attempt #{i+1} failed, retrying after " <<
                      "#{LOGIN_RETRY_WAIT} seconds..."
        sleep LOGIN_RETRY_WAIT
    end

    if logged_in?
        print_ok 'Logged-in successfully.'
        true
    else
        print_bad 'Could not re-login.'
        false
    end
end

#find_login_form(opts = {}, &block) ⇒ Object

Finds a login forms based on supplied location, collection and criteria.

Parameters:

  • opts (Hash) (defaults to: {})
  • block (Block)

    If a block and a :url are given, the request will run async and the block will be called with the result of this method.

Options Hash (opts):

  • :requires_password (Bool)

    Does the login form include a password field? (Defaults to ‘true`)

  • :action (Array, Regexp)

    Regexp to match or String to compare against the form action.

  • :inputs (String, Array, Hash, Symbol)

    Inputs that the form must contain.

  • :forms (Array<Element::Form>)

    Collection of forms to look through.

  • :pages (Page, Array<Page>)

    Pages to look through.

  • :url (String)

    URL to fetch and look for forms.



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
# File 'lib/arachni/session.rb', line 123

def ( opts = {}, &block )
    async = block_given?

    requires_password = (opts[:requires_password].nil? ? true : opts[:requires_password])

    find = proc do |cforms|
        cforms.select do |f|
            next if requires_password && !f.requires_password?

            oks = []

            if action = opts[:action]
                oks << !!(action.is_a?( Regexp ) ? f.action =~ action : f.action == action)
            end

            if inputs = opts[:inputs]
                oks << f.has_inputs?( inputs )
            end

            oks.count( true ) == oks.size
        end.first
    end

    forms = if opts[:pages]
                [opts[:pages]].flatten.map { |p| p.forms }.flatten
            elsif opts[:forms]
                opts[:forms]
            elsif (url = opts[:url])
                http_opts = {
                    update_cookies:  true,
                    follow_location: true
                }

                if async
                    http.get( url, http_opts ) do |r|
                        block.call find.call( forms_from_response( r, true ) )
                    end
                else
                    forms_from_response(
                        http.get( url, http_opts.merge( mode: :sync ) ),
                        true
                    )
                end
            end

    find.call( forms || [] ) if !async
end

#has_browser?Boolean

Returns:

  • (Boolean)


328
329
330
# File 'lib/arachni/session.rb', line 328

def has_browser?
    Browser.has_executable? && Options.scope.dom_depth_limit > 0
end

#has_login_check?Bool

Returns ‘true` if a login check exists, `false` otherwise.

Returns:

  • (Bool)

    ‘true` if a login check exists, `false` otherwise.



319
320
321
# File 'lib/arachni/session.rb', line 319

def 
    !!(Options.session.check_url && Options.session.check_pattern)
end

#httpHTTP::Client

Returns:



324
325
326
# File 'lib/arachni/session.rb', line 324

def http
    HTTP::Client
end

#logged_in?(http_options = {}, &block) ⇒ Bool?

Returns ‘true` if we’re logged-in, ‘false` otherwise.

Parameters:

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

    HTTP options to use for the check.

  • block (Block)

    If a block has been provided the check will be async and the result will be passed to it, otherwise the method will return the result.

Returns:

  • (Bool, nil)

    ‘true` if we’re logged-in, ‘false` otherwise.

Raises:



301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
# File 'lib/arachni/session.rb', line 301

def logged_in?( http_options = {}, &block )
    fail Error::NoLoginCheck if !

    http_options = http_options.merge(
        mode:            block_given? ? :async : :sync,
        follow_location: true
    )

    bool = nil
    http.get( Options.session.check_url, http_options ) do |response|
        bool = !!response.body.match( Options.session.check_pattern )
        block.call( bool ) if block
    end
    bool
end

#loginPage?

Uses the information provided by #configure to login.

Returns:

Raises:



212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
# File 'lib/arachni/session.rb', line 212

def 
    fail Error::NotConfigured, 'Please #configure the session first.' if !configured?

    if has_browser?
        print_debug 'Logging in using browser.'
    else
        print_debug 'Logging in without browser.'
    end

    print_debug "Grabbing page at: #{configuration[:url]}"

    # Revert to the Framework DOM Level 1 page handling if no browser
    # is available.
    page = refresh_browser ?
        browser.load( configuration[:url], take_snapshot: false ).to_page :
        Page.from_url( configuration[:url], precision: 1, http: {
            update_cookies: true
        })

    print_debug "Got page with URL #{page.url}"

    form = (
        # We need to reparse the body in order to override the scope
        # and thus extract even out-of-scope forms in case we're dealing
        # with a Single-Sign-On situation.
        forms:  forms_from_document( page.url, page.body, true ),
        inputs: configuration[:inputs].keys
    )

    if !form
        print_debug_level_2 page.body
        fail Error::FormNotFound,
             "Login form could not be found with: #{configuration}"
    end

    print_debug "Found login form: #{form.id}"

    form.page = page

    # Use the form DOM to submit if a browser is available.
    form = form.dom if has_browser?

    form.update configuration[:inputs]
    form.auditor = self

    print_debug "Updated form inputs: #{form.inputs}"

    page = nil
    if has_browser?
        print_debug 'Submitting form.'
        form.submit { |p| page = p }
        print_debug 'Form submitted.'

        http.update_cookies browser.cookies
    else
        page = form.submit(
            mode:            :sync,
            follow_location: false,
            update_cookies:  true
        ).to_page

        if page.response.redirection?
            url  = to_absolute( page.response.headers.location, page.url )
            print_debug "Redirected to: #{url}"

            page = Page.from_url( url, precision: 1, http: { update_cookies: true } )
        end
    end

    page
end

#with_browser(&block) ⇒ Object

Parameters:

  • block (Block)

    Block to be passed the #browser.



286
287
288
# File 'lib/arachni/session.rb', line 286

def with_browser( &block )
    block.call browser
end