# Copyright (c) 2022 Contrast Security, Inc. See https://www.contrastsecurity.com/enduser-terms-0317a for more details. # frozen_string_literal: true require 'contrast/agent/protect/rule/base' require 'contrast/components/logger' module Contrast module Agent module Protect module Rule # Encapsulate common code for protect rules that do their # input analysis on Speedracer rather in ruby code class BaseService < Contrast::Agent::Protect::Rule::Base include Contrast::Components::Logger::InstanceMethods def rule_name 'base-service' end def block_message 'Contrast Security Protect Rule Triggered. Response blocked.' end def infilter? context return false unless context&.speedracer_input_analysis&.results return false unless enabled? return false if protect_excluded_by_code? true end # Override for rules that need the response # Currently postfilter can be applied to streamed responses, # if any logic within postfilter changes to modify the response # streamed responses will break def postfilter context return unless enabled? && POSTFILTER_MODES.include?(mode) if mode == Contrast::Api::Settings::ProtectionRule::Mode::NO_ACTION || mode == Contrast::Api::Settings::ProtectionRule::Mode::PERMIT return end result = find_postfilter_attacker(context, nil) return unless result&.samples&.any? cef_logging result append_to_activity(context, result) return unless result.response == :BLOCKED raise Contrast::SecurityException.new(self, "#{ rule_name } triggered in postfilter. Response blocked.") end protected def gather_ia_results context context.speedracer_input_analysis.results.select do |ia_result| ia_result.rule_id == rule_name end end def find_attacker context, potential_attack_string, **kwargs ia_results = gather_ia_results(context) find_attacker_with_results(context, potential_attack_string, ia_results, **kwargs) end # Allows for the InputAnalysis from service to be extracted early def find_attacker_with_results context, potential_attack_string, ia_results, **kwargs logger.trace('Checking vectors for attacks', rule: rule_name, input: potential_attack_string) result = nil ia_results.each do |ia_result| if potential_attack_string idx = potential_attack_string.index(ia_result.value) next unless idx result = build_attack_with_match(context, ia_result, result, potential_attack_string, **kwargs) else result = build_attack_without_match(context, ia_result, result, **kwargs) end end result end private def find_postfilter_attacker context, potential_attack_string, **kwargs ia_results = gather_ia_results(context) ia_results.select! do |ia_result| ia_result.score_level == if ia_result.rule_id == Contrast::Agent::Protect::Rule::Sqli::NAME || ia_result.rule_id == Contrast::Agent::Protect::Rule::CmdInjection::NAME Contrast::Agent::Reporting::ScoreLevel::WORTHWATCHING else # legacy implementation for DEFINITEATATACK Contrast::Api::Settings::InputAnalysisResult::ScoreLevel::DEFINITEATTACK end end find_attacker_with_results(context, potential_attack_string, ia_results, **kwargs) end end end end end end