lib/contrast/agent/protect/rule/sqli.rb in contrast-agent-4.9.1 vs lib/contrast/agent/protect/rule/sqli.rb in contrast-agent-4.10.0

- old
+ new

@@ -1,17 +1,24 @@ # Copyright (c) 2021 Contrast Security, Inc. See https://www.contrastsecurity.com/enduser-terms-0317a for more details. # frozen_string_literal: true require 'contrast/agent/protect/rule/base_service' require 'contrast/agent/protect/policy/applies_sqli_rule' +require 'contrast/agent/protect/rule/sql_sample_builder' module Contrast module Agent module Protect module Rule # The Ruby implementation of the Protect SQL Injection rule. class Sqli < Contrast::Agent::Protect::Rule::BaseService + # Generate a sample for the SQLI injection detection rule, allowing for reporting to and rendering + # by TeamServer + include SqlSampleBuilder::SqliSample + # Defining build_attack_with_match method + include SqlSampleBuilder::AttackBuilder + NAME = 'sql-injection' BLOCK_MESSAGE = 'SQLi rule triggered. Response blocked.' def rule_name NAME @@ -28,79 +35,9 @@ return unless result append_to_activity(context, result) raise Contrast::SecurityException.new(self, BLOCK_MESSAGE) if blocked? - end - - def build_attack_with_match context, input_analysis_result, result, query_string, **kwargs - attack_string = input_analysis_result.value - regexp = Regexp.new(Regexp.escape(attack_string), Regexp::IGNORECASE) - - return unless query_string.match?(regexp) - - database = kwargs[:database] - scanner = select_scanner(database) - - ss = StringScanner.new(query_string) - length = attack_string.length - while ss.scan_until(regexp) - # the pos of StringScanner is at the end of the regexp (input string), - # we need the beginning - idx = ss.pos - attack_string.length - last_boundary, boundary = scanner.crosses_boundary(query_string, idx, input_analysis_result.value) - next unless last_boundary && boundary - - result ||= build_attack_result(context) - record_match(idx, length, boundary, last_boundary, kwargs) - append_match(context, input_analysis_result, result, query_string, **kwargs) - end - - result - end - - protected - - def build_sample context, input_analysis_result, candidate_string, **kwargs - input = input_analysis_result.value - - sample = build_base_sample(context, input_analysis_result) - sample.sqli = Contrast::Api::Dtm::SqlInjectionDetails.new - sample.sqli.query = Contrast::Utils::StringUtils.protobuf_safe_string(candidate_string) - sample.sqli.start_idx = sample.sqli.query.index(input).to_i - sample.sqli.end_idx = sample.sqli.start_idx + input.length - sample.sqli.boundary_overrun_idx = kwargs[:boundary_overrun_idx].to_i - sample.sqli.input_boundary_idx = kwargs[:input_boundary_idx].to_i - sample - end - - private - - def record_match idx, length, boundary, last_boundary, kwargs - kwargs[:start_idx] = idx - kwargs[:end_idx] = idx + length - kwargs[:boundary_overrun_idx] = boundary - kwargs[:input_boundary_idx] = last_boundary - end - - def append_match context, input_analysis_result, result, query_string, **kwargs - input_analysis_result.attack_count = input_analysis_result.attack_count + 1 - update_successful_attack_response(context, input_analysis_result, result, query_string) - append_sample(context, input_analysis_result, result, query_string, **kwargs) - end - - def select_scanner database - @sql_scanners ||= { - Contrast::Agent::Protect::Policy::AppliesSqliRule::DATABASE_MYSQL => - Contrast::Agent::Protect::Rule::Sqli::MysqlSqlScanner.new, - Contrast::Agent::Protect::Policy::AppliesSqliRule::DATABASE_PG => - Contrast::Agent::Protect::Rule::Sqli::PostgresSqlScanner.new, - Contrast::Agent::Protect::Policy::AppliesSqliRule::DATABASE_SQLITE => - Contrast::Agent::Protect::Rule::Sqli::SqliteSqlScanner.new - }.cs__freeze - - @default_sql_scanner ||= Contrast::Agent::Protect::Rule::Sqli::DefaultSqlScanner.new - @sql_scanners[database.to_s] || @default_sql_scanner end end end end end