lib/contrast/agent/protect/rule/cmd_injection.rb in contrast-agent-6.6.5 vs lib/contrast/agent/protect/rule/cmd_injection.rb in contrast-agent-6.7.0

- old
+ new

@@ -4,45 +4,40 @@ require 'contrast/agent/protect/rule/base_service' require 'contrast/utils/stack_trace_utils' require 'contrast/utils/object_share' require 'contrast/components/logger' require 'contrast/agent/reporting/input_analysis/input_type' -require 'contrast/agent/reporting/details/cmd_injection_details' +require 'contrast/agent/protect/rule/cmdi/cmdi_base_rule' +require 'contrast/agent/protect/rule/cmdi/cmdi_backdoors' module Contrast module Agent module Protect module Rule # The Ruby implementation of the Protect Command Injection rule. - class CmdInjection < Contrast::Agent::Protect::Rule::BaseService + class CmdInjection < Contrast::Agent::Protect::Rule::CmdiBaseRule include Contrast::Components::Logger::InstanceMethods include Contrast::Agent::Reporting::InputType - NAME = 'cmd-injection' - CHAINED_COMMAND_CHARS = /[;&|<>]/.cs__freeze - APPLICABLE_USER_INPUTS = [ - BODY, COOKIE_VALUE, HEADER, PARAMETER_NAME, - PARAMETER_VALUE, JSON_VALUE, MULTIPART_VALUE, - MULTIPART_FIELD_NAME, XML_VALUE, DWR_VALUE - ].cs__freeze + SUB_RULES = [Contrast::Agent::Protect::Rule::CmdiBackdoors.new].cs__freeze - class << self - # @param attack_sample [Contrast::Api::Dtm::RaspRuleSample] - # @return [Hash] the details for this specific rule - def extract_details attack_sample - { - command: attack_sample.cmdi.command, - startIndex: attack_sample.cmdi.start_idx, - endIndex: attack_sample.cmdi.end_idx - } - end - end - def rule_name NAME end + def sub_rules + SUB_RULES + end + + # CMDI infilter: + # + # @param context [Contrast::Agent::RequestContext] current request context + # @param classname [String] Name of the class + # @param method [String] name of the method triggering the rule + # @param command [String] potential dangerous command executed. + # @raise [Contrast::SecurityException] if the rule mode is set + # to BLOCK and valid cdmi is detected. def infilter context, classname, method, command return unless infilter?(context) ia_results = gather_ia_results(context) return if ia_results.empty? @@ -61,93 +56,11 @@ append_to_activity(context, result) cef_logging(result, :successful_attack) return unless blocked? - raise(Contrast::SecurityException.new(self, - 'Command Injection rule triggered. '\ - "Call to #{ classname }.#{ method } blocked.")) - end - - def build_attack_with_match context, input_analysis_result, result, candidate_string, **kwargs - if mode == Contrast::Api::Settings::ProtectionRule::Mode::NO_ACTION || - mode == Contrast::Api::Settings::ProtectionRule::Mode::PERMIT - - return result - end - - result ||= build_attack_result(context) - update_successful_attack_response(context, input_analysis_result, result, candidate_string) - append_sample(context, input_analysis_result, result, candidate_string, **kwargs) - result - end - - protected - - # Because results are not necessarily on the context across - # processes; extract early and pass into the method - 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 = super(context, potential_attack_string, ia_results, **kwargs) - if result.nil? && potential_attack_string - result = find_probable_attacker(context, potential_attack_string, ia_results, **kwargs) - end - result - end - - # Build a subclass of the RaspRuleSample using the query string and the - # evaluation - def build_sample context, input_analysis_result, candidate_string, **_kwargs - sample = build_base_sample(context, input_analysis_result) - sample.details = Contrast::Agent::Reporting::Details::CmdInjectionDetails.new - - command = candidate_string || input_analysis_result.value - command = Contrast::Utils::StringUtils.protobuf_safe_string(command) - sample.details.cmd = command - sample.details.end_idx = command.length - - # This is a special case where the user input is UNKNOWN_USER_INPUT but - # we want to send the attack value - if input_analysis_result.nil? - ui = Contrast::Agent::Reporting::UserInput.new - ui.input_type = :UNKNOWN - ui.value = command - sample.user_input = ui - end - - sample - end - - private - - def report_command_execution context, command, **kwargs - return unless report_any_command_execution? - return if protect_excluded_by_code? - - build_attack_with_match(context, nil, nil, command, **kwargs) - end - - def find_probable_attacker context, potential_attack_string, ia_results, **kwargs - return unless chained_command?(potential_attack_string) - - likely_attacker = ia_results.find { |input_analysis_result| chained_command?(input_analysis_result.value) } - return unless likely_attacker - - build_attack_with_match(context, likely_attacker, nil, potential_attack_string, **kwargs) - end - - def chained_command? command - CHAINED_COMMAND_CHARS.match(command) - end - - # Part of the Hardening for Command Injection detection is the - # ability to detect and prevent any command execution from within the - # application. This check determines if that hardening has been - # enabled. - # @return [Boolean] if the agent should report all command - # executions. - def report_any_command_execution? - ::Contrast::PROTECT.report_any_command_execution? + # Raise cmdi error + raise_error(classname, method) end end end end end