Land #14831, Add CookieJar support to http_client
This commit is contained in:
commit
6c6d7699ed
|
@ -21,6 +21,7 @@ PATH
|
|||
faye-websocket
|
||||
filesize
|
||||
hrr_rb_ssh (= 0.3.0.pre2)
|
||||
http-cookie
|
||||
irb
|
||||
jsobfu
|
||||
json
|
||||
|
@ -158,6 +159,8 @@ GEM
|
|||
dnsruby (1.61.5)
|
||||
simpleidn (~> 0.1)
|
||||
docile (1.3.5)
|
||||
domain_name (0.5.20190701)
|
||||
unf (>= 0.0.5, < 1.0.0)
|
||||
ed25519 (1.2.4)
|
||||
em-http-request (1.1.7)
|
||||
addressable (>= 2.3.4)
|
||||
|
@ -193,6 +196,8 @@ GEM
|
|||
hashery (2.1.2)
|
||||
hrr_rb_ssh (0.3.0.pre2)
|
||||
ed25519 (~> 1.2)
|
||||
http-cookie (1.0.3)
|
||||
domain_name (~> 0.5)
|
||||
http_parser.rb (0.6.0)
|
||||
i18n (1.8.10)
|
||||
concurrent-ruby (~> 1.0)
|
||||
|
|
|
@ -0,0 +1,175 @@
|
|||
require 'http/cookie'
|
||||
|
||||
module Msf
|
||||
class Exploit
|
||||
class Remote
|
||||
module HTTP
|
||||
# Acts as a wrapper for the 3rd party Cookie (http-cookie)
|
||||
class HttpCookie
|
||||
include Comparable
|
||||
|
||||
def initialize(name, value = nil, **attr_hash)
|
||||
if name.is_a?(::HTTP::Cookie)
|
||||
@cookie = name
|
||||
elsif name && value && attr_hash
|
||||
@cookie = ::HTTP::Cookie.new(name, value, **attr_hash)
|
||||
elsif name && value
|
||||
@cookie = ::HTTP::Cookie.new(name, value)
|
||||
else
|
||||
@cookie = ::HTTP::Cookie.new(name)
|
||||
end
|
||||
end
|
||||
|
||||
def name
|
||||
@cookie.name
|
||||
end
|
||||
|
||||
def name=(name)
|
||||
@cookie.name = name.to_s
|
||||
end
|
||||
|
||||
def value
|
||||
@cookie.value
|
||||
end
|
||||
|
||||
def value=(value)
|
||||
if value.nil? || value.is_a?(String)
|
||||
@cookie.value = value
|
||||
else
|
||||
@cookie.value = value.to_s
|
||||
end
|
||||
end
|
||||
|
||||
def max_age
|
||||
@cookie.max_age
|
||||
end
|
||||
|
||||
def max_age=(max_age)
|
||||
if max_age.nil? || max_age.is_a?(Integer)
|
||||
@cookie.max_age = max_age
|
||||
else
|
||||
@cookie.max_age = Integer(max_age)
|
||||
end
|
||||
end
|
||||
|
||||
def expires
|
||||
@cookie.expires
|
||||
end
|
||||
|
||||
def expires=(expires)
|
||||
if expires.nil? || expires.is_a?(Time)
|
||||
@cookie.expires = expires
|
||||
else
|
||||
t = Time.parse(expires)
|
||||
@cookie.expires = t
|
||||
end
|
||||
end
|
||||
|
||||
def expired?(time = Time.now)
|
||||
@cookie.expired?(time)
|
||||
end
|
||||
|
||||
def path
|
||||
@cookie.path
|
||||
end
|
||||
|
||||
def path=(path)
|
||||
if path.nil? || path.is_a?(String)
|
||||
@cookie.path = path
|
||||
else
|
||||
@cookie.path = path.to_s
|
||||
end
|
||||
end
|
||||
|
||||
def secure
|
||||
@cookie.secure
|
||||
end
|
||||
|
||||
def secure=(secure)
|
||||
@cookie.secure = !!secure
|
||||
end
|
||||
|
||||
def httponly
|
||||
@cookie.httponly
|
||||
end
|
||||
|
||||
def httponly=(httponly)
|
||||
@cookie.httponly = !!httponly
|
||||
end
|
||||
|
||||
def domain
|
||||
@cookie.domain
|
||||
end
|
||||
|
||||
def domain=(domain)
|
||||
if domain.nil? || domain.is_a?(DomainName)
|
||||
@cookie.domain = domain
|
||||
else
|
||||
@cookie.domain = domain.to_s
|
||||
end
|
||||
end
|
||||
|
||||
def accessed_at
|
||||
@cookie.accessed_at
|
||||
end
|
||||
|
||||
def accessed_at=(time)
|
||||
if time.nil? || time.is_a?(Time)
|
||||
@cookie.accessed_at = time
|
||||
else
|
||||
@cookie.accessed_at = Time.parse(time)
|
||||
end
|
||||
end
|
||||
|
||||
def created_at
|
||||
@cookie.created_at
|
||||
end
|
||||
|
||||
def created_at=(time)
|
||||
if time.nil? || time.is_a?(Time)
|
||||
@cookie.created_at = time
|
||||
else
|
||||
@cookie.created_at = Time.parse(time)
|
||||
end
|
||||
end
|
||||
|
||||
def session?
|
||||
@cookie.session?
|
||||
end
|
||||
|
||||
def acceptable?
|
||||
@cookie.acceptable?
|
||||
end
|
||||
|
||||
# Tests if it is OK to send this cookie to a given `uri`. An
|
||||
# ArgumentError is raised if the cookie's domain is unknown.
|
||||
def valid_for_uri?(uri)
|
||||
return false if uri.nil?
|
||||
raise ArgumentError, 'cannot tell if this cookie is valid as domain is nil' if domain.nil?
|
||||
|
||||
@cookie.valid_for_uri?(uri)
|
||||
end
|
||||
|
||||
# Tests if it is OK to accept this cookie if it is sent from a given
|
||||
# URI/URL, `uri`.
|
||||
def acceptable_from_uri?(uri)
|
||||
return false if uri.nil?
|
||||
|
||||
@cookie.acceptable_from_uri?(uri)
|
||||
end
|
||||
|
||||
def <=>(other)
|
||||
@cookie <=> other
|
||||
end
|
||||
|
||||
# Returns a string for use in the Cookie header, i.e. `name=value`
|
||||
# or `name="value"`.
|
||||
def cookie_value
|
||||
@cookie.cookie_value
|
||||
end
|
||||
alias to_s cookie_value
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
|
@ -0,0 +1,91 @@
|
|||
# 3rd party gems
|
||||
require 'http/cookie_jar/hash_store'
|
||||
require 'http/cookie_jar'
|
||||
require 'http/cookie'
|
||||
|
||||
module Msf
|
||||
class Exploit
|
||||
class Remote
|
||||
module HTTP
|
||||
# Acts as a wrapper for the 3rd party CookieJar (http-cookie)
|
||||
class HttpCookieJar
|
||||
def initialize
|
||||
@cookie_jar = ::HTTP::CookieJar.new({
|
||||
store: HashStoreWithoutAutomaticExpiration
|
||||
})
|
||||
end
|
||||
|
||||
def add(cookie)
|
||||
raise TypeError, "Passed cookie is of class '#{cookie.class}' and not a subclass of '#{Msf::Exploit::Remote::HTTP::HttpCookie}" unless cookie.is_a?(Msf::Exploit::Remote::HTTP::HttpCookie)
|
||||
|
||||
@cookie_jar.add(cookie)
|
||||
self
|
||||
end
|
||||
|
||||
def delete(cookie)
|
||||
return if @cookie_jar.cookies.empty?
|
||||
raise TypeError, "Passed cookie is of class '#{cookie.class}' and not a subclass of '#{Msf::Exploit::Remote::HTTP::HttpCookie}" unless cookie.is_a?(Msf::Exploit::Remote::HTTP::HttpCookie)
|
||||
|
||||
@cookie_jar.delete(cookie)
|
||||
self
|
||||
end
|
||||
|
||||
# Iterates over all cookies that are not expired in no particular
|
||||
# order.
|
||||
def cookies
|
||||
@cookie_jar.cookies
|
||||
end
|
||||
|
||||
def clear
|
||||
@cookie_jar.clear
|
||||
end
|
||||
|
||||
# Removes expired cookies and returns self. If `session` is true,
|
||||
# all session cookies are removed as well.
|
||||
def cleanup(expire_all = false)
|
||||
@cookie_jar.cleanup(expire_all)
|
||||
end
|
||||
|
||||
def empty?
|
||||
@cookie_jar.empty?
|
||||
end
|
||||
|
||||
def parse(set_cookie_header, origin_url, options = nil)
|
||||
::HTTP::Cookie.parse(set_cookie_header, origin_url, options)
|
||||
end
|
||||
|
||||
def parse_and_merge(set_cookie_header, origin_url, options = nil)
|
||||
parsed_cookies = ::HTTP::Cookie.parse(set_cookie_header, origin_url, options)
|
||||
parsed_cookies.each { |c| add(Msf::Exploit::Remote::HTTP::HttpCookie.new(c)) }
|
||||
parsed_cookies
|
||||
end
|
||||
end
|
||||
|
||||
class HashStoreWithoutAutomaticExpiration < ::HTTP::CookieJar::HashStore
|
||||
# On top of iterating over every item in the store, +::HTTP::CookieJar::HashStore+ also deletes any expired cookies
|
||||
# and has the option to filter cookies based on whether they are parent of a passed url.
|
||||
#
|
||||
# We've removed the extraneous features in the overwritten method.
|
||||
# - The deletion of cookies while you're iterating over them complicated simple cookie management. It also
|
||||
# prevented sending expired cookies if needed for an exploit
|
||||
# - Any URL passed for filtering could be resolved to nil if it was improperly formed or resolved to a eTLD,
|
||||
# which was too brittle for our uses
|
||||
def each(uri = nil)
|
||||
raise ArgumentError, "HashStoreWithoutAutomaticExpiration.each doesn't support url filtering" if uri
|
||||
|
||||
synchronize do
|
||||
@jar.each do |_domain, paths|
|
||||
paths.each do |_path, hash|
|
||||
hash.each do |_name, cookie|
|
||||
yield cookie
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
self
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
|
@ -91,7 +91,7 @@ module Exploit::Remote::HttpClient
|
|||
register_autofilter_services(%W{ http https })
|
||||
|
||||
# Initialize an empty cookie jar to keep cookies
|
||||
self.cookie_jar = Set.new
|
||||
self.cookie_jar = Msf::Exploit::Remote::HTTP::HttpCookieJar.new
|
||||
end
|
||||
|
||||
def deregister_http_client_options
|
||||
|
@ -108,7 +108,6 @@ module Exploit::Remote::HttpClient
|
|||
end
|
||||
|
||||
|
||||
#
|
||||
# This method is meant to be overriden in the exploit module to specify a set of regexps to
|
||||
# attempt to match against. A failure to match any of them results in a RuntimeError exception
|
||||
# being raised.
|
||||
|
@ -373,22 +372,34 @@ module Exploit::Remote::HttpClient
|
|||
# Connects to the server, creates a request, sends the request,
|
||||
# reads the response
|
||||
#
|
||||
# If a +Msf::Exploit::Remote::HTTP::HttpCookieJar+ instance is passed in the +opts+ dict under a 'cookie' key, said CookieJar will be used in
|
||||
# the request instead of the module +cookie_jar+
|
||||
#
|
||||
# Passes `opts` through directly to {Rex::Proto::Http::Client#request_cgi}.
|
||||
# Set `opts['keep_cookies']` to keep cookies from responses for reuse in requests.
|
||||
# Cookies returned by the server will be stored in +cookie_jar+
|
||||
#
|
||||
# +expire_cookies+ will control if +cleanup+ is called on any passed +Msf::Exploit::Remote::HTTP::HttpCookieJar+ or the client cookiejar
|
||||
#
|
||||
# @return (see Rex::Proto::Http::Client#send_recv))
|
||||
def send_request_cgi(opts = {}, timeout = 20, disconnect = true)
|
||||
if cookie_jar.any?
|
||||
opts = { 'cookie' => cookie_jar.to_a.join(' ') }.merge(opts)
|
||||
if opts.has_key?('cookie')
|
||||
if opts['cookie'].is_a?(Msf::Exploit::Remote::HTTP::HttpCookieJar)
|
||||
cookie_jar.cleanup unless opts['expire_cookies'] == false
|
||||
opts.merge({ 'cookie' => opts['cookie'].cookies.join('; ') })
|
||||
else
|
||||
opts.merge({ 'cookie' => opts['cookie'].to_s })
|
||||
end
|
||||
elsif !cookie_jar.empty?
|
||||
cookie_jar.cleanup unless opts['expire_cookies'] == false
|
||||
opts = opts.merge({ 'cookie' => cookie_jar.cookies.join('; ') })
|
||||
end
|
||||
|
||||
res = send_request_raw(opts.merge(cgi: true), timeout, disconnect)
|
||||
|
||||
return unless res
|
||||
|
||||
if opts['keep_cookies'] && res.headers['Set-Cookie'].present?
|
||||
# XXX: CGI::Cookie (get_cookies_parsed) is hella broken
|
||||
cookie_jar.merge(res.get_cookies.split(' '))
|
||||
cookie_jar.parse_and_merge(res.headers['Set-Cookie'], "http#{ssl ? 's' : ''}://#{vhost}:#{rport}")
|
||||
end
|
||||
|
||||
res
|
||||
|
@ -890,10 +901,12 @@ module Exploit::Remote::HttpClient
|
|||
}
|
||||
end
|
||||
|
||||
attr_reader :cookie_jar
|
||||
|
||||
protected
|
||||
|
||||
attr_accessor :client
|
||||
attr_accessor :cookie_jar
|
||||
|
||||
private
|
||||
attr_writer :cookie_jar
|
||||
end
|
||||
end
|
||||
|
|
|
@ -117,6 +117,8 @@ Gem::Specification.new do |spec|
|
|||
if Gem::Version.new(RUBY_VERSION) >= Gem::Version.new('2.3.0')
|
||||
spec.add_runtime_dependency 'xmlrpc'
|
||||
end
|
||||
# Gem for handling Cookies
|
||||
spec.add_runtime_dependency 'http-cookie'
|
||||
|
||||
#
|
||||
# File Parsing Libraries
|
||||
|
|
|
@ -123,7 +123,8 @@ class MetasploitModule < Msf::Exploit::Remote
|
|||
def check
|
||||
res = send_request_cgi(
|
||||
'method' => 'GET',
|
||||
'uri' => normalize_uri(target_uri.path)
|
||||
'uri' => normalize_uri(target_uri.path),
|
||||
'keep_cookies' => true
|
||||
)
|
||||
unless res
|
||||
return CheckCode::Unknown('Target did not respond to check.')
|
||||
|
@ -198,7 +199,8 @@ class MetasploitModule < Msf::Exploit::Remote
|
|||
vprint_status('Get "csrf" value')
|
||||
res = send_request_cgi(
|
||||
'method' => 'GET',
|
||||
'uri' => normalize_uri(uri)
|
||||
'uri' => normalize_uri(uri),
|
||||
'keep_cookies' => true
|
||||
)
|
||||
unless res
|
||||
fail_with(Failure::Unreachable, 'Unable to get the CSRF token')
|
||||
|
@ -240,7 +242,7 @@ class MetasploitModule < Msf::Exploit::Remote
|
|||
def gitea_create_repo
|
||||
uri = normalize_uri(datastore['TARGETURI'], '/repo/create')
|
||||
|
||||
res = send_request_cgi('method' => 'GET', 'uri' => uri)
|
||||
res = send_request_cgi('method' => 'GET', 'uri' => uri, 'keep_cookies' => true)
|
||||
unless res
|
||||
fail_with(Failure::Unreachable, "Unable to reach #{uri}")
|
||||
end
|
||||
|
@ -305,7 +307,7 @@ class MetasploitModule < Msf::Exploit::Remote
|
|||
uri = normalize_uri(datastore['USERNAME'], @repo_name, '/_new/master')
|
||||
filename = "#{Rex::Text.rand_text_alpha(4..8)}.txt"
|
||||
|
||||
res = send_request_cgi('method' => 'GET', 'uri' => uri)
|
||||
res = send_request_cgi('method' => 'GET', 'uri' => uri, 'keep_cookies' => true)
|
||||
unless res
|
||||
fail_with(Failure::Unreachable, "Unable to reach #{uri}")
|
||||
end
|
||||
|
@ -332,35 +334,6 @@ class MetasploitModule < Msf::Exploit::Remote
|
|||
nil
|
||||
end
|
||||
|
||||
# Hook the HTTP client method to add specific cookie management logic
|
||||
def send_request_cgi(opts, timeout = 20)
|
||||
res = super
|
||||
|
||||
return unless res
|
||||
|
||||
# HTTP client does not handle cookies with the same name correctly. It adds
|
||||
# them instead of substituing the old value with the new one.
|
||||
unless res.get_cookies.empty?
|
||||
cookie_jar_hash = cookie_jar_to_hash
|
||||
cookies_from_response = cookie_jar_to_hash(res.get_cookies.split(' '))
|
||||
cookie_jar_hash.merge!(cookies_from_response)
|
||||
cookie_jar_updated = cookie_jar_hash.each_with_object(Set.new) do |cookie, set|
|
||||
set << "#{cookie[0]}=#{cookie[1]}"
|
||||
end
|
||||
cookie_jar.clear
|
||||
cookie_jar.merge(cookie_jar_updated)
|
||||
end
|
||||
|
||||
res
|
||||
end
|
||||
|
||||
def cookie_jar_to_hash(jar = cookie_jar)
|
||||
jar.each_with_object({}) do |cookie, cookie_hash|
|
||||
name, value = cookie.split('=')
|
||||
cookie_hash[name] = value
|
||||
end
|
||||
end
|
||||
|
||||
def cleanup
|
||||
super
|
||||
return unless @need_cleanup
|
||||
|
|
|
@ -57,19 +57,20 @@ class MetasploitModule < Msf::Exploit::Remote
|
|||
class GitLabClient
|
||||
def initialize(http_client)
|
||||
@http_client = http_client
|
||||
@cookie_jar = {}
|
||||
end
|
||||
|
||||
def sign_in(username, password)
|
||||
@http_client.cookie_jar.clear
|
||||
|
||||
sign_in_path = '/users/sign_in'
|
||||
csrf_token = extract_csrf_token(
|
||||
path: sign_in_path,
|
||||
regex: %r{action="/users/sign_in".*name="authenticity_token"\s+value="([^"]+)"}
|
||||
)
|
||||
res = http_client.send_request_cgi({
|
||||
res = @http_client.send_request_cgi({
|
||||
'method' => 'POST',
|
||||
'uri' => '/users/sign_in',
|
||||
'cookie' => cookie,
|
||||
'keep_cookies' => true,
|
||||
'vars_post' => {
|
||||
'utf8' => '✓',
|
||||
'authenticity_token' => csrf_token,
|
||||
|
@ -89,8 +90,6 @@ class MetasploitModule < Msf::Exploit::Remote
|
|||
raise GitLabClientException, 'Login not successful. The account may need activated. Verify login works manually.'
|
||||
end
|
||||
|
||||
merge_cookie_jar(res)
|
||||
|
||||
current_user
|
||||
end
|
||||
|
||||
|
@ -98,7 +97,7 @@ class MetasploitModule < Msf::Exploit::Remote
|
|||
res = http_client.send_request_cgi({
|
||||
'method' => 'GET',
|
||||
'uri' => '/api/v4/user',
|
||||
'cookie' => cookie
|
||||
'keep_cookies' => true
|
||||
})
|
||||
|
||||
if res.nil? || res.body.nil?
|
||||
|
@ -114,7 +113,7 @@ class MetasploitModule < Msf::Exploit::Remote
|
|||
res = http_client.send_request_cgi({
|
||||
'method' => 'GET',
|
||||
'uri' => '/api/v4/version',
|
||||
'cookie' => cookie
|
||||
'keep_cookies' => true
|
||||
})
|
||||
|
||||
if res.nil? || res.body.nil?
|
||||
|
@ -138,7 +137,7 @@ class MetasploitModule < Msf::Exploit::Remote
|
|||
res = http_client.send_request_cgi({
|
||||
'method' => 'POST',
|
||||
'uri' => create_project_path,
|
||||
'cookie' => cookie,
|
||||
'keep_cookies' => true,
|
||||
'vars_post' => {
|
||||
'utf8' => '✓',
|
||||
'authenticity_token' => csrf_token,
|
||||
|
@ -159,8 +158,6 @@ class MetasploitModule < Msf::Exploit::Remote
|
|||
raise GitLabClientException, "Unexpected HTTP #{res.code} response."
|
||||
end
|
||||
|
||||
merge_cookie_jar(res)
|
||||
|
||||
project(user: user, project_name: project_name)
|
||||
end
|
||||
|
||||
|
@ -169,7 +166,7 @@ class MetasploitModule < Msf::Exploit::Remote
|
|||
res = http_client.send_request_cgi({
|
||||
'method' => 'GET',
|
||||
'uri' => project_path,
|
||||
'cookie' => cookie
|
||||
'keep_cookies' => true
|
||||
})
|
||||
if res.nil? || res.body.nil?
|
||||
raise GitLabClientException, 'Empty response. Please validate RHOST'
|
||||
|
@ -198,7 +195,7 @@ class MetasploitModule < Msf::Exploit::Remote
|
|||
res = http_client.send_request_cgi({
|
||||
'method' => 'POST',
|
||||
'uri' => delete_project_path,
|
||||
'cookie' => cookie,
|
||||
'keep_cookies' => true,
|
||||
'vars_post' => {
|
||||
'utf8' => '✓',
|
||||
'authenticity_token' => csrf_token,
|
||||
|
@ -226,7 +223,7 @@ class MetasploitModule < Msf::Exploit::Remote
|
|||
res = http_client.send_request_cgi({
|
||||
'method' => 'POST',
|
||||
'uri' => create_issue_path,
|
||||
'cookie' => cookie,
|
||||
'keep_cookies' => true,
|
||||
'vars_post' => {
|
||||
'utf8' => '✓',
|
||||
'authenticity_token' => csrf_token,
|
||||
|
@ -246,7 +243,6 @@ class MetasploitModule < Msf::Exploit::Remote
|
|||
raise GitLabClientException, "Unexpected HTTP #{res.code} response."
|
||||
end
|
||||
|
||||
merge_cookie_jar(res)
|
||||
issue_id = res.body[%r{You are being <a href="http://.*#{create_issue_path}/(\d+)">redirected</a>}, 1]
|
||||
|
||||
issue.merge({
|
||||
|
@ -267,7 +263,7 @@ class MetasploitModule < Msf::Exploit::Remote
|
|||
res = http_client.send_request_cgi({
|
||||
'method' => 'POST',
|
||||
'uri' => move_issue_path,
|
||||
'cookie' => cookie,
|
||||
'keep_cookies' => true,
|
||||
'ctype' => 'application/json',
|
||||
'headers' => {
|
||||
'X-CSRF-Token' => csrf_token,
|
||||
|
@ -296,7 +292,7 @@ class MetasploitModule < Msf::Exploit::Remote
|
|||
res = http_client.send_request_cgi({
|
||||
'method' => 'GET',
|
||||
'uri' => "#{project['path']}/#{path}",
|
||||
'cookie' => cookie
|
||||
'keep_cookies' => true
|
||||
})
|
||||
|
||||
if res.nil? || res.body.nil?
|
||||
|
@ -316,7 +312,7 @@ class MetasploitModule < Msf::Exploit::Remote
|
|||
res = http_client.send_request_cgi({
|
||||
'method' => 'GET',
|
||||
'uri' => path,
|
||||
'cookie' => cookie
|
||||
'keep_cookies' => true
|
||||
})
|
||||
|
||||
if res.nil? || res.body.nil?
|
||||
|
@ -325,7 +321,6 @@ class MetasploitModule < Msf::Exploit::Remote
|
|||
raise GitLabClientException, "Unexpected HTTP #{res.code} response."
|
||||
end
|
||||
|
||||
merge_cookie_jar(res)
|
||||
token = res.body[regex, 1]
|
||||
if token.nil?
|
||||
raise GitLabClientException, 'Could not successfully extract CSRF token'
|
||||
|
@ -333,17 +328,6 @@ class MetasploitModule < Msf::Exploit::Remote
|
|||
|
||||
token
|
||||
end
|
||||
|
||||
def cookie
|
||||
return nil if @cookie_jar.empty?
|
||||
|
||||
@cookie_jar.map { |(k, v)| "#{k}=#{v}" }.join(' ')
|
||||
end
|
||||
|
||||
def merge_cookie_jar(res)
|
||||
new_cookies = Hash[res.get_cookies.split(' ').map { |x| x.split('=') }]
|
||||
@cookie_jar.merge!(new_cookies)
|
||||
end
|
||||
end
|
||||
|
||||
def initialize(info = {})
|
||||
|
@ -554,11 +538,12 @@ class MetasploitModule < Msf::Exploit::Remote
|
|||
secret_key_base = read_secret_key_base
|
||||
|
||||
payload = build_payload
|
||||
signed_cookie = sign_payload(secret_key_base, payload)
|
||||
signed_cookie_value = sign_payload(secret_key_base, payload)
|
||||
|
||||
send_request_cgi({
|
||||
'uri' => normalize_uri(target_uri.path),
|
||||
'method' => 'GET',
|
||||
'cookie' => "experimentation_subject_id=#{signed_cookie}"
|
||||
'cookie' => "experimentation_subject_id=#{signed_cookie_value}"
|
||||
})
|
||||
end
|
||||
end
|
||||
|
|
|
@ -121,7 +121,8 @@ class MetasploitModule < Msf::Exploit::Remote
|
|||
def check
|
||||
res = send_request_cgi(
|
||||
'method' => 'GET',
|
||||
'uri' => normalize_uri(target_uri.path)
|
||||
'uri' => normalize_uri(target_uri.path),
|
||||
'keep_cookies' => true
|
||||
)
|
||||
unless res
|
||||
return CheckCode::Unknown('Target did not respond to check.')
|
||||
|
@ -188,7 +189,8 @@ class MetasploitModule < Msf::Exploit::Remote
|
|||
vprint_status('Get "csrf" value')
|
||||
res = send_request_cgi(
|
||||
'method' => 'GET',
|
||||
'uri' => normalize_uri(uri)
|
||||
'uri' => normalize_uri(uri),
|
||||
'keep_cookies' => true
|
||||
)
|
||||
unless res
|
||||
fail_with(Failure::Unreachable, 'Unable to get the CSRF token')
|
||||
|
@ -230,7 +232,7 @@ class MetasploitModule < Msf::Exploit::Remote
|
|||
def gogs_create_repo
|
||||
uri = normalize_uri(datastore['TARGETURI'], '/repo/create')
|
||||
|
||||
res = send_request_cgi('method' => 'GET', 'uri' => uri)
|
||||
res = send_request_cgi('method' => 'GET', 'uri' => uri, 'keep_cookies' => true)
|
||||
unless res
|
||||
fail_with(Failure::Unreachable, "Unable to reach #{uri}")
|
||||
end
|
||||
|
@ -292,7 +294,7 @@ class MetasploitModule < Msf::Exploit::Remote
|
|||
uri = normalize_uri(datastore['USERNAME'], @repo_name, '/_new/master')
|
||||
filename = "#{Rex::Text.rand_text_alpha(4..8)}.txt"
|
||||
|
||||
res = send_request_cgi('method' => 'GET', 'uri' => uri)
|
||||
res = send_request_cgi('method' => 'GET', 'uri' => uri, 'keep_cookies' => true)
|
||||
unless res
|
||||
fail_with(Failure::Unreachable, "Unable to reach #{uri}")
|
||||
end
|
||||
|
@ -319,35 +321,6 @@ class MetasploitModule < Msf::Exploit::Remote
|
|||
nil
|
||||
end
|
||||
|
||||
# Hook the HTTP client method to add specific cookie management logic
|
||||
def send_request_cgi(opts, timeout = 20)
|
||||
res = super
|
||||
|
||||
return unless res
|
||||
|
||||
# HTTP client does not handle cookies with the same name correctly. It adds
|
||||
# them instead of substituing the old value with the new one.
|
||||
unless res.get_cookies.empty?
|
||||
cookie_jar_hash = cookie_jar_to_hash
|
||||
cookies_from_response = cookie_jar_to_hash(res.get_cookies.split(' '))
|
||||
cookie_jar_hash.merge!(cookies_from_response)
|
||||
cookie_jar_updated = cookie_jar_hash.each_with_object(Set.new) do |cookie, set|
|
||||
set << "#{cookie[0]}=#{cookie[1]}"
|
||||
end
|
||||
cookie_jar.clear
|
||||
cookie_jar.merge(cookie_jar_updated)
|
||||
end
|
||||
|
||||
res
|
||||
end
|
||||
|
||||
def cookie_jar_to_hash(jar = cookie_jar)
|
||||
jar.each_with_object({}) do |cookie, cookie_hash|
|
||||
name, value = cookie.split('=')
|
||||
cookie_hash[name] = value
|
||||
end
|
||||
end
|
||||
|
||||
def cleanup
|
||||
super
|
||||
return unless @need_cleanup
|
||||
|
|
|
@ -132,7 +132,7 @@ class MetasploitModule < Msf::Exploit::Remote
|
|||
return CheckCode::Safe("Target is HorizontCMS with version #{version}")
|
||||
end
|
||||
|
||||
return CheckCode::Appears("Target is HorizontCMS with version #{version}")
|
||||
CheckCode::Appears("Target is HorizontCMS with version #{version}")
|
||||
end
|
||||
|
||||
def login
|
||||
|
@ -142,6 +142,7 @@ class MetasploitModule < Msf::Exploit::Remote
|
|||
end
|
||||
|
||||
# try to authenticate
|
||||
# Cookies from this request will overwrite the cookies currently in the jar from the +check+ method
|
||||
res = send_request_cgi({
|
||||
'method' => 'POST',
|
||||
'uri' => normalize_uri(target_uri.path, 'admin', 'login'),
|
||||
|
@ -163,10 +164,6 @@ class MetasploitModule < Msf::Exploit::Remote
|
|||
fail_with(Failure::UnexpectedReply, 'Unexpected response received while trying to authenticate.')
|
||||
end
|
||||
|
||||
# keep only the newly added cookies, otherwise subsequent requests will fail
|
||||
auth_cookies = cookie_jar.to_a[2..3]
|
||||
self.cookie_jar = auth_cookies.to_set
|
||||
|
||||
# using send_request_cgi! does not work so we have to follow the redirect manually
|
||||
res = send_request_cgi({
|
||||
'method' => 'GET',
|
||||
|
|
|
@ -164,7 +164,7 @@ class MetasploitModule < Msf::Exploit::Remote
|
|||
fail_with(Failure::Unreachable, 'Failed to access OWA login page')
|
||||
end
|
||||
|
||||
unless res.code == 200 && cookie_jar.grep(/^cadata/).any?
|
||||
unless res.code == 200 && cookie_jar.cookies.any? { |cookie| cookie.name.start_with?('cadata') }
|
||||
if res.body.include?('There are too many active sessions connected to this mailbox.')
|
||||
fail_with(Failure::NoAccess, 'Reached active session limit for mailbox')
|
||||
end
|
||||
|
|
|
@ -0,0 +1,247 @@
|
|||
require 'spec_helper'
|
||||
|
||||
RSpec.describe Msf::Exploit::Remote::HTTP::HttpCookieJar do
|
||||
def random_string(min_len = 1, max_len = 12)
|
||||
str = Faker::Alphanumeric.alpha(number: max_len)
|
||||
str[0, rand(min_len..max_len)]
|
||||
end
|
||||
|
||||
def random_cookie
|
||||
Msf::Exploit::Remote::HTTP::HttpCookie.new(
|
||||
random_string,
|
||||
random_string,
|
||||
max_age: Faker::Number.within(range: 1..100),
|
||||
path: '/' + random_string,
|
||||
secure: Faker::Boolean.boolean,
|
||||
httponly: Faker::Boolean.boolean,
|
||||
domain: random_string
|
||||
)
|
||||
end
|
||||
|
||||
let(:cookie_jar) { described_class.new }
|
||||
|
||||
before(:each) do
|
||||
Timecop.freeze(Time.local(2008, 9, 5, 10, 5, 30))
|
||||
end
|
||||
|
||||
after(:each) do
|
||||
Timecop.return
|
||||
end
|
||||
|
||||
describe 'empty?' do
|
||||
it 'will return true when no cookies are in a cookie_jar' do
|
||||
# cookie_jar made in before
|
||||
|
||||
e = cookie_jar.empty?
|
||||
|
||||
expect(e).to eq(true)
|
||||
end
|
||||
|
||||
it 'will return false when a cookie has been added to a cookie_jar' do
|
||||
c = random_cookie
|
||||
|
||||
cookie_jar.add(c)
|
||||
e = cookie_jar.empty?
|
||||
|
||||
expect(e).to eq(false)
|
||||
end
|
||||
end
|
||||
|
||||
describe 'clear' do
|
||||
it 'will make no changes to an empty cookiejar' do
|
||||
# empty cookie_jar made in before
|
||||
|
||||
cookie_jar.clear
|
||||
|
||||
expect(cookie_jar.empty?).to eq(true)
|
||||
end
|
||||
|
||||
it 'will return true when populated cookie_jar has been cleared' do
|
||||
c = random_cookie
|
||||
|
||||
cookie_jar.add(c)
|
||||
cookie_jar.clear
|
||||
|
||||
expect(cookie_jar.empty?).to eq(true)
|
||||
end
|
||||
end
|
||||
|
||||
describe 'cookies' do
|
||||
it 'will return an empty array when no cookies have been added to the jar' do
|
||||
# cookie_jar made in before
|
||||
|
||||
c_array = cookie_jar.cookies
|
||||
|
||||
expect(c_array.class).to eq(Array)
|
||||
expect(c_array.empty?).to eq(true)
|
||||
end
|
||||
|
||||
it 'will return an array of all cookies added to the cookie_jar when called with no url param' do
|
||||
c_array = []
|
||||
Faker::Number.within(range: 1..10).times do
|
||||
c = random_cookie
|
||||
|
||||
c_array.append(c)
|
||||
cookie_jar.add(c)
|
||||
end
|
||||
|
||||
jar_array = cookie_jar.cookies
|
||||
|
||||
expect(c_array.sort).to eq(jar_array.sort)
|
||||
end
|
||||
end
|
||||
|
||||
describe 'add' do
|
||||
it 'unacceptable cookie without a domain will throw an ArgumentError' do
|
||||
c = Msf::Exploit::Remote::HTTP::HttpCookie.new(
|
||||
random_string,
|
||||
random_string,
|
||||
path: '/' + random_string
|
||||
)
|
||||
|
||||
expect do
|
||||
cookie_jar.add(c)
|
||||
end.to raise_error(ArgumentError)
|
||||
end
|
||||
|
||||
it 'unacceptable cookie without a path will throw an ArgumentError' do
|
||||
c = Msf::Exploit::Remote::HTTP::HttpCookie.new(
|
||||
random_string,
|
||||
random_string,
|
||||
domain: random_string
|
||||
)
|
||||
|
||||
expect do
|
||||
cookie_jar.add(c)
|
||||
end.to raise_error(ArgumentError)
|
||||
end
|
||||
|
||||
it 'acceptable cookie added to cookie_jar successfully' do
|
||||
c = random_cookie
|
||||
|
||||
cookie_jar.add(c)
|
||||
|
||||
expect(cookie_jar.cookies[0] == c)
|
||||
end
|
||||
|
||||
it 'acceptable cookie added to cookie_jar containing cookie with the same name, domain, and path will result in an overwrite' do
|
||||
c = random_cookie
|
||||
c_dup = random_cookie
|
||||
c_dup.name = c.name
|
||||
c_dup.domain = c.domain
|
||||
c_dup.path = c.path
|
||||
|
||||
cookie_jar.add(c)
|
||||
cookie_jar.add(c_dup)
|
||||
|
||||
expect(cookie_jar.cookies).to match_array([c_dup])
|
||||
end
|
||||
|
||||
it 'variable not a subclass of ::HttpCookie will raise TypeError' do
|
||||
int = 1
|
||||
|
||||
expect do
|
||||
cookie_jar.add(int)
|
||||
end.to raise_error(TypeError)
|
||||
end
|
||||
end
|
||||
|
||||
describe 'delete' do
|
||||
it 'used on an empty jar will return nil' do
|
||||
# cookie_jar made in before
|
||||
|
||||
n = cookie_jar.delete(random_cookie)
|
||||
|
||||
expect(n).to eq(nil)
|
||||
end
|
||||
|
||||
it 'passed cookie with same name, domain, and path as cookie in jar, will delete cookie in jar' do
|
||||
c = random_cookie
|
||||
c_dup = random_cookie
|
||||
c_dup.name = c.name
|
||||
c_dup.domain = c.domain
|
||||
c_dup.path = c.path
|
||||
|
||||
cookie_jar.add(c)
|
||||
cookie_jar.delete(c_dup)
|
||||
|
||||
expect(cookie_jar.empty?).to eq(true)
|
||||
end
|
||||
|
||||
it 'passed a cookie different name, domain, and path as cookie in jar, will not delete cookie in jar' do
|
||||
c = random_cookie
|
||||
c_dup = random_cookie
|
||||
c_dup.name = c.name + random_string(1, 1)
|
||||
c_dup.domain = c.domain + random_string(1, 1)
|
||||
c_dup.path = c.path + random_string(1, 1)
|
||||
|
||||
cookie_jar.add(c)
|
||||
cookie_jar.delete(c_dup)
|
||||
|
||||
expect(cookie_jar.cookies.length).to eq(1)
|
||||
expect(cookie_jar.cookies[0]).to eql(c)
|
||||
end
|
||||
|
||||
it 'variable not a subclass of ::HttpCookie will not raise TypeError when the cookie_jar is empty' do
|
||||
int = Faker::Number.within(range: 1..100)
|
||||
|
||||
n = cookie_jar.delete(int)
|
||||
|
||||
expect(n).to eq(nil)
|
||||
expect(cookie_jar.empty?).to eq(true)
|
||||
end
|
||||
|
||||
it 'variable not a subclass of ::HttpCookie will raise TypeError when the cookie_jar is not empty' do
|
||||
cookie_jar.add(random_cookie)
|
||||
int = Faker::Number.within(range: 1..100)
|
||||
|
||||
expect do
|
||||
cookie_jar.delete(int)
|
||||
end.to raise_error(TypeError)
|
||||
end
|
||||
end
|
||||
|
||||
describe 'cleanup' do
|
||||
it 'will make no changes to an empty cookiejar' do
|
||||
# empty cookie_jar made in before
|
||||
|
||||
cookie_jar.cleanup
|
||||
|
||||
expect(cookie_jar.empty?).to eq(true)
|
||||
end
|
||||
|
||||
it 'will remove expired cookies with max_age value' do
|
||||
expired_cookies = [random_cookie, random_cookie]
|
||||
expired_cookies[0].max_age = 1
|
||||
expired_cookies[1].max_age = 1
|
||||
expired_cookies[0].created_at = Time.local(2008, 9, 5, 10, 5, 1)
|
||||
expired_cookies[1].created_at = Time.local(2008, 9, 5, 10, 5, 1)
|
||||
cookies = [random_cookie, random_cookie]
|
||||
cookies[0].max_age = 10000
|
||||
cookies[1].max_age = 10000
|
||||
cookies[0].created_at = Time.now
|
||||
cookies[1].created_at = Time.now
|
||||
|
||||
cookies.each { |c| cookie_jar.add(c) }
|
||||
expired_cookies.each { |c| cookie_jar.add(c) }
|
||||
cookie_jar.cleanup
|
||||
|
||||
expect(cookie_jar.cookies).to match_array(cookies)
|
||||
end
|
||||
|
||||
it 'will remove expired cookies with expires value' do
|
||||
expired_cookies = [random_cookie, random_cookie]
|
||||
expired_cookies[0].expires = Time.local(2008, 9, 3, 10, 5, 0)
|
||||
expired_cookies[1].expires = Time.local(2008, 9, 4, 10, 5, 0)
|
||||
cookies = [random_cookie, random_cookie]
|
||||
cookies[0].expires = Time.local(2008, 9, 6, 10, 5, 0)
|
||||
cookies[1].expires = Time.local(2008, 9, 7, 10, 5, 0)
|
||||
|
||||
cookies.each { |c| cookie_jar.add(c) }
|
||||
expired_cookies.each { |c| cookie_jar.add(c) }
|
||||
cookie_jar.cleanup
|
||||
|
||||
expect(cookie_jar.cookies).to match_array(cookies)
|
||||
end
|
||||
end
|
||||
end
|
|
@ -0,0 +1,696 @@
|
|||
require 'spec_helper'
|
||||
require 'faker'
|
||||
|
||||
RSpec.describe Msf::Exploit::Remote::HTTP::HttpCookie do
|
||||
def random_string(min_len = 1, max_len = 12)
|
||||
str = Faker::Alphanumeric.alpha(number: max_len)
|
||||
str[0, rand(min_len..max_len)]
|
||||
end
|
||||
|
||||
let(:cookie) { described_class.new(random_string, random_string) }
|
||||
|
||||
before(:each) do
|
||||
Timecop.freeze(Time.local(2008, 9, 5, 10, 5, 30))
|
||||
end
|
||||
|
||||
after(:each) do
|
||||
Timecop.return
|
||||
end
|
||||
|
||||
describe 'name' do
|
||||
describe 'String' do
|
||||
it 'is assigned to name successfully' do
|
||||
n = random_string
|
||||
|
||||
cookie.name = n
|
||||
|
||||
expect(cookie.name).to eql(n)
|
||||
expect(cookie.name.class).to eql(String)
|
||||
end
|
||||
|
||||
it 'that is empty is passed to name and throws an ArgumentError' do
|
||||
n = ''
|
||||
|
||||
expect do
|
||||
cookie.name = n
|
||||
end.to raise_error(ArgumentError)
|
||||
end
|
||||
end
|
||||
|
||||
describe 'nil' do
|
||||
it 'assigned to name throws an ArgumentError' do
|
||||
n = nil
|
||||
|
||||
expect do
|
||||
cookie.name = n
|
||||
end.to raise_error(ArgumentError)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
describe 'value' do
|
||||
describe 'String' do
|
||||
it 'is assigned to value successfully' do
|
||||
v = random_string
|
||||
|
||||
cookie.value = v
|
||||
|
||||
expect(cookie.value).to eql(v)
|
||||
expect(cookie.value.class).to eql(String)
|
||||
end
|
||||
|
||||
it 'that is empty is passed to value successfully' do
|
||||
v = ''
|
||||
|
||||
cookie.value = v
|
||||
|
||||
expect(cookie.value).to eql(v)
|
||||
expect(cookie.value.class).to eql(String)
|
||||
end
|
||||
end
|
||||
|
||||
describe 'nil' do
|
||||
it 'assigned to value results in it being set to an empty string & expires is set UNIX_EPOCH' do
|
||||
v = nil
|
||||
|
||||
cookie.value = v
|
||||
|
||||
expect(cookie.value).to eql('')
|
||||
expect(cookie.value.class).to eql(String)
|
||||
expect(cookie.expires).to eql(Time.at(0))
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
describe 'max_age' do
|
||||
describe 'Integer' do
|
||||
it 'assigns an integer of 0 to max_age successfully' do
|
||||
int = 0
|
||||
|
||||
cookie.max_age = int
|
||||
|
||||
expect(cookie.max_age).to eql(int)
|
||||
end
|
||||
|
||||
it 'assigns a positive integer to max_age successfully' do
|
||||
pos_val = rand(1..100)
|
||||
|
||||
cookie.max_age = pos_val
|
||||
|
||||
expect(cookie.max_age).to eql(pos_val)
|
||||
end
|
||||
|
||||
it 'assigns a negative integer to max_age successfully' do
|
||||
neg_val = rand(-1..-100)
|
||||
|
||||
cookie.max_age = neg_val
|
||||
|
||||
expect(cookie.max_age).to eql(neg_val)
|
||||
end
|
||||
end
|
||||
|
||||
describe 'String' do
|
||||
it 'assigns a String of 0 to max_age successfully' do
|
||||
str = '0'
|
||||
|
||||
cookie.max_age = str
|
||||
|
||||
expect(cookie.max_age).to eql(str.to_i)
|
||||
end
|
||||
|
||||
it 'assigns a String of a positive Integer to max_age successfully' do
|
||||
pos_val = rand(1..100)
|
||||
|
||||
cookie.max_age = pos_val.to_s
|
||||
|
||||
expect(cookie.max_age).to eql(pos_val)
|
||||
end
|
||||
|
||||
it 'assigns a String of a negative Integer to max_age successfully' do
|
||||
neg_val = rand(-100..-1)
|
||||
|
||||
cookie.max_age = neg_val.to_s
|
||||
|
||||
expect(cookie.max_age).to eql(neg_val)
|
||||
end
|
||||
|
||||
it 'throws an ArgumentError with a String that cannot be converted into an Integer' do
|
||||
invalid_str = random_string
|
||||
|
||||
expect do
|
||||
cookie.max_age = invalid_str
|
||||
end.to raise_error(ArgumentError)
|
||||
end
|
||||
end
|
||||
|
||||
describe 'Complex Object' do
|
||||
it 'throws an TypeError with a Complex Object that cannot be converted into an Integer' do
|
||||
obj = [rand(9), rand(9), rand(9)]
|
||||
|
||||
expect do
|
||||
cookie.max_age = obj
|
||||
end.to raise_error(TypeError)
|
||||
end
|
||||
end
|
||||
|
||||
describe 'nil' do
|
||||
it 'assigns a nil to max_age successfully' do
|
||||
cookie.max_age = nil
|
||||
expect(cookie.max_age).to eql(nil)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
describe 'expires' do
|
||||
describe 'Time' do
|
||||
it 'instance is assigned to expires successfully' do
|
||||
t = Time.now
|
||||
|
||||
cookie.expires = t
|
||||
|
||||
expect(cookie.expires).to eql(t)
|
||||
expect(cookie.expires.class).to eql(Time)
|
||||
end
|
||||
end
|
||||
|
||||
describe 'Non-Time' do
|
||||
it 'which can be parsed to a Time object, will be assigned to expires successfully' do
|
||||
obj = Time.now.to_s
|
||||
|
||||
cookie.expires = obj
|
||||
|
||||
expect(cookie.expires).to eql(Time.parse(obj))
|
||||
end
|
||||
|
||||
it 'which cannot be parsed to a Time object, when assigned to expires, will throw a TypeError' do
|
||||
obj = [rand(9), rand(9), rand(9)]
|
||||
|
||||
expect do
|
||||
cookie.expires = obj
|
||||
end.to raise_error(TypeError)
|
||||
end
|
||||
end
|
||||
|
||||
describe 'nil' do
|
||||
it 'assigned to expires successfully' do
|
||||
cookie.expires = nil
|
||||
expect(cookie.expires).to eql(nil)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
describe 'path' do
|
||||
describe 'String' do
|
||||
it 'beginning with "/" is passed to "path" successfully' do
|
||||
p = '/' + random_string
|
||||
|
||||
cookie.path = p
|
||||
|
||||
expect(cookie.path).to eql(p)
|
||||
expect(cookie.path.class).to eql(String)
|
||||
end
|
||||
|
||||
it 'not beginning with "/" is passed to "path" and is set as "/"' do
|
||||
p = random_string
|
||||
|
||||
cookie.path = p
|
||||
|
||||
expect(cookie.path).to eql('/')
|
||||
expect(cookie.path.class).to eql(String)
|
||||
end
|
||||
|
||||
it 'that is empty is passed to "path" and is set as "/"' do
|
||||
p = ''
|
||||
|
||||
cookie.path = p
|
||||
|
||||
expect(cookie.path).to eql('/')
|
||||
expect(cookie.path.class).to eql(String)
|
||||
end
|
||||
end
|
||||
|
||||
# If the Object A responds to to_str with a truthy Object that responds true to start_with?('/'),
|
||||
# then path is set to A
|
||||
describe 'Complex Object' do
|
||||
it 'which is a kind of String will be passed to path successfully' do
|
||||
clazz = Class.new(String) do
|
||||
end
|
||||
str = "/#{random_string(0)}"
|
||||
my_str = clazz.new(str)
|
||||
|
||||
cookie.path = my_str
|
||||
|
||||
expect(cookie.path).to eql(str)
|
||||
end
|
||||
end
|
||||
|
||||
describe 'nil' do
|
||||
it 'assigned to expired successfully' do
|
||||
n = nil
|
||||
|
||||
cookie.expires = n
|
||||
|
||||
expect(cookie.expires).to eql(n)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
describe 'domain' do
|
||||
describe 'DomainName' do
|
||||
it 'assigned to domain when origin is set will result in a domain based on origin.host' do
|
||||
d = DomainName(random_string)
|
||||
|
||||
cookie.domain = d
|
||||
|
||||
expect(cookie.domain).to eql(d.hostname)
|
||||
end
|
||||
end
|
||||
|
||||
describe 'nil' do
|
||||
it 'assigned to domain when origin is not set will result in a nil domain' do
|
||||
n = nil
|
||||
|
||||
cookie.domain = n
|
||||
|
||||
expect(cookie.domain).to eql(n)
|
||||
end
|
||||
end
|
||||
|
||||
describe 'String' do
|
||||
it 'assigned to domain will be converted to a DomainName and assigned' do
|
||||
s = random_string
|
||||
|
||||
cookie.domain = s
|
||||
|
||||
expect(cookie.domain).to eql(DomainName(s).domain)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
describe 'httponly' do
|
||||
describe 'Truthy' do
|
||||
it 'empty string passed to httponly is set as true' do
|
||||
str = ''
|
||||
|
||||
cookie.httponly = str
|
||||
|
||||
expect(cookie.httponly).to eql(true)
|
||||
end
|
||||
|
||||
it 'populated string passed to httponly is set as true' do
|
||||
str = random_string
|
||||
|
||||
cookie.httponly = str
|
||||
|
||||
expect(cookie.httponly).to eql(true)
|
||||
end
|
||||
|
||||
it 'integer passed to httponly is set as true' do
|
||||
int = rand(0..10)
|
||||
|
||||
cookie.httponly = int
|
||||
|
||||
expect(cookie.httponly).to eql(true)
|
||||
end
|
||||
|
||||
it 'true passed to httponly is set as true' do
|
||||
t = true
|
||||
|
||||
cookie.httponly = t
|
||||
|
||||
expect(cookie.httponly).to eql(true)
|
||||
end
|
||||
end
|
||||
|
||||
describe 'Falsey' do
|
||||
it 'nil passed to httponly is set as false' do
|
||||
n = nil
|
||||
|
||||
cookie.httponly = n
|
||||
|
||||
expect(cookie.httponly).to eql(false)
|
||||
end
|
||||
|
||||
it 'false passed to httponly is set as false' do
|
||||
f = false
|
||||
|
||||
cookie.httponly = f
|
||||
|
||||
expect(cookie.httponly).to eql(false)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
describe 'secure' do
|
||||
describe 'Truthy' do
|
||||
it 'empty string passed to secure is set as true' do
|
||||
str = ''
|
||||
|
||||
cookie.secure = str
|
||||
|
||||
expect(cookie.secure).to eql(true)
|
||||
end
|
||||
|
||||
it 'populated string passed to secure is set as true' do
|
||||
str = random_string
|
||||
|
||||
cookie.secure = str
|
||||
|
||||
expect(cookie.secure).to eql(true)
|
||||
end
|
||||
|
||||
it 'integer passed to secure is set as true' do
|
||||
int = rand(0..10)
|
||||
|
||||
cookie.secure = int
|
||||
|
||||
expect(cookie.secure).to eql(true)
|
||||
end
|
||||
|
||||
it 'true passed to secure is set as true' do
|
||||
t = true
|
||||
|
||||
cookie.secure = t
|
||||
|
||||
expect(cookie.secure).to eql(true)
|
||||
end
|
||||
end
|
||||
|
||||
describe 'Falsey' do
|
||||
it 'nil passed to secure is set as false' do
|
||||
n = nil
|
||||
|
||||
cookie.secure = n
|
||||
|
||||
expect(cookie.secure).to eql(false)
|
||||
end
|
||||
|
||||
it 'false passed to secure is set as false' do
|
||||
f = false
|
||||
|
||||
cookie.secure = f
|
||||
|
||||
expect(cookie.secure).to eql(false)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
describe 'created_at' do
|
||||
describe 'Time' do
|
||||
it 'is returned by created_at after the cookie is initialized' do
|
||||
# cookie created in before
|
||||
|
||||
c = cookie.created_at
|
||||
|
||||
expect(c.class).to eql(Time)
|
||||
end
|
||||
|
||||
it 'instance is assigned to created_at successfully' do
|
||||
t = Time.now
|
||||
|
||||
cookie.created_at = t
|
||||
|
||||
expect(cookie.created_at).to eql(t)
|
||||
expect(cookie.created_at.class).to eql(Time)
|
||||
end
|
||||
end
|
||||
|
||||
describe 'Non-Time' do
|
||||
it 'which can be parsed to a Time object, will be assigned to created_at successfully' do
|
||||
obj = Time.now.to_s
|
||||
|
||||
cookie.created_at = obj
|
||||
|
||||
expect(cookie.created_at).to eql(Time.parse(obj))
|
||||
end
|
||||
|
||||
it 'which cannot be parsed to a Time object, when assigned to created_at, will throw a TypeError' do
|
||||
obj = [rand(9), rand(9), rand(9)]
|
||||
|
||||
expect do
|
||||
cookie.created_at = obj
|
||||
end.to raise_error(TypeError)
|
||||
end
|
||||
end
|
||||
|
||||
describe 'nil' do
|
||||
it 'assigned to created_at successfully' do
|
||||
cookie.created_at = nil
|
||||
expect(cookie.created_at).to eql(nil)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
describe 'accessed_at' do
|
||||
describe 'Time' do
|
||||
it 'is returned by accessed_at after the cookie is initialized' do
|
||||
# cookie created in before
|
||||
|
||||
c = cookie.accessed_at
|
||||
|
||||
expect(c.class).to eql(Time)
|
||||
end
|
||||
|
||||
it 'instance is assigned to accessed_at successfully' do
|
||||
t = Time.now
|
||||
|
||||
cookie.accessed_at = t
|
||||
|
||||
expect(cookie.accessed_at).to eql(t)
|
||||
expect(cookie.accessed_at.class).to eql(Time)
|
||||
end
|
||||
end
|
||||
|
||||
describe 'Non-Time' do
|
||||
it 'which can be parsed to a Time object, will be assigned to accessed_at successfully' do
|
||||
obj = Time.now.to_s
|
||||
|
||||
cookie.accessed_at = obj
|
||||
|
||||
expect(cookie.accessed_at).to eql(Time.parse(obj))
|
||||
end
|
||||
|
||||
it 'which cannot be parsed to a Time object, when assigned to accessed_at, will throw a TypeError' do
|
||||
obj = [rand(9), rand(9), rand(9)]
|
||||
|
||||
expect do
|
||||
cookie.accessed_at = obj
|
||||
end.to raise_error(TypeError)
|
||||
end
|
||||
end
|
||||
|
||||
describe 'nil' do
|
||||
it 'assigned to accessed_at successfully' do
|
||||
cookie.accessed_at = nil
|
||||
expect(cookie.accessed_at).to eql(nil)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
describe 'session' do
|
||||
it 'is set as True when initialized with no max_age or expires args and a nil value' do
|
||||
# cookie created in before
|
||||
|
||||
s = cookie.session?
|
||||
|
||||
expect(s).to eql(true)
|
||||
end
|
||||
|
||||
it 'is set to true when nil is assigned to max_age' do
|
||||
max_age = nil
|
||||
|
||||
cookie.max_age = max_age
|
||||
|
||||
expect(cookie.session?).to eq(true)
|
||||
end
|
||||
|
||||
it 'is set to false when an integer > 0 is assigned to max_age' do
|
||||
max_age = Faker::Number.within(range: 1..100)
|
||||
|
||||
cookie.max_age = max_age
|
||||
|
||||
expect(cookie.session?).to eq(false)
|
||||
end
|
||||
|
||||
it 'is set to true when nil is assigned to expires' do
|
||||
expires = nil
|
||||
|
||||
cookie.expires = expires
|
||||
|
||||
expect(cookie.session?).to eq(true)
|
||||
end
|
||||
|
||||
it 'is set to false when a valid Time is assigned to expires' do
|
||||
expires = Time.now
|
||||
|
||||
cookie.expires = expires
|
||||
|
||||
expect(cookie.session?).to eq(false)
|
||||
end
|
||||
end
|
||||
|
||||
describe 'expired?' do
|
||||
describe 'nil' do
|
||||
it 'assigned to expires causes expired? to return false' do
|
||||
n = nil
|
||||
|
||||
cookie.expires = n
|
||||
|
||||
expect(cookie.expired?).to eql(false)
|
||||
end
|
||||
end
|
||||
|
||||
describe 'No-Arg' do
|
||||
it 'call of expired? returns false when expires & max_age are set to nil' do
|
||||
n = nil
|
||||
|
||||
cookie.expires = n
|
||||
|
||||
expect(cookie.expired?).to eql(false)
|
||||
end
|
||||
|
||||
it 'call of expired? will return true when expires is set to a Time in the past' do
|
||||
t = Faker::Time.backward(days: 1)
|
||||
|
||||
cookie.expires = t
|
||||
|
||||
expect(cookie.expired?).to eql(true)
|
||||
end
|
||||
|
||||
it 'call of expired? will return false when expires is set to a Time in the future' do
|
||||
t = Faker::Time.forward(days: 1)
|
||||
|
||||
cookie.expires = t
|
||||
|
||||
expect(cookie.expired?).to eql(false)
|
||||
end
|
||||
|
||||
it 'call of expired? will return true when max_age in seconds plus cookie.created_at is before Time.now' do
|
||||
max_age = 1
|
||||
|
||||
cookie.max_age = max_age
|
||||
cookie.created_at = Time.local(2008, 9, 5, 10, 5, 30) - 5.seconds
|
||||
|
||||
expect(cookie.expired?).to eql(true)
|
||||
end
|
||||
|
||||
it 'call of expired? will return false when max_age in seconds plus cookie.created_at is after Time.now' do
|
||||
max_age = 10
|
||||
|
||||
cookie.max_age = max_age
|
||||
cookie.created_at = Time.local(2008, 9, 5, 10, 5, 30) - 5.seconds
|
||||
|
||||
expect(cookie.expired?).to eql(false)
|
||||
end
|
||||
end
|
||||
|
||||
describe 'Time' do
|
||||
it 'call of expired? with any Time value returns false when expires is set to nil' do
|
||||
n = nil
|
||||
|
||||
cookie.expires = n
|
||||
cookie.max_age = n
|
||||
|
||||
expect(cookie.expired?(Time.now)).to eql(false)
|
||||
end
|
||||
|
||||
it 'before "cookie.expires" is passed to expired? will return false' do
|
||||
t = Faker::Time.backward(days: 1)
|
||||
|
||||
cookie.expires = Time.now
|
||||
|
||||
expect(cookie.expired?(t)).to eq(false)
|
||||
end
|
||||
|
||||
it 'after "cookie.expires" is passed to expired? will return true' do
|
||||
t = Faker::Time.forward(days: 1)
|
||||
|
||||
cookie.expires = Time.now
|
||||
|
||||
expect(cookie.expired?(t)).to eql(true)
|
||||
end
|
||||
|
||||
it 'passed to expired?, will return true when cookie.created_at + max_age.seconds is before the passed value' do
|
||||
t = Faker::Time.forward(days: 1)
|
||||
|
||||
cookie.max_age = 1
|
||||
cookie.created_at = Time.now
|
||||
|
||||
expect(cookie.expired?(t)).to eq(true)
|
||||
end
|
||||
|
||||
it 'passed to expired?, will return false when created_at + seconds.max_age is after the passed value' do
|
||||
t = Faker::Time.backward(days: 1)
|
||||
|
||||
cookie.max_age = 1
|
||||
cookie.created_at = Time.now
|
||||
|
||||
expect(cookie.expired?(t)).to eq(false)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
describe 'valid_for_uri?' do
|
||||
it "will return false when domain hasn't been set" do
|
||||
# domain set as nil in before
|
||||
|
||||
expect do
|
||||
cookie.valid_for_uri?(URI(random_string))
|
||||
end.to raise_error(ArgumentError)
|
||||
end
|
||||
|
||||
it 'will return false if secure is set as true and the passed cookie is a https url' do
|
||||
cookie.domain = random_string
|
||||
|
||||
v = cookie.valid_for_uri?(URI("https://#{random_string}"))
|
||||
|
||||
expect(v).to eq(false)
|
||||
end
|
||||
end
|
||||
|
||||
describe 'acceptable_from_uri?' do
|
||||
it 'will return false when passed nil' do
|
||||
n = nil
|
||||
|
||||
a = cookie.acceptable_from_uri?(n)
|
||||
|
||||
expect(a).to eq(false)
|
||||
end
|
||||
|
||||
it 'will return false if url without http(s) protocol is passed' do
|
||||
generic_uri = random_string
|
||||
|
||||
v = cookie.acceptable_from_uri?(generic_uri)
|
||||
|
||||
expect(v).to eq(false)
|
||||
end
|
||||
|
||||
it 'will return false if url with http(s) protocol is passed but has no host' do
|
||||
protcol = 'http://'
|
||||
|
||||
v = cookie.acceptable_from_uri?(protcol)
|
||||
|
||||
expect(URI(protcol).is_a?(::URI::HTTP)).to eq(true)
|
||||
expect(v).to eq(false)
|
||||
end
|
||||
|
||||
it 'will return true if url with http(s) protocol is passed with a domain that matches the url domain' do
|
||||
host = random_string
|
||||
uri = "http://#{host}/#{random_string}"
|
||||
cookie.domain = host
|
||||
|
||||
v = cookie.acceptable_from_uri?(uri)
|
||||
|
||||
expect(v).to eq(true)
|
||||
end
|
||||
|
||||
it "will return domain.nil? if url with http(s) protocol is passed with a domain that doesn't match the url domain" do
|
||||
uri = "http://#{random_string}/#{random_string}"
|
||||
cookie.domain = rand(0..1) == 1 ? nil : random_string
|
||||
|
||||
v = cookie.acceptable_from_uri?(uri)
|
||||
|
||||
expect(v).to eq(cookie.domain.nil?)
|
||||
end
|
||||
end
|
||||
end
|
|
@ -84,6 +84,10 @@ RSpec.configure do |config|
|
|||
# as the one that triggered the failure.
|
||||
Kernel.srand config.seed
|
||||
|
||||
# Implemented to avoid regression issue with code calling Faker not being deterministic
|
||||
# https://github.com/faker-ruby/faker/issues/2281
|
||||
Faker::Config.random = Random.new(config.seed)
|
||||
|
||||
config.expect_with :rspec do |expectations|
|
||||
# Enable only the newer, non-monkey-patching expect syntax.
|
||||
expectations.syntax = :expect
|
||||
|
|
Loading…
Reference in New Issue