# Copyright (c) 2023 Contrast Security, Inc. See https://www.contrastsecurity.com/enduser-terms-0317a for more details. # frozen_string_literal: true require 'contrast/agent/assess/rule/response/auto_complete_rule' require 'contrast/agent/assess/rule/response/cache_control_header_rule' require 'contrast/agent/assess/rule/response/click_jacking_header_rule' require 'contrast/agent/assess/rule/response/csp_header_insecure_rule' require 'contrast/agent/assess/rule/response/csp_header_missing_rule' require 'contrast/agent/assess/rule/response/hsts_header_rule' require 'contrast/agent/assess/rule/response/parameters_pollution_rule' require 'contrast/agent/assess/rule/response/x_content_type_header_rule' require 'contrast/agent/assess/rule/response/x_xss_protection_header_rule' require 'contrast/agent/protect/input_analyzer/input_analyzer' require 'contrast/components/logger' require 'contrast/utils/log_utils' require 'contrast/utils/string_utils' module Contrast module Agent # This class extends RequestContexts: this class acts to encapsulate information about the currently # executed request, making it available to the Agent for the duration of the request in a standardized # and normalized format which the Agent understands. module RequestContextExtend include Contrast::Utils::CEFLogUtils include Contrast::Components::Logger::InstanceMethods # Convert the discovered route for this request to appropriate forms and disseminate it to those locations # where it is necessary for our route coverage and finding vulnerability discovery features to function. # # @param route [Contrast::Agent::Reporting::RouteCoverage] def append_to_observed_route route return unless route @observed_route.signature = route.route @observed_route.verb = route.verb @observed_route.url = route.url if route.url @request.observed_route = @observed_route observation = Contrast::Agent::Reporting::RouteDiscoveryObservation.new(route.url, route.verb) @discovered_route = Contrast::Agent::Reporting::RouteDiscovery.new(route.route, observation) @request.discovered_route = @discovered_route end # If protect is enabled for this request, examine said request for any possible attack input. If those inputs # provided match a rule which should block at the perimeter, that will be raised here. # # @raise [Contrast::SecurityException] def protect_input_analysis return false unless ::Contrast::AGENT.enabled? return false unless ::Contrast::PROTECT.enabled? return false if @do_not_track if (ia = Contrast::Agent::Protect::InputAnalyzer.analyse(request)) # Handle prefilter Contrast::Agent::Protect::InputAnalyzer.input_classification(ia, prefilter: true) # Reflected xss infilter Contrast::Agent::Protect::InputAnalyzer.input_classification_for('reflected-xss', ia) @agent_input_analysis = ia else logger.trace('Analysis from Agent was empty.') end rescue Contrast::SecurityException => e raise(e) rescue StandardError => e logger.warn('Unable to extract protect information from request', e) end # Builds IA only for postfilter rules. If rules during infilter were not triggered there will be # no IA for them later to use it in postfilter. # # @raise [Contrast::SecurityException] def protect_postfilter_ia return false unless ::Contrast::AGENT.enabled? return false unless ::Contrast::PROTECT.enabled? # Handle postfilter Contrast::Agent::Protect::InputAnalyzer.input_classification(@agent_input_analysis, postfilter: true) rescue Contrast::SecurityException => e raise(e) rescue StandardError => e logger.warn('Unable to extract protect information from request - postfilter', e) end # append anything we've learned to the request seen message this is the sum-total of all inventory information # that has been accumulated since the last request def extract_after rack_response # We must ALWAYS save the response, even if we don't need it here for response sampling. It is used for other # vulnerability detection, most notably XSS, and not capturing it may suppress valid findings. @response = Contrast::Agent::Response.new(rack_response) return unless @sample_res Contrast::Agent::Assess::Rule::Response::AutoComplete.new.analyze(@response) Contrast::Agent::Assess::Rule::Response::CacheControl.new.analyze(@response) Contrast::Agent::Assess::Rule::Response::ClickJacking.new.analyze(@response) Contrast::Agent::Assess::Rule::Response::CspHeaderMissing.new.analyze(@response) Contrast::Agent::Assess::Rule::Response::CspHeaderInsecure.new.analyze(@response) Contrast::Agent::Assess::Rule::Response::HSTSHeader.new.analyze(@response) Contrast::Agent::Assess::Rule::Response::ParametersPollution.new.analyze(@response) Contrast::Agent::Assess::Rule::Response::XContentType.new.analyze(@response) Contrast::Agent::Assess::Rule::Response::XXssProtection.new.analyze(@response) rescue StandardError => e logger.error('Unable to extract information after request', e) end end end end