Class: Arachni::Element::Cookie

Inherits:
Base show all
Includes:
Arachni::Element::Capabilities::Analyzable, Arachni::Element::Capabilities::Inputtable, Arachni::Element::Capabilities::Mutable, 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

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

Arachni::Element::Capabilities::Analyzable::Taint::TAINT_CACHE, Arachni::Element::Capabilities::Analyzable::Taint::TAINT_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::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::Submittable

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

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



64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
# File 'lib/arachni/element/cookie.rb', line 64

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.



150
151
152
153
# File 'lib/arachni/element/cookie.rb', line 150

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.



53
54
55
# File 'lib/arachni/element/cookie.rb', line 53

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:



425
426
427
# File 'lib/arachni/element/cookie.rb', line 425

def decode( str )
    ::URI.decode_www_form_component str.to_s
end

.encode(str) ⇒ 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%20"

Parameters:

Returns:



412
413
414
# File 'lib/arachni/element/cookie.rb', line 412

def encode( str )
    Arachni::HTTP::Request.encode( str )
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)


274
275
276
277
# File 'lib/arachni/element/cookie.rb', line 274

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:



302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
# File 'lib/arachni/element/cookie.rb', line 302

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:



236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
# File 'lib/arachni/element/cookie.rb', line 236

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


329
330
331
332
333
334
335
336
# File 'lib/arachni/element/cookie.rb', line 329

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:



287
288
289
290
# File 'lib/arachni/element/cookie.rb', line 287

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

.from_rpc_data(data) ⇒ Object



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

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:



346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
# File 'lib/arachni/element/cookie.rb', line 346

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:



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

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


429
430
431
432
433
434
435
436
437
438
439
440
# File 'lib/arachni/element/cookie.rb', line 429

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



395
396
397
398
399
400
# File 'lib/arachni/element/cookie.rb', line 395

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:



189
190
191
# File 'lib/arachni/element/cookie.rb', line 189

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

#encode(*args) ⇒ Object

See Also:



184
185
186
# File 'lib/arachni/element/cookie.rb', line 184

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)


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

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



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

def expires_at
    @data[:expires]
end

#http_only?Bool

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

Returns:

  • (Bool)


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

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)


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

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)


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

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)


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

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



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

def simple
    self.inputs.dup
end

#to_rpc_dataObject



193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
# File 'lib/arachni/element/cookie.rb', line 193

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.



165
166
167
# File 'lib/arachni/element/cookie.rb', line 165

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

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

Returns:

  • (String)

    Converts self to a ‘Set-Cookie` string.



171
172
173
174
175
176
177
178
179
180
181
# File 'lib/arachni/element/cookie.rb', line 171

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