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 collapse

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.



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
84
85
# File 'lib/arachni/element/cookie.rb', line 54

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.



233
234
235
236
# File 'lib/arachni/element/cookie.rb', line 233

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

Instance Attribute Details

#dataObject (readonly)

Returns the value of attribute data.



43
44
45
# File 'lib/arachni/element/cookie.rb', line 43

def data
  @data
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:



484
485
486
# File 'lib/arachni/element/cookie.rb', line 484

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:



468
469
470
471
472
473
# File 'lib/arachni/element/cookie.rb', line 468

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)


349
350
351
352
# File 'lib/arachni/element/cookie.rb', line 349

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:



377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
# File 'lib/arachni/element/cookie.rb', line 377

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:



311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
# File 'lib/arachni/element/cookie.rb', line 311

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


407
408
409
410
411
412
413
414
# File 'lib/arachni/element/cookie.rb', line 407

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:



362
363
364
365
# File 'lib/arachni/element/cookie.rb', line 362

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

.from_rpc_data(data) ⇒ Object



290
291
292
293
294
295
296
297
298
299
# File 'lib/arachni/element/cookie.rb', line 290

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:



424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
# File 'lib/arachni/element/cookie.rb', line 424

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:



450
451
452
453
454
455
456
# File 'lib/arachni/element/cookie.rb', line 450

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


488
489
490
491
492
493
494
495
496
497
498
499
# File 'lib/arachni/element/cookie.rb', line 488

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:



272
273
274
# File 'lib/arachni/element/cookie.rb', line 272

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

#domDOM

Returns:



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

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



215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
# File 'lib/arachni/element/cookie.rb', line 215

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 OptionGroups::Audit#cookies_extensively option.

Parameters:

Yields:

  • (elem)
  • (mutation)

    Each generated mutation.

Yield Parameters:

  • (Mutable)

See Also:



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

def each_mutation( payload, opts = {}, &block )
    opts        = opts.dup
    flip        = opts.delete( :param_flip )
    extensively = opts[:extensively]
    extensively = Arachni::Options.audit.cookies_extensively? if extensively.nil?

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

        next if !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 name 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:



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

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)


134
135
136
# File 'lib/arachni/element/cookie.rb', line 134

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



124
125
126
# File 'lib/arachni/element/cookie.rb', line 124

def expires_at
    @data[:expires]
end

#http_only?Bool

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

Returns:

  • (Bool)


108
109
110
# File 'lib/arachni/element/cookie.rb', line 108

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.



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

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)


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

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)


101
102
103
# File 'lib/arachni/element/cookie.rb', line 101

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)


117
118
119
# File 'lib/arachni/element/cookie.rb', line 117

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



146
147
148
# File 'lib/arachni/element/cookie.rb', line 146

def simple
    self.inputs.dup
end

#to_rpc_dataObject



276
277
278
279
280
281
282
283
284
285
286
# File 'lib/arachni/element/cookie.rb', line 276

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.



248
249
250
# File 'lib/arachni/element/cookie.rb', line 248

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.



254
255
256
257
258
259
260
261
262
263
264
# File 'lib/arachni/element/cookie.rb', line 254

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