# 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/agent/patching/policy/policy_node' module Contrast module Agent module Assess module Policy # This class functions to translate our policy.json into an actionable # Ruby object, allowing for dynamic patching over hardcoded patching. class PolicyNode < Contrast::Agent::Patching::Policy::PolicyNode attr_accessor :tags, :type attr_reader :sources, :targets, :source_string, :target_string def initialize policy_hash = {} super(policy_hash) @source_string = policy_hash[JSON_SOURCE] @target_string = policy_hash[JSON_TARGET] @tags = Set.new(policy_hash[JSON_TAGS]) generate_sources generate_targets end def feature 'Assess' end def node_class 'Node' end def node_type :TYPE_METHOD end def target @_target ||= begin if targets&.any? targets[0] elsif sources&.any? sources[0] end end end def source_string= value @source_string = value generate_sources end def target_string= value @target_string = value generate_targets end # Sometimes we need to tie information to an event. We'll add a # properties section to the patch node, which can pass them along to # the pre-dtm event def add_property name, value return unless name && value @properties ||= {} @properties[name] = value end def get_property name return unless @properties @properties[name] end # Given a source in the format A,B,C, populate the sources of this node # 1) Split on ',' # 2) If 'O', add the source, else it's P (we don't have R sources) and # needs to be converted. P type will either be P:name or P# where # # is the index of the paramter. Drop the P and store the int as int # or name as symbol def generate_sources if source_string @sources = [] source_string.split(Contrast::Utils::ObjectShare::COMMA).each do |s| is_object = (s == Contrast::Utils::ObjectShare::OBJECT_KEY) if is_object @sources << s else parameter_source = s[1..-1] @sources << if parameter_source.start_with?(Contrast::Utils::ObjectShare::COLON) parameter_source[1..-1].to_sym else parameter_source.to_i end end end else @sources = Contrast::Utils::ObjectShare::EMPTY_ARRAY end end # Given a target in the format A,B,C, populate the targets of this node # 1) Split on ',' # 2) If 'O' or 'R', add the target, else it's P and needs to be # converted. P type will either be P:name or P# where # is the index # of the paramter. Drop the P and store the int as int or name as # symbol def generate_targets if target_string @targets = [] target_string.split(Contrast::Utils::ObjectShare::COMMA).each do |t| case t when Contrast::Utils::ObjectShare::OBJECT_KEY @targets << t when Contrast::Utils::ObjectShare::RETURN_KEY @targets << t else parameter_target = t[1..-1] @targets << if parameter_target.start_with?(Contrast::Utils::ObjectShare::COLON) parameter_target[1..-1].to_sym else parameter_target.to_i end end end else @targets = Contrast::Utils::ObjectShare::EMPTY_ARRAY end end # Don't let nodes be created that will be missing things we need # later on. Really, if they don't have these things, they couldn't have # done their jobs anyway. def validate super validate_tags end # TeamServer is picky. The tags here match to ENUMs there. If there # isn't a matching ENUM in TS land, the database gets got. We really # don't want to get them, so we're going to prevent the node from being # made. def validate_tags return unless tags tags.each do |tag| next if VALID_TAGS.include?(tag) || VALID_SOURCE_TAGS.include?(tag) raise(ArgumentError, "#{ node_class } #{ id } had an invalid tag. #{ tag } is not a known value.") end end ALL_TYPE = 'A' CREATION_TYPE = 'CREATION' TRIGGER_TYPE = 'TRIGGER' TO_MARKER = '2' # Convert our action, built from our source and target, into # the TS appropriate action. That's a single source to single # target marker (A,O,P,R) # # Creation (source nodes) don't have sources (they are the source) # Trigger (trigger nodes) don't have targets (they are the target) # Everything else (propagation nodes) are Source2Target def build_action @event_action ||= begin source = source_string target = target_string if source.nil? :CREATION elsif target.nil? :TRIGGER else # TeamServer can't handle the multi-source or multi-target # values. Give it some help by changing them to 'A' source = ALL_TYPE if source.include?(Contrast::Utils::ObjectShare::COMMA) target = ALL_TYPE if target.include?(Contrast::Utils::ObjectShare::COMMA) str = source[0] + TO_MARKER + target[0] # TODO: RUBY-139 PERF -- save in the patcher str.to_sym end end @event_action end # EventTagTypeDTM VALID_TAGS = %w[ XML_ENCODED XML_DECODED HTML_ENCODED HTML_DECODED URL_ENCODED URL_DECODED CSS_ENCODED CSS_DECODED BASE64_ENCODED BASE64_DECODED JAVASCRIPT_ENCODED JAVASCRIPT_DECODED JAVA_ENCODED JAVA_DECODED CSV_ENCODED CSV_DECODED SQL_ENCODED SQL_DECODED LDAP_ENCODED LDAP_DECODED XPATH_ENCODED XPATH_DECODED OS_ENCODED OS_DECODED VBSCRIPT_ENCODED VBSCRIPT_DECODED POTENTIAL_SANITIZED POTENTIAL_VALIDATED NO_CONTROL_CHARS CUSTOM CUSTOM_ENCODED CUSTOM_ENCODED_CMD_INJECTION CUSTOM_ENCODED_EXPRESSION_LANGUAGE_INJECTION CUSTOM_ENCODED_HEADER_INJECTION CUSTOM_ENCODED_HQL_INJECTION CUSTOM_ENCODED_LDAP_INJECTION CUSTOM_ENCODED_LOG_INJECTION CUSTOM_ENCODED_NOSQL_INJECTION CUSTOM_ENCODED_PATH_TRAVERSAL CUSTOM_ENCODED_REDOS CUSTOM_ENCODED_REFLECTED_XSS CUSTOM_ENCODED_REFLECTION_INJECTION CUSTOM_ENCODED_SMTP_INJECTION CUSTOM_ENCODED_SQL_INJECTION CUSTOM_ENCODED_SSRF CUSTOM_ENCODED_STORED_XSS CUSTOM_ENCODED_TRUST_BOUNDARY_VIOLATION CUSTOM_ENCODED_UNSAFE_CODE_EXECUTION CUSTOM_ENCODED_UNSAFE_READLINE CUSTOM_ENCODED_UNSAFE_XML_DECODE CUSTOM_ENCODED_UNTRUSTED_DESERIALIZATION CUSTOM_ENCODED_UNVALIDATED_FORWARD CUSTOM_ENCODED_UNVALIDATED_REDIRECT CUSTOM_ENCODED_XPATH_INJECTION CUSTOM_ENCODED_XXE CUSTOM_SECURITY_CONTROL_APPLIED CUSTOM_VALIDATED CUSTOM_VALIDATED_CMD_INJECTION CUSTOM_VALIDATED_EXPRESSION_LANGUAGE_INJECTION CUSTOM_VALIDATED_HEADER_INJECTION CUSTOM_VALIDATED_HQL_INJECTION CUSTOM_VALIDATED_LDAP_INJECTION CUSTOM_VALIDATED_LOG_INJECTION CUSTOM_VALIDATED_NOSQL_INJECTION CUSTOM_VALIDATED_PATH_TRAVERSAL CUSTOM_VALIDATED_REDOS CUSTOM_VALIDATED_REFLECTED_XSS CUSTOM_VALIDATED_REFLECTION_INJECTION CUSTOM_VALIDATED_SMTP_INJECTION CUSTOM_VALIDATED_SQL_INJECTION CUSTOM_VALIDATED_SSRF CUSTOM_VALIDATED_STORED_XSS CUSTOM_VALIDATED_TRUST_BOUNDARY_VIOLATION CUSTOM_VALIDATED_UNSAFE_CODE_EXECUTION CUSTOM_VALIDATED_UNSAFE_READLINE CUSTOM_VALIDATED_UNSAFE_XML_DECODE CUSTOM_VALIDATED_UNTRUSTED_DESERIALIZATION CUSTOM_VALIDATED_UNVALIDATED_FORWARD CUSTOM_VALIDATED_UNVALIDATED_REDIRECT CUSTOM_VALIDATED_XPATH_INJECTION CUSTOM_VALIDATED_XXE DATABASE_WRITE ].cs__freeze VALID_SOURCE_TAGS = %w[ NO_NEWLINES UNTRUSTED CROSS_SITE LIMITED_CHARS ].cs__freeze # The keys used to read from policy.json to create the individual # policy nodes. These are common across node types JSON_SOURCE = 'source' JSON_TARGET = 'target' JSON_TAGS = 'tags' JSON_DATAFLOW = 'dataflow' end end end end end