# Copyright (c) 2023 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/agent/reporting/details/path_traversal_semantic_analysis_details' require 'contrast/agent/request/request_context' require 'contrast/utils/string_utils' require 'contrast/agent_lib/api/path_semantic_file_security_bypass' require 'contrast/agent_lib/interface' module Contrast module Agent module Protect module Rule # The Ruby implementation of the Protect Path Traversal Semantic # Bypass sub-rule. This rule should report the attack result class PathTraversalSemanticBypass < Contrast::Agent::Protect::Rule::Base NAME = 'path-traversal-semantic-file-security-bypass' SYSTEM_PATHS = %w[ /proc/self etc/passwd etc/shadow etc/hosts etc/groups etc/gshadow ntuser.dat /Windows/win.ini /windows/system32/ /windows/repair/ ].cs__freeze def rule_name NAME end def sub_rules Contrast::Utils::ObjectShare::EMPTY_ARRAY end # Path traversal Semantic infilter: # This rule does not have input classification. # # @param context [Contrast::Agent::RequestContext] current request contest # @param method [String] name of the method triggering the rule # @param path [String] potential dangerous path # @raise [Contrast::SecurityException] if the rule mode ise set # to BLOCK and valid cdmi is detected. def infilter context, method, path return if protect_excluded_by_url?(rule_name, context.request.path) return unless rule_violated?(path) result = build_violation(context, path) return unless result append_to_activity(context, result) return unless blocked? result_rule_name = Contrast::Utils::StringUtils.transform_string(result.rule_id) cef_logging(result, :successful_attack, value: path) exception_messasge = "#{ result_rule_name } rule triggered. Call to File.#{ method } blocked." raise(Contrast::SecurityException.new(self, exception_messasge)) end protected # Check if semantic file security bypass is detected # # @param file_path [String] command to check. # @param is_custom_code[String] whether the file is being accessed by custom (user) code, # rather than framework code # @return result[Integer, nil] returns: # 1 => security bypass is detected. # 0 => no security bypass is detected. def file_security_bypassed? file_path, is_custom_code = nil return false unless (agent_lib = Contrast::AGENT_LIB) || file_path custom_call = is_custom_code.nil? ? 0 : 1 agent_lib.check_path_semantic_security_bypass(file_path, custom_call) == 1 end def rule_violated? path return false if custom_code_access_sysfile_enabled? is_custom_code = custom_code_accessing_system_file?(path) is_custom_code || file_security_bypassed?(path, is_custom_code) end def build_sample context, _input_analysis_result, path, **_kwargs sample = build_base_sample(context, nil) sample.details = Contrast::Agent::Reporting::Details::PathTraversalSemanticAnalysisDetails.new path = Contrast::Utils::StringUtils.protobuf_safe_string(path) sample.details.path = path is_custom_code = custom_code_accessing_system_file?(path) # This should catch all the types of security breaches in that sub-rule scope # but apparently it's not, because some of the system files is being skipped and not detected, # but for our previous logic - it was expected for certain files to be detected and blocked security_bypassed = file_security_bypassed?(path, is_custom_code) # if agent lib sub-rule returns true and is custom code -> assign and report if security_bypassed if is_custom_code sample.details.findings << :CUSTOM_CODE_ACCESSING_SYSTEM_FILES sample.details.findings << :COMMON_FILE_EXPLOITS if common_file_exploits_enabled? end return sample end if is_custom_code sample.details.findings << :CUSTOM_CODE_ACCESSING_SYSTEM_FILES return sample end nil end def custom_code_access_sysfile_enabled? ::Contrast::PROTECT.report_custom_code_sysfile_access? end def custom_code_accessing_system_file? input system_file?(input) && Contrast::Utils::StackTraceUtils.custom_code_context? end def system_file? path return false unless path SYSTEM_PATHS.any? { |sys_path| sys_path.include?(path) } end def common_file_exploits_enabled? false end end end end end end