# Copyright (c) 2020 Contrast Security, Inc. See https://www.contrastsecurity.com/enduser-terms-0317a for more details. # frozen_string_literal: true require 'contrast/components/interface' require 'contrast/utils/invalid_configuration_util' module Contrast module Framework module Rails module Patch # This module is used to analyze rails session storage configuration for assess vulnerabilities module AssessConfiguration include Contrast::Components::Interface access_component :agent, :analysis, :logging CS__SESSION_TIMEOUT_NAME = 'session-timeout' SAFE_SESSION_TIMEOUT = (30 * 60 * 1000) CS__SECURE_RULE_NAME = 'secure-flag-missing' CS__HTTPONLY_RULE_NAME = 'rails-http-only-disabled' class << self include Contrast::Utils::InvalidConfigurationUtil def analyze_session_store *args return if PROTECT.enabled? apply_httponly_disabled(*args) apply_secure_cookie_disabled(*args) apply_session_timeout(*args) end private def vulnerable_setting? setting_key, safe_settings_value, original_args, safe_default: true, comparison_type: nil # In most cases, Rails is pretty nice and the default value is safe return !safe_default unless original_args && original_args.length > 1 # If the user overrode some args, but not ours, fall back on the default rails_session_settings = original_args[1] return !safe_default unless rails_session_settings&.key?(setting_key) value = rails_session_settings[setting_key] return value.to_i > safe_settings_value.to_i if comparison_type&.to_sym == :greater_than value != safe_settings_value end def apply_session_timeout *args return if ASSESS.rule_disabled? CS__SESSION_TIMEOUT_NAME return unless vulnerable_setting?(:expire_after, SAFE_SESSION_TIMEOUT, args, comparison_type: :greater_than, safe_default: false) rails_session_settings = args[1] cs__report_finding(CS__SESSION_TIMEOUT_NAME, rails_session_settings, caller_locations(3, 2)[0]) rescue StandardError => e begin logger.error('Unable to track call to set session timeout', e) rescue StandardError nil end end def apply_secure_cookie_disabled *args return if ASSESS.rule_disabled? CS__SECURE_RULE_NAME return unless vulnerable_setting?(:secure, true, args) rails_session_settings = args[1] cs__report_finding(CS__SECURE_RULE_NAME, rails_session_settings, caller_locations(3, 2)[0]) rescue StandardError => e begin logger.error('Unable to track call to disable secure cookies', e) rescue StandardError nil end end def apply_httponly_disabled *args return if ASSESS.rule_disabled? CS__HTTPONLY_RULE_NAME return unless vulnerable_setting?(:httponly, true, args) rails_session_settings = args[1] cs__report_finding(CS__HTTPONLY_RULE_NAME, rails_session_settings, caller_locations(3, 2)[0]) rescue StandardError => e begin logger.error('Unable to track call to disable httponly in session cookie', e) rescue StandardError nil end end end end end end end end