Class: Arachni::Element::Cookie

Inherits:
Base show all
Includes:
Arachni::Element::Capabilities::Analyzable, Arachni::Element::Capabilities::WithDOM
Defined in:
lib/arachni/element/cookie.rb,
lib/arachni/element/cookie/dom.rb

Overview

Represents a Cookie object and provides helper class methods for parsing, encoding, etc.

Author:

Defined Under Namespace

Classes: DOM

Constant Summary collapse

DEFAULT =

Default cookie values

{
    name:        nil,
    value:       nil,
    version:     0,
    port:        nil,
    discard:     nil,
    comment_url: nil,
    expires:     nil,
    max_age:     nil,
    comment:     nil,
    secure:      nil,
    path:        nil,
    domain:      nil,
    httponly:    false
}

Constants included from Arachni::Element::Capabilities::Analyzable::Differential

Arachni::Element::Capabilities::Analyzable::Differential::DIFFERENTIAL_OPTIONS

Constants included from Arachni::Element::Capabilities::Analyzable::Taint

Arachni::Element::Capabilities::Analyzable::Taint::TAINT_OPTIONS

Constants included from Arachni::Element::Capabilities::Auditable

Arachni::Element::Capabilities::Auditable::OPTIONS

Constants included from Arachni::Element::Capabilities::Mutable

Arachni::Element::Capabilities::Mutable::MUTATION_OPTIONS

Instance Attribute Summary

Attributes included from Arachni::Element::Capabilities::Auditable

#audit_options

Attributes included from Arachni::Element::Capabilities::WithAuditor

#auditor

Attributes included from Arachni::Element::Capabilities::Mutable

#affected_input_name, #format, #seed

Attributes included from Arachni::Element::Capabilities::Inputtable

#default_inputs, #inputs

Attributes included from Arachni::Element::Capabilities::WithNode

#html

Attributes inherited from Base

#initialization_options, #page

Class Method Summary collapse

Instance Method Summary collapse

Methods included from Arachni::Element::Capabilities::Analyzable

has_timeout_candidates?, reset, timeout_audit_run

Methods included from Arachni::Element::Capabilities::Analyzable::Differential

#differential_analysis, reset

Methods included from Arachni::Element::Capabilities::Analyzable::Timeout

add_phase_2_candidate, candidates_include?, deduplicate, deduplicate?, do_not_deduplicate, #ensure_responsiveness, has_candidates?, payload_delay_from_options, reset, run, #timeout_analysis, timeout_from_options, #timeout_id, #timing_attack_probe, #timing_attack_verify

Methods included from Arachni::Element::Capabilities::Analyzable::Taint

#taint_analysis

Methods included from Arachni::Element::Capabilities::Auditable

#audit, #audit_id, #audit_status_message, #audit_status_message_action, #audit_verbose_message, #coverage_hash, #coverage_id, #dup, #matches_skip_like_blocks?, #reset, reset, #skip?, skip_like

Methods included from Arachni::Element::Capabilities::WithAuditor

#dup, #marshal_dump, #orphan?, #prepare_for_report, #remove_auditor

Methods included from Arachni::Element::Capabilities::Mutable

#affected_input_value, #affected_input_value=, #dup, #immutables, #mutation?, #mutations, #reset, #switch_method, #to_h

Methods included from Arachni::Element::Capabilities::Submittable

#action, #action=, #dup, #http, #id, #method, #method=, #platforms, #submit, #to_h

Methods included from Arachni::Element::Capabilities::Inputtable

#[], #[]=, #changes, #dup, #has_inputs?, #inputtable_id, #reset, #to_h, #try_input, #update, #valid_input_data?, #valid_input_name?, #valid_input_name_data?, #valid_input_value?, #valid_input_value_data?

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 Arachni::Element::Capabilities::WithDOM

#dup

Methods included from Arachni::Element::Capabilities::WithNode

#dup, #node, #to_h

Methods inherited from Base

#==, #action, #dup, #hash, #id, #marshal_dump, #marshal_load, #persistent_hash, #prepare_for_report, #reset, #to_h, #to_hash, type, #type, #url, #url=

Methods included from Arachni::Element::Capabilities::WithScope

#scope

Constructor Details

#initialize(options) ⇒ Cookie

Returns a new instance of Cookie.

Parameters:

  • options (Hash)

    For options see DEFAULT, with the following extras:

Options Hash (options):

  • :url (String)

    URL of the page which created the cookie – required.

  • :action (String)

    URL of the page to submit the cookie – defaults to ‘:url`.

  • :inputs (Hash)

    Allows you to pass cookie data as a ‘name => value` pair instead of the more complex DEFAULT structure.



52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
# File 'lib/arachni/element/cookie.rb', line 52

def initialize( options )
    @data = {}
    super( options )

    if options[:name] && options[:value]
        options[:name]  = options[:name].to_s.recode
        options[:value] = options[:value].to_s.recode

        self.inputs = { options[:name] => options[:value] }
        @data.merge!( options )
    else
        self.inputs = (options[:inputs] || {}).dup
    end

    @data.merge!( DEFAULT.merge( @data ) )
    @data[:value] = decode( @data[:value].to_s )

    parsed_uri = uri_parse( action )
    if !@data[:path]
        path = parsed_uri.path
        path = !path.empty? ? path : '/'
        @data[:path] = path
    end

    if @data[:expires] && !@data[:expires].is_a?( Time )
        @data[:expires] = Time.parse( @data[:expires] ) rescue nil
    end

    @data[:domain] ||= parsed_uri.host

    @default_inputs = self.inputs.dup.freeze
end

Dynamic Method Handling

This class handles dynamic methods through the method_missing method

#method_missing(sym, *args, &block) ⇒ Object

Uses the method name as a key to cookie attributes in DEFAULT.



228
229
230
231
# File 'lib/arachni/element/cookie.rb', line 228

def method_missing( sym, *args, &block )
    return @data[sym] if @data.include? sym
    super( sym, *args, &block )
end

Class Method Details

.decode(str) ⇒ String

Decodes a String encoded for the ‘Cookie` header field.

Examples:

p Cookie.decode "%2B%3B%25%3D%00+"
#=> "+;%=\x00 "

Parameters:

Returns:



479
480
481
# File 'lib/arachni/element/cookie.rb', line 479

def decode( str )
    URI.decode( str.to_s.recode.gsub( '+', ' ' ) )
end

.encode(str, type = :value) ⇒ String

Encodes a String‘s reserved characters in order to prepare it for the `Cookie` header field.

Examples:

p Cookie.encode "+;%=\0 "
#=> "%2B%3B%25%3D%00+"

Parameters:

Returns:



463
464
465
466
467
468
# File 'lib/arachni/element/cookie.rb', line 463

def encode( str, type = :value )
    reserved = "+;%\0\'\""
    reserved << '=' if type == :name

    URI.encode( str, reserved ).recode.gsub( ' ', '+' )
end

.expires_to_time(expires) ⇒ Time

Converts a cookie’s expiration date to a Ruby ‘Time` object.

Examples:

String time format

p Cookie.expires_to_time "Tue, 02 Oct 2012 19:25:57 GMT"
#=> 2012-10-02 22:25:57 +0300

Seconds since Epoch

p Cookie.expires_to_time "1596981560"
#=> 2020-08-09 16:59:20 +0300

p Cookie.expires_to_time 1596981560
#=> 2020-08-09 16:59:20 +0300

Parameters:

Returns:

  • (Time)


344
345
346
347
# File 'lib/arachni/element/cookie.rb', line 344

def expires_to_time( expires )
    return nil if expires == '0'
    (expires_to_i = expires.to_i) > 0 ? Time.at( expires_to_i ) : Time.parse( expires )
end

.from_document(url, document) ⇒ Array<Cookie>

Extracts cookies from a document based on ‘Set-Cookie` `http-equiv` meta tags.

Parameters:

  • url (String)

    Owner URL.

  • document (String, Nokogiri::HTML::Document)

Returns:

See Also:



372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
# File 'lib/arachni/element/cookie.rb', line 372

def from_document( url, document )
    # optimizations in case there are no cookies in the doc,
    # avoid parsing unless absolutely necessary!
    if !document.is_a?( Nokogiri::HTML::Document )
        # get get the head in order to check if it has an http-equiv for set-cookie
        head = document.to_s.match( /<head(.*)<\/head>/imx )

        # if it does feed the head to the parser in order to extract the cookies
        return [] if !head || !head.to_s.downcase.include?( 'set-cookie' )

        document = Nokogiri::HTML( head.to_s )
    end

    Arachni::Utilities.exception_jail {
        document.search( "//meta[@http-equiv]" ).map do |elem|
            next if elem['http-equiv'].downcase != 'set-cookie'
            from_set_cookie( url, elem['content'] )
        end.flatten.compact
    } rescue []
end

.from_file(url, filepath) ⇒ Array<Cookie>

Parses a Netscape Cookie-jar into an Array of Arachni::Element::Cookie.

Parameters:

Returns:

See Also:



306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
# File 'lib/arachni/element/cookie.rb', line 306

def from_file( url, filepath )
    File.open( filepath, 'r' ).map do |line|
        # skip empty lines
        next if (line = line.strip).empty? || line[0] == '#'

        c = {}
        c['domain'], foo, c['path'], c['secure'], c['expires'], c['name'],
            c['value'] = *line.split( "\t" )

        # expiry date is optional so if we don't have one push everything back
        begin
            c['expires'] = expires_to_time( c['expires'] )
        rescue
            c['value'] = c['name'].dup
            c['name'] = c['expires'].dup
            c['expires'] = nil
        end
        c['secure'] = (c['secure'] == 'TRUE') ? true : false
        new( { url: url }.merge( c.my_symbolize_keys ) )
    end.flatten.compact
end

.from_headers(url, headers) ⇒ Array<Cookie>

Extracts cookies from the ‘Set-Cookie` HTTP response header field.

Parameters:

Returns:

See Also:

  • forms_set_cookie


402
403
404
405
406
407
408
409
# File 'lib/arachni/element/cookie.rb', line 402

def from_headers( url, headers )
    headers = Arachni::HTTP::Headers.new( headers )
    return [] if headers.set_cookie.empty?

    exception_jail {
        headers.set_cookie.map { |c| from_set_cookie( url, c ) }.flatten
    } rescue []
end

.from_response(response) ⇒ Array<Cookie>

Extracts cookies from an HTTP response.

Parameters:

Returns:

See Also:



357
358
359
360
# File 'lib/arachni/element/cookie.rb', line 357

def from_response( response )
    ( from_document( response.url, response.body ) |
        from_headers( response.url, response.headers ) )
end

.from_rpc_data(data) ⇒ Object



285
286
287
288
289
290
291
292
293
294
# File 'lib/arachni/element/cookie.rb', line 285

def from_rpc_data( data )
    if data['initialization_options']['expires'] &&
        !data['initialization_options']['expires'].is_a?( Time )

        data['initialization_options']['expires'] =
            Time.parse( data['initialization_options']['expires'] ) rescue nil
    end

    super data
end

Parses the ‘Set-Cookie` header value into cookie elements.

Parameters:

Returns:



419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
# File 'lib/arachni/element/cookie.rb', line 419

def from_set_cookie( url, str )
    WEBrick::Cookie.parse_set_cookies( str ).flatten.uniq.map do |cookie|
        cookie_hash = {}
        cookie.instance_variables.each do |var|
            cookie_hash[var.to_s.gsub( /@/, '' )] = cookie.instance_variable_get( var )
        end
        cookie_hash['expires'] = cookie.expires

        cookie_hash['path'] ||= '/'
        cookie_hash['name']  = decode( cookie.name )
        cookie_hash['value'] = decode( cookie.value )

        new( { url: url }.merge( cookie_hash.my_symbolize_keys ) )
    end.flatten.compact
end

.from_string(url, string) ⇒ Array<Cookie>

Parses a string formatted for the ‘Cookie` HTTP request header field into cookie elements.

Parameters:

Returns:



445
446
447
448
449
450
451
# File 'lib/arachni/element/cookie.rb', line 445

def from_string( url, string )
    return [] if string.empty?
    string.split( ';' ).map do |cookie_pair|
        k, v = *cookie_pair.split( '=', 2 )
        new( url: url, inputs: { decode( k.strip ) => decode( v.strip ) } )
    end.flatten.compact
end


483
484
485
486
487
488
489
490
491
492
493
494
# File 'lib/arachni/element/cookie.rb', line 483

def keep_for_set_cookie
    return @keep if @keep

    @keep = Set.new( DEFAULT.keys )
    @keep.delete( :name )
    @keep.delete( :value )
    @keep.delete( :url )
    @keep.delete( :secure )
    @keep.delete( :httponly )
    @keep.delete( :version )
    @keep
end

Instance Method Details

#decode(str) ⇒ Object

See Also:



267
268
269
# File 'lib/arachni/element/cookie.rb', line 267

def decode( str )
    self.class.decode( str )
end

#domDOM

Returns:



86
87
88
89
90
91
92
93
94
# File 'lib/arachni/element/cookie.rb', line 86

def dom
    return if @skip_dom || inputs.empty?

    # In case the cookie already has input data not supported by its DOM
    # extension.
    @skip_dom = !try_input { super }

    @dom
end

#each_extensive_mutation(mutation) ⇒ Object



210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
# File 'lib/arachni/element/cookie.rb', line 210

def each_extensive_mutation( mutation )
    return if orphan?

    (auditor.page.links | auditor.page.forms).each do |e|
        next if e.inputs.empty?

        c = e.dup
        c.affected_input_name = "mutation for the '#{name}' cookie"
        c.auditor = auditor
        c.audit_options[:submit] ||= {}
        c.audit_options[:submit][:cookies] = mutation.inputs.dup
        c.inputs = Arachni::Options.input.fill( c.inputs.dup )

        yield c
    end
end

#each_mutation(payload, opts = {}) {|elem| ... } ⇒ Object

Overrides Arachni::Element::Capabilities::Mutable#each_mutation to handle cookie-specific limitations and the Options#audit_cookies_extensively option.

Parameters:

Yields:

  • (elem)
  • (mutation)

    Each generated mutation.

Yield Parameters:

  • (Mutable)

See Also:



186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
# File 'lib/arachni/element/cookie.rb', line 186

def each_mutation( payload, opts = {}, &block )
    flip = opts.delete( :param_flip )

    super( payload, opts ) do |elem|
        yield elem

        next if !Arachni::Options.audit.cookies_extensively?
        elem.each_extensive_mutation( elem, &block )
    end

    return if !flip

    if !valid_input_name_data?( payload )
        print_debug_level_2 'Payload not supported as input value by' <<
                                " #{audit_id}: #{payload.inspect}"
        return
    end

    elem = self.dup
    elem.affected_input_name = 'Parameter flip'
    elem.inputs = { payload => seed }
    yield elem if block_given?
end

#encode(*args) ⇒ Object

See Also:



262
263
264
# File 'lib/arachni/element/cookie.rb', line 262

def encode( *args )
    self.class.encode( *args )
end

#expired?(time = Time.now) ⇒ Boolean

Indicates whether or not the cookie has expired.

Parameters:

  • time (Time) (defaults to: Time.now)

    To compare against.

Returns:

  • (Boolean)


132
133
134
# File 'lib/arachni/element/cookie.rb', line 132

def expired?( time = Time.now )
    expires_at != nil && time > expires_at
end

#expires_atTime, NilClass

Returns Expiration ‘Time` of the cookie or `nil` if it doesn’t have one (i.e. is a session cookie).

Returns:

  • (Time, NilClass)

    Expiration ‘Time` of the cookie or `nil` if it doesn’t have one (i.e. is a session cookie).



122
123
124
# File 'lib/arachni/element/cookie.rb', line 122

def expires_at
    @data[:expires]
end

#http_only?Bool

Indicates whether the cookie is safe from modification from client-side code.

Returns:

  • (Bool)


106
107
108
# File 'lib/arachni/element/cookie.rb', line 106

def http_only?
    @data[:httponly] == true
end

#inputs=(inputs) ⇒ Object

Examples:

p c = Cookie.from_set_cookie( 'http://owner-url.com', 'session=stuffstuffstuff' ).first
#=> ["session=stuffstuffstuff"]

p c.inputs
#=> {"session"=>"stuffstuffstuff"}

p c.inputs = { 'new-name' => 'new-value' }
#=> {"new-name"=>"new-value"}

p c
#=> new-name=new-value

Parameters:

  • inputs (Hash)

    Sets inputs.



163
164
165
166
167
168
169
170
171
172
173
174
175
# File 'lib/arachni/element/cookie.rb', line 163

def inputs=( inputs )
    k = inputs.keys.first.to_s
    v = inputs.values.first.to_s

    @data[:name]  = k
    @data[:value] = v

    if k.to_s.empty?
        super( {} )
    else
        super( { k => v } )
    end
end

#respond_to?(*args) ⇒ Bool

Used by #method_missing to determine if it should process the call.

Returns:

  • (Bool)


237
238
239
# File 'lib/arachni/element/cookie.rb', line 237

def respond_to?( *args )
    (@data && @data.include?( args.first )) || super
end

#secure?Bool

Indicates whether the cookie must be only sent over an encrypted channel.

Returns:

  • (Bool)


99
100
101
# File 'lib/arachni/element/cookie.rb', line 99

def secure?
    @data[:secure] == true
end

#session?Bool

Indicates whether the cookie is to be discarded at the end of the session.

Doesn’t play a role during the scan but it can provide useful info to checks and such.

Returns:

  • (Bool)


115
116
117
# File 'lib/arachni/element/cookie.rb', line 115

def session?
    @data[:expires].nil?
end

#simpleHash

Returns Simple representation of the cookie as a hash – with the cookie name as ‘key` and the cookie value as `value`.

Examples:

p Cookie.from_set_cookie( 'http://owner-url.com', 'session=stuffstuffstuff' ).first.simple
#=> {"session"=>"stuffstuffstuff"}

Returns:

  • (Hash)

    Simple representation of the cookie as a hash – with the cookie name as ‘key` and the cookie value as `value`.



144
145
146
# File 'lib/arachni/element/cookie.rb', line 144

def simple
    self.inputs.dup
end

#to_rpc_dataObject



271
272
273
274
275
276
277
278
279
280
281
# File 'lib/arachni/element/cookie.rb', line 271

def to_rpc_data
    h = super
    if expires_at
        h.merge(
           'initialization_options' => h['initialization_options'].merge( expires: expires_at.to_s ),
           'data'                   => h['data'].merge( expires: expires_at.to_s )
        )
    else
        h
    end
end

#to_sString

Returns To be used in a ‘Cookie` HTTP request header.

Returns:

  • (String)

    To be used in a ‘Cookie` HTTP request header.



243
244
245
# File 'lib/arachni/element/cookie.rb', line 243

def to_s
    "#{encode( name, :name )}=#{encode( value )}"
end

Returns Converts self to a ‘Set-Cookie` string.

Returns:

  • (String)

    Converts self to a ‘Set-Cookie` string.



249
250
251
252
253
254
255
256
257
258
259
# File 'lib/arachni/element/cookie.rb', line 249

def to_set_cookie
    set_cookie = "#{self.to_s}; "
    set_cookie << @data.map do |k, v|
        next if !v || !self.class.keep_for_set_cookie.include?( k )
        "#{k.capitalize}=#{v}"
    end.compact.join( '; ' )

    set_cookie << '; Secure'   if secure?
    set_cookie << '; HttpOnly' if http_only?
    set_cookie
end