Class: Contrast::Agent::Assess::Policy::TriggerNode

Inherits:
PolicyNode show all
Includes:
Components::Logger::InstanceMethods
Defined in:
lib/contrast/agent/assess/policy/trigger_node.rb

Overview

This class functions to translate our policy.json into an actionable Ruby object, allowing for dynamic patching over hardcoded patching, specifically for those methods which result in the trigger of a vulnerability (indicate points in the application where uncontrolled user input can do damage).

Constant Summary collapse

JSON_BAD_VALUE =
'bad_value'
JSON_GOOD_VALUE =
'good_value'
JSON_DISALLOWED_TAGS =
'disallowed_tags'
JSON_REQUIRED_TAGS =
'required_tags'
JSON_RULE_NAME =
'name'
JSON_CUSTOM_PATCH =
'custom_patch'
COLLECTABLE_RULES =

Our list with rules to be collected and reported back when we have response from the application. Some rules rely on Content-Type validation.

%w[reflected-xss].cs__freeze
ENCODER_START =
'CUSTOM_ENCODED_'
UNTRUSTED =

By default, any rule will be triggered if the source of the rule event has an untrusted tag range that is not covered by one of its disallowed tags.

'UNTRUSTED'
TRIGGER =
'Trigger'
VALIDATOR_START =
'CUSTOM_VALIDATED_'
LIMITED_CHARS =

If a level 1 rule comes from TeamServer, it will have the tag ‘custom-encoder-#{ name }’ or ‘custom-validator-#{ name }’. All rules should take this into account. Additionally, if something is marked ‘limited-chars’ it means it has been properly vetted to not contain dangerous input.

'LIMITED_CHARS'
CUSTOM_ENCODED =
'CUSTOM_ENCODED'
CUSTOM_VALIDATED =
'CUSTOM_VALIDATED'

Constants inherited from PolicyNode

PolicyNode::ALL_TYPE, PolicyNode::JSON_DATAFLOW, PolicyNode::JSON_SOURCE, PolicyNode::JSON_TAGS, PolicyNode::JSON_TARGET, PolicyNode::ORIGINAL_OBJECT_METHODS, PolicyNode::RESPONSE_SOURCES, PolicyNode::TO_MARKER, PolicyNode::TO_S

Constants inherited from Patching::Policy::PolicyNode

Patching::Policy::PolicyNode::JSON_CLASS_NAME, Patching::Policy::PolicyNode::JSON_INSTANCE_METHOD, Patching::Policy::PolicyNode::JSON_METHOD_NAME, Patching::Policy::PolicyNode::JSON_METHOD_SCOPE, Patching::Policy::PolicyNode::JSON_METHOD_VISIBILITY, Patching::Policy::PolicyNode::JSON_PROPERTIES

Instance Attribute Summary collapse

Attributes inherited from PolicyNode

#source_string, #sources, #tags, #target_string, #targets, #type

Attributes inherited from Patching::Policy::PolicyNode

#class_name, #instance_method, #method_name, #method_scope, #method_visibility, #properties

Instance Method Summary collapse

Methods included from Components::Logger::InstanceMethods

#cef_logger, #logger

Methods inherited from PolicyNode

#add_property, #assign_on_bang_check, #build_action, #feature, #get_property, #response_source_node?, #use_original_object?, #use_original_on_bang_method?, #use_response_as_source?, #validate_tags

Methods inherited from Patching::Policy::PolicyNode

#feature, #id, #instance_method?

Methods included from Components::Scope::InstanceMethods

#contrast_enter_method_scopes!, #contrast_exit_method_scopes!, #with_app_scope, #with_contrast_scope, #with_deserialization_scope, #with_split_scope

Constructor Details

#initialize(trigger_hash = {}, rule_hash = {}) ⇒ TriggerNode

Returns a new instance of TriggerNode.



59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
# File 'lib/contrast/agent/assess/policy/trigger_node.rb', line 59

def initialize trigger_hash = {}, rule_hash = {}
  super(trigger_hash)
  good_value = trigger_hash[JSON_GOOD_VALUE]
  bad_value = trigger_hash[JSON_BAD_VALUE]
  @good_value = Regexp.new(good_value, true) if good_value
  @bad_value = Regexp.new(bad_value, true) if bad_value
  @regexp = !@dataflow && (@good_value || @bad_value)
  @custom_patch = trigger_hash.fetch(JSON_CUSTOM_PATCH, false)
  @rule_id = rule_hash.fetch(JSON_RULE_NAME) # raises KeyError exception if not found
  @dataflow = rule_hash.fetch(JSON_DATAFLOW, true)
  @required_tags = populate_tags(rule_hash[JSON_REQUIRED_TAGS])
  @disallowed_tags = populate_disallowed(rule_hash[JSON_DISALLOWED_TAGS])
  @trigger_class = trigger_hash['trigger_class']
  @trigger_method = trigger_hash['trigger_method']
  @trigger_method = @trigger_method.to_sym if @trigger_method
  validate
rescue ArgumentError => e
  logger.error('Trigger Node initialization failed with: ', e)
  nil
end

Instance Attribute Details

#bad_valueArray<String> (readonly)

Returns:



41
42
43
# File 'lib/contrast/agent/assess/policy/trigger_node.rb', line 41

def bad_value
  @bad_value
end

#disallowed_tagsArray<String> (readonly)

Returns:



37
38
39
# File 'lib/contrast/agent/assess/policy/trigger_node.rb', line 37

def disallowed_tags
  @disallowed_tags
end

#good_valueArray<String> (readonly)

Returns:



39
40
41
# File 'lib/contrast/agent/assess/policy/trigger_node.rb', line 39

def good_value
  @good_value
end

#required_tagsArray<String> (readonly)

Returns:



35
36
37
# File 'lib/contrast/agent/assess/policy/trigger_node.rb', line 35

def required_tags
  @required_tags
end

#rule_idString (readonly)

Returns:



33
34
35
# File 'lib/contrast/agent/assess/policy/trigger_node.rb', line 33

def rule_id
  @rule_id
end

Instance Method Details

#apply_custom_trigger(trigger_node, source, object, ret, *args) ⇒ Object



84
85
86
# File 'lib/contrast/agent/assess/policy/trigger_node.rb', line 84

def apply_custom_trigger trigger_node, source, object, ret, *args
  custom_trigger_class.send(@trigger_method, trigger_node, source, object, ret, *args)
end

#collectable?Boolean

Returns:

  • (Boolean)


104
105
106
# File 'lib/contrast/agent/assess/policy/trigger_node.rb', line 104

def collectable?
  COLLECTABLE_RULES.include?(rule_id)
end

#custom_patch?Boolean

Returns:

  • (Boolean)


96
97
98
# File 'lib/contrast/agent/assess/policy/trigger_node.rb', line 96

def custom_patch?
  @custom_patch
end

#custom_trigger?Boolean

Returns:

  • (Boolean)


92
93
94
# File 'lib/contrast/agent/assess/policy/trigger_node.rb', line 92

def custom_trigger?
  @trigger_class && @trigger_method
end

#custom_trigger_classObject



88
89
90
# File 'lib/contrast/agent/assess/policy/trigger_node.rb', line 88

def custom_trigger_class
  @_custom_trigger_class ||= Object.cs__const_get(@trigger_class)
end

#dataflow?Boolean

Indicate if this is a dataflow based trigger, meaning it has a proper synch that requires a tainted source to reach it. If this returns false, this rule is for method validation, ensuring that an insecure method, such as a non-cryptographically secure random, is not invoked

Returns:

  • (Boolean)


116
117
118
# File 'lib/contrast/agent/assess/policy/trigger_node.rb', line 116

def dataflow?
  @dataflow
end

#loud_nameObject

the name of the rule, in capital & underscore format used to make it match enum things in TeamServer



131
132
133
134
# File 'lib/contrast/agent/assess/policy/trigger_node.rb', line 131

def loud_name
  @_loud_name ||= rule_id.upcase.gsub(Contrast::Utils::ObjectShare::DASH,
                                      Contrast::Utils::ObjectShare::UNDERSCORE)
end

#node_classObject



80
81
82
# File 'lib/contrast/agent/assess/policy/trigger_node.rb', line 80

def node_class
  TRIGGER
end

#node_typeObject



100
101
102
# File 'lib/contrast/agent/assess/policy/trigger_node.rb', line 100

def node_type
  :TYPE_METHOD
end

#regexp_rule?Boolean

Indicate if this is a regexp based trigger, meaning it has a proper synch that requires a source to reach it. While this type of rule does not require the source to be tainted, it does validate it with a regular expression to determine if the method is being invoked safely.

Returns:

  • (Boolean)


125
126
127
# File 'lib/contrast/agent/assess/policy/trigger_node.rb', line 125

def regexp_rule?
  @regexp
end

#rule_disabled?Boolean

Returns:

  • (Boolean)


108
109
110
# File 'lib/contrast/agent/assess/policy/trigger_node.rb', line 108

def rule_disabled?
  ::Contrast::ASSESS.rule_disabled?(rule_id)
end

#validateObject

Standard validation + TS trace version two rules: Must have source @raise raises if any of the required fields is invalid or missing

Raises:

  • (ArgumentError)


163
164
165
166
167
168
# File 'lib/contrast/agent/assess/policy/trigger_node.rb', line 163

def validate
  super
  # If this isn't a dataflow rule, it can't have a source
  return unless dataflow?
  raise(ArgumentError, "Trigger #{ id } did not have a proper source. Unable to create.") unless sources&.any?
end

#violated?(source) ⇒ Boolean

Determine if a dataflow rule violation has occurred

Returns:

  • (Boolean)


137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
# File 'lib/contrast/agent/assess/policy/trigger_node.rb', line 137

def violated? source
  # if the source isn't tracked, there can't be a violation
  # this condition may not hold true forever, but for now it's
  # a nice optimization
  return false unless Contrast::Agent::Assess::Tracker.tracked?(source)

  properties = Contrast::Agent::Assess::Tracker.properties(source)
  # find the ranges that violate the rule (untrusted, etc)
  vulnerable_ranges = ranges_with_all_tags(properties, required_tags)
  # if there aren't any vulnerable ranges, nope out
  return false if vulnerable_ranges.empty?

  # find the ranges that are exempt from the rule
  # (validated, sanitized, etc)
  secure_ranges = ranges_with_any_tag(properties, disallowed_tags)
  # if there are vulnerable ranges and no secure, report
  return true if secure_ranges.empty?

  # figure out if there are any vulnerable ranges that aren't
  # covered by a secure one. if there are, the rule was violated
  !Contrast::Utils::TagUtil.covered?(vulnerable_ranges, secure_ranges)
end