Class: Contrast::Agent::Assess::Policy::TriggerNode
- Inherits:
-
PolicyNode
- Object
- Patching::Policy::PolicyNode
- PolicyNode
- Contrast::Agent::Assess::Policy::TriggerNode
- 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
- #bad_value ⇒ Array<String> readonly
- #disallowed_tags ⇒ Array<String> readonly
- #good_value ⇒ Array<String> readonly
- #required_tags ⇒ Array<String> readonly
- #rule_id ⇒ String readonly
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
- #apply_custom_trigger(trigger_node, source, object, ret, *args) ⇒ Object
- #collectable? ⇒ Boolean
- #custom_patch? ⇒ Boolean
- #custom_trigger? ⇒ Boolean
- #custom_trigger_class ⇒ Object
-
#dataflow? ⇒ Boolean
Indicate if this is a dataflow based trigger, meaning it has a proper synch that requires a tainted source to reach it.
-
#initialize(trigger_hash = {}, rule_hash = {}) ⇒ TriggerNode
constructor
A new instance of TriggerNode.
-
#loud_name ⇒ Object
the name of the rule, in capital & underscore format used to make it match enum things in TeamServer.
- #node_class ⇒ Object
- #node_type ⇒ Object
-
#regexp_rule? ⇒ Boolean
Indicate if this is a regexp based trigger, meaning it has a proper synch that requires a source to reach it.
- #rule_disabled? ⇒ Boolean
-
#validate ⇒ Object
Standard validation + TS trace version two rules: Must have source @raise raises if any of the required fields is invalid or missing.
-
#violated?(source) ⇒ Boolean
Determine if a dataflow rule violation has occurred.
Methods included from Components::Logger::InstanceMethods
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 = (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_value ⇒ Array<String> (readonly)
41 42 43 |
# File 'lib/contrast/agent/assess/policy/trigger_node.rb', line 41 def bad_value @bad_value end |
#disallowed_tags ⇒ Array<String> (readonly)
37 38 39 |
# File 'lib/contrast/agent/assess/policy/trigger_node.rb', line 37 def @disallowed_tags end |
#good_value ⇒ Array<String> (readonly)
39 40 41 |
# File 'lib/contrast/agent/assess/policy/trigger_node.rb', line 39 def good_value @good_value end |
#required_tags ⇒ Array<String> (readonly)
35 36 37 |
# File 'lib/contrast/agent/assess/policy/trigger_node.rb', line 35 def @required_tags end |
#rule_id ⇒ String (readonly)
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
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
96 97 98 |
# File 'lib/contrast/agent/assess/policy/trigger_node.rb', line 96 def custom_patch? @custom_patch end |
#custom_trigger? ⇒ 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_class ⇒ Object
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
116 117 118 |
# File 'lib/contrast/agent/assess/policy/trigger_node.rb', line 116 def dataflow? @dataflow end |
#loud_name ⇒ Object
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_class ⇒ Object
80 81 82 |
# File 'lib/contrast/agent/assess/policy/trigger_node.rb', line 80 def node_class TRIGGER end |
#node_type ⇒ Object
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.
125 126 127 |
# File 'lib/contrast/agent/assess/policy/trigger_node.rb', line 125 def regexp_rule? @regexp end |
#rule_disabled? ⇒ 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 |
#validate ⇒ Object
Standard validation + TS trace version two rules: Must have source @raise raises if any of the required fields is invalid or missing
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
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 = (properties, ) # 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, ) # 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 |