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.



49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
# File 'lib/contrast/agent/assess/policy/trigger_node.rb', line 49

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_valueObject (readonly)

Returns the value of attribute bad_value.



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

def bad_value
  @bad_value
end

#disallowed_tagsObject (readonly)

Returns the value of attribute disallowed_tags.



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

def disallowed_tags
  @disallowed_tags
end

#good_valueObject (readonly)

Returns the value of attribute good_value.



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

def good_value
  @good_value
end

#required_tagsObject (readonly)

Returns the value of attribute required_tags.



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

def required_tags
  @required_tags
end

#rule_idObject (readonly)

Returns the value of attribute rule_id.



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

def rule_id
  @rule_id
end

Instance Method Details

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



74
75
76
# File 'lib/contrast/agent/assess/policy/trigger_node.rb', line 74

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)


94
95
96
# File 'lib/contrast/agent/assess/policy/trigger_node.rb', line 94

def collectable?
  COLLECTABLE_RULES.include?(rule_id)
end

#custom_patch?Boolean

Returns:

  • (Boolean)


86
87
88
# File 'lib/contrast/agent/assess/policy/trigger_node.rb', line 86

def custom_patch?
  @custom_patch
end

#custom_trigger?Boolean

Returns:

  • (Boolean)


82
83
84
# File 'lib/contrast/agent/assess/policy/trigger_node.rb', line 82

def custom_trigger?
  @trigger_class && @trigger_method
end

#custom_trigger_classObject



78
79
80
# File 'lib/contrast/agent/assess/policy/trigger_node.rb', line 78

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)


106
107
108
# File 'lib/contrast/agent/assess/policy/trigger_node.rb', line 106

def dataflow?
  @dataflow
end

#loud_nameObject

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



121
122
123
124
# File 'lib/contrast/agent/assess/policy/trigger_node.rb', line 121

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

#node_classObject



70
71
72
# File 'lib/contrast/agent/assess/policy/trigger_node.rb', line 70

def node_class
  TRIGGER
end

#node_typeObject



90
91
92
# File 'lib/contrast/agent/assess/policy/trigger_node.rb', line 90

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)


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

def regexp_rule?
  @regexp
end

#rule_disabled?Boolean

Returns:

  • (Boolean)


98
99
100
# File 'lib/contrast/agent/assess/policy/trigger_node.rb', line 98

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)


153
154
155
156
157
158
# File 'lib/contrast/agent/assess/policy/trigger_node.rb', line 153

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)


127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
# File 'lib/contrast/agent/assess/policy/trigger_node.rb', line 127

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