# 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/cmd_injection' require 'contrast/agent/protect/rule/deserialization' require 'contrast/agent/protect/rule/http_method_tampering' require 'contrast/agent/protect/rule/no_sqli' require 'contrast/api/dtm.pb' require 'contrast/components/logger' module Contrast module Agent module Reporting # This is the new ApplicationDefendAttackSample class which includes a samples of an attack for the given rule of # the given result observed in the activity period. class ApplicationDefendAttackSample include Contrast::Agent::Reporting::InputType # @return [Hash] attr_reader :time_stamp class << self # @param attack_result [Contrast::Api::Dtm::AttackResult] # @param attack_sample [Contrast::Api::Dtm::RaspRuleSample] def convert attack_result, attack_sample activity = new activity.attach_data(attack_result, attack_sample) activity end end def initialize @blocked = false @event_type = :application_defend_attack_sample end def to_controlled_hash { blocked: @blocked, input: @input, details: @details, request: @request&.to_controlled_hash, stack: @stack&.map(&:to_controlled_hash), timestamp: time_stamp } end # @param attack_result [Contrast::Api::Dtm::AttackResult] # @param attack_sample [Contrast::Api::Dtm::RaspRuleSample] def attach_data attack_result, attack_sample @blocked = attack_result.response == ::Contrast::Api::Dtm::AttackResult::ResponseType::BLOCKED @input = build_input(attack_sample) @details = build_details(attack_result.rule_id, attack_sample) @time_stamp = build_time_stamp(attack_sample.timestamp_ms) @request = FindingRequest.convert(Contrast::Agent::REQUEST_TRACKER.current&.request) @stack = Contrast::Utils::StackTraceUtils.build_protect_report_stack_array end # @param start [Integer] # @return [Hash] def build_time_stamp start { start: start, elapsed: Contrast::Utils::Timer.now_ms - start } end # @param attack_sample [Contrast::Api::Dtm::RaspRuleSample] def build_input attack_sample user_input = attack_sample.user_input { documentPath: user_input.path, documentType: build_document_type(user_input.document_type), filters: user_input.matcher_ids, name: user_input.key, time: attack_sample.timestamp_ms, type: build_input_type(user_input.input_type), value: user_input.value } end # Convert the document type api enum to a String constant TeamServer can understand. # # @param type_enum [::Contrast::Api::Dtm::HttpRequest::DocumentType] # @return [String] def build_document_type type_enum case type_enum when ::Contrast::Api::Dtm::HttpRequest::DocumentType::JSON 'JSON' when ::Contrast::Api::Dtm::HttpRequest::DocumentType::XML 'XML' else 'NORMAL' end end # Convert the input type api enum to a String constant TeamServer can understand. # # @param type_enum [when ::Contrast::Api::Dtm::UserInput::InputType] # @return [String] def build_input_type type_enum case type_enum when ::Contrast::Api::Dtm::UserInput::InputType::UNDEFINED_TYPE 'UNDEFINED_TYPE' when ::Contrast::Api::Dtm::UserInput::InputType::BODY 'BODY' when ::Contrast::Api::Dtm::UserInput::InputType::COOKIE_NAME 'COOKIE_NAME' when ::Contrast::Api::Dtm::UserInput::InputType::COOKIE_VALUE 'COOKIE_VALUE' when ::Contrast::Api::Dtm::UserInput::InputType::HEADER 'HEADER' when ::Contrast::Api::Dtm::UserInput::InputType::PARAMETER_NAME 'PARAMETER_NAME' when ::Contrast::Api::Dtm::UserInput::InputType::PARAMETER_VALUE 'PARAMETER_VALUE' when ::Contrast::Api::Dtm::UserInput::InputType::QUERYSTRING 'QUERYSTRING' when ::Contrast::Api::Dtm::UserInput::InputType::URI 'URI' when ::Contrast::Api::Dtm::UserInput::InputType::SOCKET 'SOCKET' when ::Contrast::Api::Dtm::UserInput::InputType::JSON_VALUE 'JSON_VALUE' when ::Contrast::Api::Dtm::UserInput::InputType::JSON_ARRAYED_VALUE 'JSON_ARRAYED_VALUE' when ::Contrast::Api::Dtm::UserInput::InputType::MULTIPART_CONTENT_TYPE 'MULTIPART_CONTENT_TYPE' when ::Contrast::Api::Dtm::UserInput::InputType::MULTIPART_VALUE 'MULTIPART_VALUE' when ::Contrast::Api::Dtm::UserInput::InputType::MULTIPART_FIELD_NAME 'MULTIPART_FIELD_NAME' when ::Contrast::Api::Dtm::UserInput::InputType::MULTIPART_NAME 'MULTIPART_NAME' when ::Contrast::Api::Dtm::UserInput::InputType::XML_VALUE 'XML_VALUE' when ::Contrast::Api::Dtm::UserInput::InputType::DWR_VALUE 'DWR_VALUE' when ::Contrast::Api::Dtm::UserInput::InputType::METHOD 'METHOD' when ::Contrast::Api::Dtm::UserInput::InputType::REQUEST 'REQUEST' when ::Contrast::Api::Dtm::UserInput::InputType::URL_PARAMETER 'URL_PARAMETER' else # ::Contrast::Api::Dtm::UserInput::InputType::UNKNOWN 'UNKNOWN' end end # This is a temporary method to facilitate the translation of API details to Reporting details. As we move off # of protobuf, this responsibility should shift from this class to the individual rules, as they do today when # they populate their details in Contrast::Api::Dtm::RaspRuleSample # # @param rule_id [String] # @param attack_sample [Contrast::Api::Dtm::RaspRuleSample] # @return [Hash] the details for this specific rule def build_details rule_id, attack_sample case rule_id when Contrast::Agent::Protect::Rule::CmdInjection::NAME Contrast::Agent::Protect::Rule::CmdInjection.extract_details(attack_sample) when Contrast::Agent::Protect::Rule::Deserialization::NAME Contrast::Agent::Protect::Rule::Deserialization.extract_details(attack_sample) when Contrast::Agent::Protect::Rule::HttpMethodTampering::NAME Contrast::Agent::Protect::Rule::HttpMethodTampering.extract_details(attack_sample) when Contrast::Agent::Protect::Rule::NoSqli::NAME Contrast::Agent::Protect::Rule::NoSqli.extract_details(attack_sample) when Contrast::Agent::Protect::Rule::PathTraversal::NAME Contrast::Agent::Protect::Rule::PathTraversal.extract_details(attack_sample) when Contrast::Agent::Protect::Rule::Sqli::NAME Contrast::Agent::Protect::Rule::Sqli.extract_details(attack_sample) when Contrast::Agent::Protect::Rule::Xss::NAME Contrast::Agent::Protect::Rule::Xss.extract_details(attack_sample) when Contrast::Agent::Protect::Rule::Xxe::NAME Contrast::Agent::Protect::Rule::Xxe.extract_details(attack_sample) else # something unknown or Contrast::Agent::Protect::Rule::UnsafeFileUpload::NAME Contrast::Utils::ObjectShare::EMPTY_HASH end end end end end end