Class: Arachni::Element::Cookie

Inherits:
Base show all
Includes:
Arachni::Element::Capabilities::Analyzable, Arachni::Element::Capabilities::Auditable, Arachni::Element::Capabilities::Inputtable, Arachni::Element::Capabilities::Mutable, Arachni::Element::Capabilities::Submittable, Arachni::Element::Capabilities::WithDOM, Arachni::Element::Capabilities::WithSource
Defined in:
lib/arachni/element/cookie.rb,
lib/arachni/element/cookie/dom.rb,
lib/arachni/element/cookie/capabilities/mutable.rb,
lib/arachni/element/cookie/capabilities/with_dom.rb,
lib/arachni/element/cookie/capabilities/inputtable.rb

Overview

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

Author:

Defined Under Namespace

Modules: Capabilities Classes: DOM

Constant Summary collapse

ENCODE_CHARACTERS =
['+', ';', '%', "\0", '&', ' ', '"', "\n", "\r"]
ENCODE_CHARACTERS_LIST =
ENCODE_CHARACTERS.join
ENCODE_CHARACTERS_IN_NAME =
ENCODE_CHARACTERS + ['=']
ENCODE_CHARACTERS_IN_NAME_LIST =
ENCODE_CHARACTERS_IN_NAME.join
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::Mutable

Arachni::Element::Capabilities::Mutable::EXTRA_NAME, Arachni::Element::Capabilities::Mutable::FUZZ_NAME, Arachni::Element::Capabilities::Mutable::FUZZ_NAME_VALUE, Arachni::Element::Capabilities::Mutable::MUTATION_OPTIONS

Constants included from Arachni::Element::Capabilities::Inputtable

Arachni::Element::Capabilities::Inputtable::INPUTTABLE_CACHE

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

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

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

Arachni::Element::Capabilities::Analyzable::Timeout::TIMEOUT_OPTIONS

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

Arachni::Element::Capabilities::Analyzable::Signature::SIGNATURE_CACHE, Arachni::Element::Capabilities::Analyzable::Signature::SIGNATURE_OPTIONS

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

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

Constants inherited from Base

Base::MAX_SIZE

Instance Attribute Summary collapse

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::WithDOM

#dom, #skip_dom

Attributes included from Arachni::Element::Capabilities::WithSource

#source

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

#differential_analysis_options

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

#timing_attack_remark_data

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

#audit_options

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

#auditor

Attributes inherited from Base

#initialization_options, #page

Class Method Summary collapse

Instance Method Summary collapse

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

#affected_input_value, #affected_input_value=, #dup, #each_mutation, #immutables, #inspect, #mutation?, #mutations, #reset, #switch_method, #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 Arachni::Element::Capabilities::WithDOM

#dup, #skip_dom?

Methods included from Arachni::Element::Capabilities::WithSource

#dup, #to_h

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

has_timeout_candidates?, reset, timeout_audit_run

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

#differential_analysis, #dup, reset

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

add_phase_2_candidate, candidates_include?, deduplicate, deduplicate?, do_not_deduplicate, #dup, #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::Signature

#signature_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 Utilities

#available_port, #bytes_to_kilobytes, #bytes_to_megabytes, #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, #full_and_absolute_url?, #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?, #regexp_array_match, #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::Submittable

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

Methods inherited from Base

#==, #action, #dup, #hash, #id, #marshal_dump, #marshal_load, #persistent_hash, #prepare_for_report, #reset, #to_h, #to_hash, too_big?, 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.



72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
# File 'lib/arachni/element/cookie.rb', line 72

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



158
159
160
161
# File 'lib/arachni/element/cookie.rb', line 158

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.



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

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:



455
456
457
# File 'lib/arachni/element/cookie.rb', line 455

def decode( str )
    Form.decode str
end

.encode(str, name = false) ⇒ 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=%00+"

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

Parameters:

Returns:



422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
# File 'lib/arachni/element/cookie.rb', line 422

def encode( str, name = false )
    str = str.to_s

    return str if !(name ? ENCODE_CHARACTERS_IN_NAME : ENCODE_CHARACTERS).
        find { |c| str.include? c }

    # Instead of just encoding everything we do this selectively because:
    #
    #  * Some webapps don't actually decode some cookies, they just get
    #    the raw value, so if we encode something may break.
    #  * We need to encode spaces as '+' because of the above.
    #    Since we decode values, any un-encoded '+' will be converted
    #    to spaces, and in order to send back a value that the server
    #    expects we use '+' for spaces.

    s = ::URI.encode(
        str,
        name ? ENCODE_CHARACTERS_IN_NAME_LIST :
            ENCODE_CHARACTERS_LIST
    )
    s.gsub!( '%20', '+' )
    s
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)


282
283
284
285
# File 'lib/arachni/element/cookie.rb', line 282

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:



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

def from_document( url, document )
    if !document.is_a?( Nokogiri::HTML::Document )
        document = document.to_s

        return [] if !(document =~ /set-cookie/i )

        document = Nokogiri::HTML( document )
    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:



244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
# File 'lib/arachni/element/cookie.rb', line 244

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


337
338
339
340
341
342
343
344
# File 'lib/arachni/element/cookie.rb', line 337

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:



295
296
297
298
# File 'lib/arachni/element/cookie.rb', line 295

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

.from_rpc_data(data) ⇒ Object



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

def from_rpc_data( data )
    if data['initialization_options']['expires']
        data['initialization_options']['expires'] =
            Time.parse( data['initialization_options']['expires'] )
    end

    if data['data']['expires']
        data['data']['expires'] = Time.parse( data['data']['expires'] )
    end

    data['data'] = data['data'].my_symbolize_keys(false)

    super data
end

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

Parameters:

Returns:



354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
# File 'lib/arachni/element/cookie.rb', line 354

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 )

        if too_big?( cookie.value )
            cookie_hash['value'] = ''
        else
            cookie_hash['value'] = decode( cookie.value )
        end

        new( { url: url, source: str }.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:



385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
# File 'lib/arachni/element/cookie.rb', line 385

def from_string( url, string )
    return [] if string.empty?

    string.split( ';' ).map do |cookie_pair|
        cookie_pair.strip!

        k, v = *cookie_pair.split( '=', 2 )

        v = '' if too_big?( v )

        new(
            url:    url,
            source: cookie_pair,
            inputs: { decode( k ) => value_to_v0( v ) }
        )
    end.flatten.compact
end


459
460
461
462
463
464
465
466
467
468
469
470
# File 'lib/arachni/element/cookie.rb', line 459

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

.value_to_v0(value) ⇒ Object



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

def value_to_v0( value )
    return '' if !value

    value.start_with?( '"' ) && value.end_with?( '"' ) ?
        value[1...-1] : decode( value )
end

Instance Method Details

#decode(str) ⇒ Object

See Also:



197
198
199
# File 'lib/arachni/element/cookie.rb', line 197

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

#encode(*args) ⇒ Object

See Also:



192
193
194
# File 'lib/arachni/element/cookie.rb', line 192

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)


141
142
143
# File 'lib/arachni/element/cookie.rb', line 141

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



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

def expires_at
    @data[:expires]
end

#http_only?Bool

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

Returns:

  • (Bool)


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

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

#respond_to?(*args) ⇒ Bool

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

Returns:

  • (Bool)


167
168
169
# File 'lib/arachni/element/cookie.rb', line 167

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)


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

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)


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

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



153
154
155
# File 'lib/arachni/element/cookie.rb', line 153

def simple
    self.inputs.dup
end

#to_rpc_dataObject



201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
# File 'lib/arachni/element/cookie.rb', line 201

def to_rpc_data
    h = super

    if h['initialization_options']['expires']
        h['initialization_options']['expires'] =
            h['initialization_options']['expires'].to_s
    end

    h['data'] = h['data'].my_stringify_keys(false)
    if h['data']['expires']
        h['data']['expires'] = h['data']['expires'].to_s
    end

    h
end

#to_sString

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

Returns:

  • (String)

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



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

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

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

Returns:

  • (String)

    Converts self to a ‘Set-Cookie` string.



179
180
181
182
183
184
185
186
187
188
189
# File 'lib/arachni/element/cookie.rb', line 179

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