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

Inherits:
PolicyNode show all
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'
TRIGGER =
'Trigger'

Constants inherited from PolicyNode

PolicyNode::ALL_TYPE, PolicyNode::JSON_DATAFLOW, PolicyNode::JSON_SOURCE, PolicyNode::JSON_TAGS, PolicyNode::JSON_TARGET, PolicyNode::TO_MARKER

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 inherited from PolicyNode

#add_property, #build_action, #feature, #generate_sources, #generate_targets, #get_property, #target, #validate_tags

Methods inherited from Patching::Policy::PolicyNode

#feature, #id, #instance_method?

Methods included from Components::Interface

included

Constructor Details

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

Returns a new instance of TriggerNode.



27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
# File 'lib/contrast/agent/assess/policy/trigger_node.rb', line 27

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
end

Instance Attribute Details

#bad_valueObject (readonly)

Returns the value of attribute bad_value.



25
26
27
# File 'lib/contrast/agent/assess/policy/trigger_node.rb', line 25

def bad_value
  @bad_value
end

#disallowed_tagsObject (readonly)

Returns the value of attribute disallowed_tags.



25
26
27
# File 'lib/contrast/agent/assess/policy/trigger_node.rb', line 25

def disallowed_tags
  @disallowed_tags
end

#good_valueObject (readonly)

Returns the value of attribute good_value.



25
26
27
# File 'lib/contrast/agent/assess/policy/trigger_node.rb', line 25

def good_value
  @good_value
end

#required_tagsObject (readonly)

Returns the value of attribute required_tags.



25
26
27
# File 'lib/contrast/agent/assess/policy/trigger_node.rb', line 25

def required_tags
  @required_tags
end

#rule_idObject (readonly)

Returns the value of attribute rule_id.



25
26
27
# File 'lib/contrast/agent/assess/policy/trigger_node.rb', line 25

def rule_id
  @rule_id
end

Instance Method Details

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



50
51
52
# File 'lib/contrast/agent/assess/policy/trigger_node.rb', line 50

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

#custom_patch?Boolean

Returns:

  • (Boolean)


62
63
64
# File 'lib/contrast/agent/assess/policy/trigger_node.rb', line 62

def custom_patch?
  @custom_patch
end

#custom_trigger?Boolean

Returns:

  • (Boolean)


58
59
60
# File 'lib/contrast/agent/assess/policy/trigger_node.rb', line 58

def custom_trigger?
  @trigger_class && @trigger_method
end

#custom_trigger_classObject



54
55
56
# File 'lib/contrast/agent/assess/policy/trigger_node.rb', line 54

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)


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

def dataflow?
  @dataflow
end

#loud_nameObject

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



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

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

#node_classObject



46
47
48
# File 'lib/contrast/agent/assess/policy/trigger_node.rb', line 46

def node_class
  TRIGGER
end

#node_typeObject



66
67
68
# File 'lib/contrast/agent/assess/policy/trigger_node.rb', line 66

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)


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

def regexp_rule?
  @regexp
end

#rule_disabled?Boolean

Returns:

  • (Boolean)


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

def rule_disabled?
  ASSESS.rule_disabled?(rule_id)
end

#validateObject

Standard validation + TS trace version two rules: Must have source

Raises:

  • (ArgumentError)


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

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)


99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
# File 'lib/contrast/agent/assess/policy/trigger_node.rb', line 99

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(Contrast::Utils::StringUtils.ret_length(source), 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