# Copyright (c) 2020 Contrast Security, Inc. See https://www.contrastsecurity.com/enduser-terms-0317a for more details. # frozen_string_literal: true module Contrast module Components # This component encapsulates the statefulness of settings. # When we say 'settings', we're referring specifically to external # directives (likely provided by TeamServer) about product operation. # 'Settings' is not a generic term for 'configurable stuff'. module Settings # This is a class. class Interface include Contrast::Components::ComponentBase include Contrast::Components::Interface access_component :config attr_reader :assess_rules, :protect_rules # Other stateful information that doesn't yet cleanly fit anywhere: # tainted_columns are database columns that receive unsanitized input. # this statefulness attr_reader :tainted_columns # This can probably go into assess_state? # a vulnerability like padding oracle is exploited across # multiple requests, as a timing attack. these attempts must be # accumulated, in order to recognize the pattern and block the attack. attr_reader :accumulator_settings # These three 'state' variables represent atomic config/setting state, # outside of things like rule defs. def assess_state @assess_state ||= { # rubocop:disable Naming/MemoizedInstanceVariableName enabled: false, sampling_features: nil } end def protect_state @protect_state ||= { # rubocop:disable Naming/MemoizedInstanceVariableName enabled: false, accumulator_settings: Contrast::Api::Settings::AccumulatorSettings.new } end def application_state @application_state ||= { # rubocop:disable Naming/MemoizedInstanceVariableName modes_by_id: Hash.new(:NO_ACTION), exclusion_matchers: [], disabled_assess_rules: [] } end # These are settings that we receive & store. # Rules are settings too, but they're more involved. # So, between this block and rules, that's setting state. PROTECT_STATE_ATTRS = %i[].cs__freeze ASSESS_STATE_ATTRS = %i[sampling_features].cs__freeze APPLICATION_STATE_ATTRS = %i[modes_by_id exclusion_matchers disabled_assess_rules session_id].cs__freeze # Meta-define an accessor for each state attribute. begin PROTECT_STATE_ATTRS.each do |attr| define_method(attr) do protect_state[attr] end end ASSESS_STATE_ATTRS.each do |attr| define_method(attr) do assess_state[attr] end end APPLICATION_STATE_ATTRS.each do |attr| define_method(attr) do application_state[attr] end end end def initialize reset_state end def update_from_server_features server_features # protect begin protect_state[:enabled] = server_features.protect_enabled? end # assess begin assess_state[:enabled] = server_features.assess_enabled? assess_state[:sampling_settings] = server_features.assess.sampling Contrast::Utils::Assess::SamplingUtil.instance.update end end def update_from_application_settings application_settings application_state.merge!(application_settings.application_state_translation) end # Wipe state to zero. def reset_state @assess_rules = {} @protect_rules = {} @tainted_columns = {} @assess_state = nil @protect_state = nil @application_state = nil end def build_assess_rules @assess_rules = {} Contrast::Agent::Assess::Rule::Csrf.new Contrast::Agent::Assess::Rule::Redos.new end def build_protect_rules @protect_rules = {} # rules Contrast::Agent::Protect::Rule::CmdInjection.new Contrast::Agent::Protect::Rule::Deserialization.new Contrast::Agent::Protect::Rule::HttpMethodTampering.new Contrast::Agent::Protect::Rule::NoSqli.new Contrast::Agent::Protect::Rule::PathTraversal.new Contrast::Agent::Protect::Rule::Sqli.new Contrast::Agent::Protect::Rule::UnsafeFileUpload.new Contrast::Agent::Protect::Rule::Xss.new Contrast::Agent::Protect::Rule::Xxe.new # Beta Rules Contrast::Agent::Protect::Rule::Csrf.new end # these are less 'settings' and more 'how do I behave.' # relocate to Agent or Assess/Protect. def protect_rule_mode rule_id CONFIG.root.protect.rules[rule_id]&.mode || modes_by_id[rule_id] || :NO_ACTION end end COMPONENT_INTERFACE = Interface.new end end end