# 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/request/request_context' require 'contrast/utils/object_share' require 'contrast/agent/protect/rule/cmdi/cmdi_base_rule' require 'contrast/agent/protect/rule/cmdi/cmd_injection' module Contrast module Agent module Protect module Rule # The Ruby implementation of the Protect Command Injection Command # Backdoors sub-rule. class CmdiBackdoors < Contrast::Agent::Protect::Rule::CmdiBaseRule NAME = 'cmd-injection-command-backdoors' MATCHES = %w[/bin/bash-c /bin/sh-c sh-c bash-c].cs__freeze def rule_name NAME end def sub_rules Contrast::Utils::ObjectShare::EMPTY_ARRAY end # CMDI Backdoors infilter: # This rule does not have input classification. # If a value matches the CMDI applicable input types and it's length is > 2 # we can check if it's used as command backdoors. # # @param context [Contrast::Agent::RequestContext] current request contest # @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 ise set # to BLOCK and valid cdmi is detected. def infilter context, classname, method, command return if protect_excluded_by_url?(rule_name) return unless backdoors_match?(command) return unless (result = build_attack_with_match(context, nil, nil, command, **{ classname: classname, method: method })) append_to_activity(context, result) cef_logging(result, :successful_attack) return unless blocked? raise_error(classname, method) end private # Used to customize the raised error message. # # @param classname [String] Name of the class # @param method [String] name of the method triggering the rule # @raise [Contrast::SecurityException] def raise_error classname, method raise(Contrast::SecurityException.new(self, 'Command Injection Command Backdoor rule triggered. ' \ "Call to #{ classname }.#{ method } blocked.")) end # Check to see if value is backdoor match # # @param potential_attack_string [String] def backdoors_match? potential_attack_string return false unless potential_attack_string && potential_attack_string.length > 2 return false unless matches_shell_parameter?(potential_attack_string) true end # Checks to see if input is used as parameter for a shell cmd. # # @param cmd [String] # @return [Boolean] def matches_shell_parameter? cmd normalize_cmd = cmd.delete(Contrast::Utils::ObjectShare::SPACE) MATCHES.each do |match| return true if normalize_cmd.include?(match) end false end end end end end end