# Copyright (c) 2020 Contrast Security, Inc. See https://www.contrastsecurity.com/enduser-terms-0317a for more details. # frozen_string_literal: true cs__scoped_require 'contrast/components/interface' cs__scoped_require 'contrast/extensions/ruby_core/protect/applies_deserialization_rule' module Contrast module CoreExtensions module Protect # This Module is how we apply the Command Injection rule. It is called # from our patches of the targeted methods in which command execution # occurs. It is responsible for deciding if the infilter methods of the # rule should be invoked. # In addition, b/c of the nature of Deserialization's sand boxing # function, this Module's apply methods call through to the # {#apply_deserialization_command_check} method of the # Deserialization applicator. module AppliesCommandInjectionRule CS__SEMICOLON = '; ' class << self include Contrast::Components::Interface access_component :logging, :analysis def apply_command_injection_rule method, _exception, _properties, object, args return unless valid_command?(args) command = build_command(args) Contrast::CoreExtensions::Protect::AppliesDeserializationRule.apply_deserialization_command_check(command) return if skip_analysis? begin clazz = object.is_a?(Module) ? object : object.cs__class class_name = clazz.cs__name rule.infilter(Contrast::Agent::REQUEST_TRACKER.current, class_name, method, command) end rescue Contrast::SecurityException => e raise e rescue StandardError => e msg = "command injection: class=#{ object }.#{ method }" logger.error(e, msg) end private def rule PROTECT.rule Contrast::Agent::Protect::Rule::CmdInjection::NAME end def skip_analysis? context = Contrast::Agent::REQUEST_TRACKER.current return true unless context&.app_loaded? return true unless rule&.enabled? false end def valid_command? command command && (command.is_a?(String) || command.is_a?(Array)) end def build_command command return command if command.is_a?(String) command = command.drop(1) if command.length > 1 command.join(CS__SEMICOLON) end end end end end end