contrast-agent 3.15.0 → 4.3.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (158) hide show
  1. checksums.yaml +4 -4
  2. data/Rakefile +1 -0
  3. data/ext/cs__assess_marshal_module/cs__assess_marshal_module.c +22 -10
  4. data/ext/cs__assess_marshal_module/cs__assess_marshal_module.h +4 -3
  5. data/lib/contrast/agent.rb +4 -12
  6. data/lib/contrast/agent/assess/contrast_event.rb +121 -130
  7. data/lib/contrast/agent/assess/contrast_object.rb +51 -0
  8. data/lib/contrast/agent/assess/events/source_event.rb +5 -10
  9. data/lib/contrast/agent/assess/policy/dynamic_source_factory.rb +10 -3
  10. data/lib/contrast/agent/assess/policy/patcher.rb +4 -3
  11. data/lib/contrast/agent/assess/policy/policy_node.rb +46 -69
  12. data/lib/contrast/agent/assess/policy/policy_scanner.rb +19 -2
  13. data/lib/contrast/agent/assess/policy/preshift.rb +3 -3
  14. data/lib/contrast/agent/assess/policy/propagation_method.rb +13 -19
  15. data/lib/contrast/agent/assess/policy/propagation_node.rb +12 -24
  16. data/lib/contrast/agent/assess/policy/propagator/append.rb +1 -2
  17. data/lib/contrast/agent/assess/policy/propagator/center.rb +1 -2
  18. data/lib/contrast/agent/assess/policy/propagator/custom.rb +1 -1
  19. data/lib/contrast/agent/assess/policy/propagator/database_write.rb +1 -3
  20. data/lib/contrast/agent/assess/policy/propagator/insert.rb +2 -3
  21. data/lib/contrast/agent/assess/policy/propagator/keep.rb +1 -2
  22. data/lib/contrast/agent/assess/policy/propagator/match_data.rb +3 -5
  23. data/lib/contrast/agent/assess/policy/propagator/next.rb +1 -2
  24. data/lib/contrast/agent/assess/policy/propagator/prepend.rb +1 -2
  25. data/lib/contrast/agent/assess/policy/propagator/remove.rb +2 -4
  26. data/lib/contrast/agent/assess/policy/propagator/replace.rb +1 -2
  27. data/lib/contrast/agent/assess/policy/propagator/reverse.rb +1 -2
  28. data/lib/contrast/agent/assess/policy/propagator/select.rb +4 -7
  29. data/lib/contrast/agent/assess/policy/propagator/splat.rb +2 -9
  30. data/lib/contrast/agent/assess/policy/propagator/split.rb +77 -122
  31. data/lib/contrast/agent/assess/policy/propagator/substitution.rb +32 -25
  32. data/lib/contrast/agent/assess/policy/propagator/trim.rb +3 -7
  33. data/lib/contrast/agent/assess/policy/source_method.rb +2 -14
  34. data/lib/contrast/agent/assess/policy/trigger/reflected_xss.rb +9 -13
  35. data/lib/contrast/agent/assess/policy/trigger/xpath.rb +1 -1
  36. data/lib/contrast/agent/assess/policy/trigger_method.rb +39 -14
  37. data/lib/contrast/agent/assess/policy/trigger_node.rb +31 -37
  38. data/lib/contrast/agent/assess/policy/trigger_validation/ssrf_validator.rb +1 -1
  39. data/lib/contrast/agent/assess/property/evented.rb +5 -18
  40. data/lib/contrast/agent/assess/property/tagged.rb +28 -16
  41. data/lib/contrast/agent/assess/property/updated.rb +0 -5
  42. data/lib/contrast/agent/assess/rule/provider/hardcoded_key.rb +58 -5
  43. data/lib/contrast/agent/assess/rule/provider/hardcoded_password.rb +23 -8
  44. data/lib/contrast/agent/assess/rule/provider/hardcoded_value_rule.rb +83 -14
  45. data/lib/contrast/agent/assess/rule/redos.rb +1 -1
  46. data/lib/contrast/agent/assess/tag.rb +1 -1
  47. data/lib/contrast/agent/assess/tracker.rb +16 -18
  48. data/lib/contrast/agent/at_exit_hook.rb +5 -5
  49. data/lib/contrast/agent/deadzone/policy/deadzone_node.rb +7 -0
  50. data/lib/contrast/agent/inventory.rb +15 -0
  51. data/lib/contrast/agent/inventory/dependencies.rb +50 -0
  52. data/lib/contrast/agent/inventory/dependency_analysis.rb +37 -0
  53. data/lib/contrast/agent/inventory/dependency_usage_analysis.rb +104 -0
  54. data/lib/contrast/agent/inventory/gemfile_digest_cache.rb +38 -0
  55. data/lib/contrast/agent/middleware.rb +51 -3
  56. data/lib/contrast/agent/patching/policy/after_load_patch.rb +5 -5
  57. data/lib/contrast/agent/patching/policy/after_load_patcher.rb +20 -20
  58. data/lib/contrast/agent/patching/policy/method_policy.rb +1 -1
  59. data/lib/contrast/agent/patching/policy/module_policy.rb +10 -10
  60. data/lib/contrast/agent/patching/policy/patch.rb +6 -0
  61. data/lib/contrast/agent/patching/policy/policy.rb +16 -2
  62. data/lib/contrast/agent/protect/policy/applies_command_injection_rule.rb +3 -5
  63. data/lib/contrast/agent/protect/policy/applies_deserialization_rule.rb +47 -1
  64. data/lib/contrast/agent/protect/policy/applies_path_traversal_rule.rb +4 -3
  65. data/lib/contrast/agent/protect/policy/applies_xxe_rule.rb +1 -1
  66. data/lib/contrast/agent/protect/policy/rule_applicator.rb +53 -0
  67. data/lib/contrast/agent/protect/rule/base.rb +63 -14
  68. data/lib/contrast/agent/protect/rule/cmd_injection.rb +12 -28
  69. data/lib/contrast/agent/protect/rule/default_scanner.rb +1 -4
  70. data/lib/contrast/agent/protect/rule/deserialization.rb +4 -1
  71. data/lib/contrast/agent/protect/rule/no_sqli.rb +3 -3
  72. data/lib/contrast/agent/protect/rule/no_sqli/mongo_no_sql_scanner.rb +1 -0
  73. data/lib/contrast/agent/protect/rule/sqli.rb +3 -3
  74. data/lib/contrast/agent/protect/rule/xxe.rb +32 -11
  75. data/lib/contrast/agent/protect/rule/xxe/entity_wrapper.rb +10 -6
  76. data/lib/contrast/agent/reaction_processor.rb +1 -1
  77. data/lib/contrast/agent/request.rb +34 -34
  78. data/lib/contrast/agent/request_handler.rb +1 -1
  79. data/lib/contrast/agent/response.rb +5 -5
  80. data/lib/contrast/agent/rewriter.rb +3 -3
  81. data/lib/contrast/agent/scope.rb +81 -55
  82. data/lib/contrast/agent/static_analysis.rb +15 -9
  83. data/lib/contrast/agent/tracepoint_hook.rb +1 -1
  84. data/lib/contrast/agent/version.rb +1 -1
  85. data/lib/contrast/api/communication/socket_client.rb +36 -1
  86. data/lib/contrast/api/decorators.rb +3 -0
  87. data/lib/contrast/api/decorators/address.rb +13 -14
  88. data/lib/contrast/api/decorators/application_update.rb +1 -1
  89. data/lib/contrast/api/decorators/library.rb +54 -0
  90. data/lib/contrast/api/decorators/library_usage_update.rb +31 -0
  91. data/lib/contrast/api/decorators/message.rb +1 -0
  92. data/lib/contrast/api/decorators/trace_event.rb +31 -41
  93. data/lib/contrast/api/decorators/trace_event_object.rb +11 -3
  94. data/lib/contrast/api/decorators/trace_event_signature.rb +27 -5
  95. data/lib/contrast/api/decorators/user_input.rb +2 -1
  96. data/lib/contrast/common_agent_configuration.rb +2 -1
  97. data/lib/contrast/components/agent.rb +6 -5
  98. data/lib/contrast/components/app_context.rb +39 -30
  99. data/lib/contrast/components/assess.rb +36 -0
  100. data/lib/contrast/components/config.rb +29 -37
  101. data/lib/contrast/components/contrast_service.rb +9 -9
  102. data/lib/contrast/components/interface.rb +30 -6
  103. data/lib/contrast/components/inventory.rb +6 -1
  104. data/lib/contrast/components/scope.rb +72 -6
  105. data/lib/contrast/components/settings.rb +23 -23
  106. data/lib/contrast/config/assess_configuration.rb +2 -1
  107. data/lib/contrast/config/inventory_configuration.rb +2 -2
  108. data/lib/contrast/config/service_configuration.rb +4 -2
  109. data/lib/contrast/configuration.rb +1 -1
  110. data/lib/contrast/extension/assess/array.rb +9 -6
  111. data/lib/contrast/extension/assess/erb.rb +6 -3
  112. data/lib/contrast/extension/assess/eval_trigger.rb +6 -6
  113. data/lib/contrast/extension/assess/exec_trigger.rb +0 -3
  114. data/lib/contrast/extension/assess/fiber.rb +5 -6
  115. data/lib/contrast/extension/assess/hash.rb +7 -5
  116. data/lib/contrast/extension/assess/kernel.rb +19 -22
  117. data/lib/contrast/extension/assess/marshal.rb +40 -28
  118. data/lib/contrast/extension/assess/regexp.rb +6 -11
  119. data/lib/contrast/extension/assess/string.rb +14 -13
  120. data/lib/contrast/extension/protect/kernel.rb +3 -3
  121. data/lib/contrast/framework/base_support.rb +51 -53
  122. data/lib/contrast/framework/manager.rb +6 -5
  123. data/lib/contrast/framework/rack/patch/session_cookie.rb +10 -10
  124. data/lib/contrast/framework/rack/support.rb +2 -1
  125. data/lib/contrast/framework/rails/patch/action_controller_live_buffer.rb +14 -14
  126. data/lib/contrast/framework/rails/patch/assess_configuration.rb +1 -1
  127. data/lib/contrast/framework/rails/patch/rails_application_configuration.rb +11 -11
  128. data/lib/contrast/framework/rails/patch/support.rb +1 -1
  129. data/lib/contrast/framework/rails/rewrite/action_controller_railties_helper_inherited.rb +12 -12
  130. data/lib/contrast/framework/rails/rewrite/active_record_attribute_methods_read.rb +13 -13
  131. data/lib/contrast/framework/rails/rewrite/active_record_named.rb +3 -3
  132. data/lib/contrast/framework/rails/rewrite/active_record_time_zone_inherited.rb +13 -13
  133. data/lib/contrast/framework/rails/support.rb +5 -1
  134. data/lib/contrast/framework/sinatra/patch/base.rb +11 -11
  135. data/lib/contrast/framework/sinatra/support.rb +7 -6
  136. data/lib/contrast/logger/application.rb +1 -4
  137. data/lib/contrast/logger/log.rb +7 -2
  138. data/lib/contrast/utils/duck_utils.rb +1 -1
  139. data/lib/contrast/utils/heap_dump_util.rb +1 -1
  140. data/lib/contrast/utils/invalid_configuration_util.rb +2 -5
  141. data/lib/contrast/utils/inventory_util.rb +0 -7
  142. data/lib/contrast/utils/object_share.rb +3 -3
  143. data/lib/contrast/utils/preflight_util.rb +1 -1
  144. data/lib/contrast/utils/prevent_serialization.rb +1 -1
  145. data/lib/contrast/utils/resource_loader.rb +1 -1
  146. data/lib/contrast/utils/sha256_builder.rb +2 -14
  147. data/lib/contrast/utils/string_utils.rb +1 -1
  148. data/lib/contrast/utils/tag_util.rb +9 -13
  149. data/resources/assess/policy.json +31 -12
  150. data/resources/deadzone/policy.json +156 -0
  151. data/resources/protect/policy.json +12 -0
  152. data/ruby-agent.gemspec +11 -6
  153. data/service_executables/VERSION +1 -1
  154. data/service_executables/linux/contrast-service +0 -0
  155. data/service_executables/mac/contrast-service +0 -0
  156. metadata +91 -28
  157. data/lib/contrast/utils/boolean_util.rb +0 -30
  158. data/lib/contrast/utils/gemfile_reader.rb +0 -193
@@ -51,16 +51,23 @@ module Contrast
51
51
  incoming_tracked = args && determine_tracked(args)
52
52
  return ret unless self_tracked || incoming_tracked
53
53
 
54
+ parent_events = []
54
55
  if block
55
56
  block_sub(self_tracked, source, ret)
56
57
  elsif args.is_a?(String)
57
- string_sub(self_tracked, preshift, ret, args, incoming_tracked, global)
58
+ string_sub(parent_events, self_tracked, preshift, ret, args, incoming_tracked, global)
58
59
  elsif args.is_a?(Hash)
59
60
  hash_sub(self_tracked, source, ret)
60
61
  else # Enumerator, only for gsub
61
- pattern_gsub(preshift, ret)
62
+ pattern_gsub(parent_events, preshift, ret)
62
63
  end
63
- string_build_event(patcher, preshift, ret)
64
+
65
+ if self_tracked
66
+ source_properties = Contrast::Agent::Assess::Tracker.properties(source)
67
+ parent_event = source_properties&.event
68
+ parent_events.prepend(parent_event) if parent_event
69
+ end
70
+ string_build_event(parent_events, patcher, preshift, ret)
64
71
  rescue StandardError => e
65
72
  logger.error('Unable to apply gsub propagator', e)
66
73
  end
@@ -84,9 +91,12 @@ module Contrast
84
91
  end
85
92
  end
86
93
 
87
- def string_sub self_tracked, preshift, ret, incoming, incoming_tracked, global
88
- properties = Contrast::Agent::Assess::Tracker.properties(ret)
89
- return unless properties
94
+ def string_sub parent_events, self_tracked, preshift, ret, incoming, incoming_tracked, global
95
+ return unless (properties = Contrast::Agent::Assess::Tracker.properties!(ret))
96
+
97
+ incoming_properties = Contrast::Agent::Assess::Tracker.properties(incoming)
98
+ parent_event = incoming_properties&.event
99
+ parent_events << parent_event if parent_event
90
100
 
91
101
  pattern = preshift.args[0]
92
102
  source = preshift.object
@@ -119,8 +129,8 @@ module Contrast
119
129
  properties.delete_tags_at_ranges(ranges)
120
130
  properties.shift_tags(ranges)
121
131
  return unless incoming_tracked
132
+ return unless incoming_properties
122
133
 
123
- incoming_properties = Contrast::Agent::Assess::Tracker.properties(incoming)
124
134
  tags = incoming_properties.tag_keys
125
135
  ranges.each do |range|
126
136
  tags.each do |tag|
@@ -130,40 +140,36 @@ module Contrast
130
140
  end
131
141
 
132
142
  def block_sub self_tracked, source, ret
133
- properties = Contrast::Agent::Assess::Tracker.properties(ret)
134
- properties&.splat_from(source, ret) if self_tracked
143
+ return unless self_tracked
144
+
145
+ properties = Contrast::Agent::Assess::Tracker.properties!(ret)
146
+ properties&.splat_from(source, ret)
135
147
  end
136
148
 
137
149
  def hash_sub self_tracked, source, ret
138
- properties = Contrast::Agent::Assess::Tracker.properties(ret)
139
- properties&.splat_from(source, ret) if self_tracked
140
- end
150
+ return unless self_tracked
141
151
 
142
- def pattern_gsub preshift, ret
143
- properties = Contrast::Agent::Assess::Tracker.properties(ret)
144
- return unless properties
152
+ properties = Contrast::Agent::Assess::Tracker.properties!(ret)
153
+ properties&.splat_from(source, ret)
154
+ end
145
155
 
156
+ def pattern_gsub parent_events, preshift, ret
146
157
  source = preshift.object
147
- source_properties = Contrast::Agent::Assess::Tracker.properties(source)
148
- return unless source_properties
158
+ return unless (source_properties = Contrast::Agent::Assess::Tracker.properties(source))
159
+ return unless (properties = Contrast::Agent::Assess::Tracker.properties!(ret))
149
160
 
150
161
  source_properties.tag_keys.each do |key|
151
162
  properties.add_tag(key, 0...1)
152
163
  end
164
+ parent_event = source_properties.event
165
+ parent_events << parent_event if parent_event
153
166
  end
154
167
 
155
- def string_build_event patcher, preshift, ret
168
+ def string_build_event parent_events, patcher, preshift, ret
156
169
  return unless Contrast::Agent::Assess::Tracker.tracked?(ret)
157
170
 
158
171
  properties = Contrast::Agent::Assess::Tracker.properties(ret)
159
172
  args = preshift.args
160
- if args.length > 1
161
- arg = args[1]
162
- arg_properties = Contrast::Agent::Assess::Tracker.properties(arg)
163
- arg_properties&.events&.each do |event|
164
- properties.events << event
165
- end
166
- end
167
173
  properties.build_event(
168
174
  patcher,
169
175
  ret,
@@ -171,6 +177,7 @@ module Contrast
171
177
  ret,
172
178
  args,
173
179
  2)
180
+ properties.event.instance_variable_set(:@_parent_events, parent_events)
174
181
  end
175
182
  end
176
183
  end
@@ -11,13 +11,11 @@ module Contrast
11
11
  # Disclaimer: there may be a better way, but we're
12
12
  # in a 'get it work' state. hopefully, we'll be in
13
13
  # a 'get it right' state soon.
14
- class Trim
14
+ module Trim
15
15
  class << self
16
16
  def tr_tagger patcher, preshift, ret, _block
17
17
  return ret unless ret && !ret.empty?
18
-
19
- properties = Contrast::Agent::Assess::Tracker.properties(ret)
20
- return unless properties
18
+ return ret unless (properties = Contrast::Agent::Assess::Tracker.properties!(ret))
21
19
 
22
20
  source = preshift.object
23
21
  args = preshift.args
@@ -59,9 +57,7 @@ module Contrast
59
57
 
60
58
  def tr_s_tagger patcher, preshift, ret, _block
61
59
  return unless ret && !ret.empty?
62
-
63
- properties = Contrast::Agent::Assess::Tracker.properties(ret)
64
- return unless properties
60
+ return unless (properties = Contrast::Agent::Assess::Tracker.properties!(ret))
65
61
 
66
62
  source = preshift.object
67
63
  args = preshift.args
@@ -178,8 +178,8 @@ module Contrast
178
178
  # don't apply second source -- probably needs tuning later if we
179
179
  # use more than 'UNTRUSTED' in our sources
180
180
  return if Contrast::Agent::Assess::Tracker.tracked?(target)
181
+ return unless (properties = Contrast::Agent::Assess::Tracker.properties!(target))
181
182
 
182
- properties = Contrast::Agent::Assess::Tracker.properties(target)
183
183
  # otherwise for each tag this source_node applies, create a tag range
184
184
  # on the target object
185
185
  # I realize this looping is counter-intuitive from the above
@@ -243,19 +243,7 @@ module Contrast
243
243
  when Contrast::Utils::ObjectShare::OBJECT_KEY
244
244
  object
245
245
  else
246
- if source_target.is_a?(Integer)
247
- args[source_target]
248
- # If this isn't an index param, it's a named one. R.I.P.
249
- else
250
- arg = nil
251
- args.each do |search|
252
- next unless search.is_a?(Hash)
253
-
254
- arg = search[source_target]
255
- break if arg
256
- end
257
- arg
258
- end
246
+ args[source_target]
259
247
  end
260
248
  end
261
249
 
@@ -25,24 +25,22 @@ module Contrast
25
25
  TEMPLATE_PROPAGATION_NODE = Contrast::Agent::Assess::Policy::PropagationNode.new(NODE_HASH)
26
26
 
27
27
  def xss_tilt_trigger context, trigger_node, _source, object, ret, *args
28
- properties = Contrast::Agent::Assess::Tracker.properties(ret)
29
- return unless properties
28
+ return unless (properties = Contrast::Agent::Assess::Tracker.properties!(ret))
30
29
 
31
30
  scope = args[0]
32
31
  erb_template_prerender = object.instance_variable_get(:@data)
33
32
  interpolated_inputs = []
34
- handle_binding_variables(scope, erb_template_prerender, ret, interpolated_inputs)
35
- handle_local_variables(args, erb_template_prerender, ret, interpolated_inputs)
33
+ handle_binding_variables(scope, erb_template_prerender, ret, properties, interpolated_inputs)
34
+ handle_local_variables(args, erb_template_prerender, ret, properties, interpolated_inputs)
35
+ properties.build_event(TEMPLATE_PROPAGATION_NODE, ret, erb_template_prerender, ret, interpolated_inputs)
36
36
  unless interpolated_inputs.empty?
37
+ current_event = properties.event
37
38
  interpolated_inputs.each do |input|
38
39
  input_properties = Contrast::Agent::Assess::Tracker.properties(input)
39
- next unless input_properties
40
+ next unless input_properties&.event
40
41
 
41
- input_properties.events.each do |event|
42
- properties.events << event
43
- end
42
+ current_event.parent_events << input_properties.event
44
43
  end
45
- properties.build_event(TEMPLATE_PROPAGATION_NODE, ret, erb_template_prerender, ret, interpolated_inputs)
46
44
  end
47
45
 
48
46
  if Contrast::Agent::Assess::Tracker.tracked?(ret)
@@ -54,8 +52,7 @@ module Contrast
54
52
 
55
53
  private
56
54
 
57
- def handle_binding_variables scope, erb_template_prerender, ret, interpolated_inputs
58
- properties = Contrast::Agent::Assess::Tracker.properties(ret)
55
+ def handle_binding_variables scope, erb_template_prerender, ret, properties, interpolated_inputs
59
56
  binding_variables = scope.instance_variables
60
57
 
61
58
  binding_variables.each do |bound_variable_sym|
@@ -72,8 +69,7 @@ module Contrast
72
69
  end
73
70
  end
74
71
 
75
- def handle_local_variables args, erb_template_prerender, ret, interpolated_inputs
76
- properties = Contrast::Agent::Assess::Tracker.properties(ret)
72
+ def handle_local_variables args, erb_template_prerender, ret, properties, interpolated_inputs
77
73
  locals = args[1]
78
74
  locals.each do |local_name, local_value|
79
75
  next unless Contrast::Agent::Assess::Tracker.tracked?(local_value)
@@ -43,7 +43,7 @@ module Contrast
43
43
  next unless trigger_node.violated?(arg)
44
44
 
45
45
  Contrast::Agent::Assess::Policy::TriggerMethod.build_finding(
46
- context, trigger_node, arg, object, ret, args)
46
+ context, trigger_node, arg, object, ret, *args)
47
47
  end
48
48
 
49
49
  ret
@@ -27,6 +27,24 @@ module Contrast
27
27
  CURRENT_FINDING_VERSION = 4
28
28
 
29
29
  class << self
30
+ # Append the given finding to the given context to be reported when
31
+ # the Context's activity is sent to the Service or, in the absence
32
+ # of that Context, generate an Activity and queue it manually
33
+ # @param finding [Contrast::Api::Dtm::Finding]
34
+ def report_finding finding
35
+ context = Contrast::Agent::REQUEST_TRACKER.current
36
+ if context
37
+ context.activity.findings << finding
38
+ else
39
+ activity = Contrast::Api::Dtm::Activity.new
40
+ activity.findings << finding
41
+
42
+ Contrast::Agent.messaging_queue.send_event_eventually(activity)
43
+ end
44
+ logger.debug('Finding reported',
45
+ rule: finding.rule_id)
46
+ end
47
+
30
48
  # This is called from within our woven proc. It will be called as if it
31
49
  # were inline in the Rack application.
32
50
  #
@@ -69,8 +87,8 @@ module Contrast
69
87
  # This converts the source of the finding, and the events leading
70
88
  # up to it into a Finding
71
89
  #
72
- # @param context [Contrast::Utils::ThreadTracker] the current request
73
- # context
90
+ # @param context [Contrast::Agent::RequestContext] the current
91
+ # request context
74
92
  # @param trigger_node [Contrast::Agent::Assess::Policy::TriggerNode]
75
93
  # the node to direct applying this trigger event
76
94
  # @param source [Object] the source of the Trigger Event
@@ -98,11 +116,11 @@ module Contrast
98
116
  build_hash(finding, source)
99
117
  finding.routes << context.route if context.route
100
118
  finding.version = determine_compliance_version(finding)
101
- context.activity.findings << finding
102
119
  logger.trace('Finding created',
103
120
  node_id: trigger_node.id,
104
121
  source_id: source.__id__,
105
122
  rule: trigger_node.rule_id)
123
+ report_finding(finding)
106
124
  rescue StandardError => e
107
125
  logger.error('Unable to build a finding', e, rule: trigger_node.rule_id, node_id: trigger_node.id)
108
126
  end
@@ -112,8 +130,8 @@ module Contrast
112
130
  # This is our method that actually checks the taint on the object
113
131
  # our trigger_node targets.
114
132
  #
115
- # @param context [Contrast::Utils::ThreadTracker] the current request
116
- # context
133
+ # @param context [Contrast::Agent::RequestContext] the current
134
+ # request context
117
135
  # @param trigger_node [Contrast::Agent::Assess::Policy::TriggerNode]
118
136
  # the node to direct applying this trigger event
119
137
  # @param source [Object] the source of the Trigger Event
@@ -181,8 +199,8 @@ module Contrast
181
199
  # This is our method that actually checks the taint on the object
182
200
  # our trigger_node targets for our Regexp based rules.
183
201
  #
184
- # @param context [Contrast::Utils::ThreadTracker] the current request
185
- # context
202
+ # @param context [Contrast::Agent::RequestContext] the current
203
+ # request context
186
204
  # @param trigger_node [Contrast::Agent::Assess::Policy::TriggerNode]
187
205
  # the node to direct applying this trigger event
188
206
  # @param source [Object] the source of the Trigger Event
@@ -201,8 +219,8 @@ module Contrast
201
219
  # This is our method that actually checks the taint on the object
202
220
  # our trigger_node targets for our Dataflow based rules.
203
221
  #
204
- # @param context [Contrast::Utils::ThreadTracker] the current request
205
- # context
222
+ # @param context [Contrast::Agent::RequestContext] the current
223
+ # request context
206
224
  # @param trigger_node [Contrast::Agent::Assess::Policy::TriggerNode]
207
225
  # the node to direct applying this trigger event
208
226
  # @param source [Object] the source of the Trigger Event
@@ -244,11 +262,7 @@ module Contrast
244
262
  properties = Contrast::Agent::Assess::Tracker.properties(source)
245
263
  return unless properties
246
264
 
247
- # events could technically be nil, but we would have failed
248
- # the rule check before getting here. not worth the nil check
249
- properties.events.each do |event|
250
- finding.events << event.to_dtm_event
251
- end
265
+ build_events finding, properties.event if properties.event
252
266
 
253
267
  # Google::Protobuf::Map doesn't support merge!, so we have to do this
254
268
  # long form
@@ -261,6 +275,17 @@ module Contrast
261
275
  end
262
276
  end
263
277
 
278
+ def build_events finding, event
279
+ return unless event
280
+
281
+ event.parent_events&.each do |parent_event|
282
+ build_events(finding, parent_event)
283
+ end
284
+ # events could technically be nil, but we would have failed
285
+ # the rule check before getting here. not worth the nil check
286
+ finding.events << event.to_dtm_event
287
+ end
288
+
264
289
  def build_hash finding, source
265
290
  hash_code = Contrast::Utils::HashDigest.generate_event_hash(finding, source)
266
291
  finding.hash_code = Contrast::Utils::StringUtils.force_utf8(hash_code)
@@ -104,13 +104,13 @@ module Contrast
104
104
 
105
105
  properties = Contrast::Agent::Assess::Tracker.properties(source)
106
106
  # find the ranges that violate the rule (untrusted, etc)
107
- vulnerable_ranges = find_ranges_by_all_tags(Contrast::Utils::StringUtils.ret_length(source), properties, required_tags)
107
+ vulnerable_ranges = ranges_with_all_tags(Contrast::Utils::StringUtils.ret_length(source), properties, required_tags)
108
108
  # if there aren't any vulnerable ranges, nope out
109
109
  return false if vulnerable_ranges.empty?
110
110
 
111
111
  # find the ranges that are exempt from the rule
112
112
  # (validated, sanitized, etc)
113
- secure_ranges = find_ranges_by_any_tag(properties, disallowed_tags)
113
+ secure_ranges = ranges_with_any_tag(properties, disallowed_tags)
114
114
  # if there are vulnerable ranges and no secure, report
115
115
  return true if secure_ranges.empty?
116
116
 
@@ -181,49 +181,43 @@ module Contrast
181
181
  # tags.
182
182
  # @param properties [Contrast::Agent::Assess::Properties] the
183
183
  # properties to check for the tags
184
- # @param tags [Set<String>] the list of tags on which to match
184
+ # @param required_tags [Set<String>] the list of tags on which to match
185
185
  # @return [Array<Contrast::Agent::Assess::Tag>] the ranges satisfied
186
186
  # by the given conditions
187
- def find_ranges_by_all_tags length, properties, tags
188
- # if there aren't any all_tags or tags, break early
187
+ def ranges_with_all_tags length, properties, required_tags
188
+ # if there are no tags, not required tags, or the tags don't have
189
+ # all the required tags, we can just return here.
189
190
  return Contrast::Utils::ObjectShare::EMPTY_ARRAY unless properties.tracked?
190
- return Contrast::Utils::ObjectShare::EMPTY_ARRAY unless tags&.any?
191
-
192
- # :zap: faster to treat all as any if there's only one tag
193
- return find_ranges_by_any_tag(properties, tags) if tags.length == 1
191
+ return Contrast::Utils::ObjectShare::EMPTY_ARRAY unless required_tags&.any?
192
+ return Contrast::Utils::ObjectShare::EMPTY_ARRAY unless required_tags.all? { |tag| properties.tag_keys.include?(tag) }
194
193
 
195
194
  ranges = []
196
- # TODO: RUBY-946 clean this up, perhaps with
197
- # tags.each { |tag| applicable << properties.fetch_tag(tag) }
198
- # return Contrast::Utils::ObjectShare::EMPTY_ARRAY unless applicable.length == tags.length
199
- # ...
195
+ chunking = false
200
196
  # find all the indicies on the source that have all the given tags
201
197
  (0..length).each do |idx|
202
- tags_at = properties.tags_at(idx)
203
- ranges << idx if tags.all? do |tag|
204
- found = false
205
- tags_at.each do |tag_at|
206
- found = tag_at.label == tag
207
- break if found
208
- end
209
- found
198
+ tags_at = properties.tags_at(idx).to_a
199
+ # only those that have all the required tags in the tags_at
200
+ # satisfy the requirement
201
+ satisfied = tags_at.any? && required_tags.all? { |tag| tags_at.any? { |found| found.label == tag } }
202
+ # if this range matches all the required tags and we're already
203
+ # chunking, meaning the previous range matched, do nothing
204
+ next if satisfied && chunking
205
+
206
+ # if we are satisfied and we were not chunking, this represents
207
+ # the start of the next range, so create a new entry.
208
+ if satisfied
209
+ ranges << Contrast::Agent::Assess::Tag.new('required', 0, idx)
210
+ chunking = true
211
+ # if we are chunking and not satisfied, this represents the end
212
+ # of the range, meaning the last index is what satisfied the
213
+ # range. Because the range is exclusive end, we can just use this
214
+ # index.
215
+ elsif chunking
216
+ ranges[-1]&.update_end(idx)
217
+ chunking = false
210
218
  end
211
219
  end
212
- # break early if no indicies satisfy all the tags
213
- return Contrast::Utils::ObjectShare::EMPTY_ARRAY if ranges.empty?
214
-
215
- # chunk all the adjacent ranges
216
- chunked = ranges.chunk_while { |i, j| i + 1 == j }
217
- tag_ranges = []
218
- # and convert them into Tags
219
- chunked.each do |join|
220
- start = join[0]
221
- stop = join[-1]
222
- # add the 1 to account for end index being exclusive
223
- tag_length = stop - start + 1
224
- tag_ranges = Contrast::Utils::TagUtil.ordered_merge(tag_ranges, Tag.new(tag_length, start))
225
- end
226
- tag_ranges
220
+ ranges
227
221
  end
228
222
 
229
223
  # Find the ranges that satisfy any of the given tags.
@@ -233,7 +227,7 @@ module Contrast
233
227
  # @param tags [Set<String>] the list of tags on which to match
234
228
  # @return [Array<Contrast::Agent::Assess::Tag>] the ranges satisfied
235
229
  # by the given conditions
236
- def find_ranges_by_any_tag properties, tags
230
+ def ranges_with_any_tag properties, tags
237
231
  # if there aren't any all_tags or tags, break early
238
232
  return Contrast::Utils::ObjectShare::EMPTY_ARRAY unless properties.tracked?
239
233
  return Contrast::Utils::ObjectShare::EMPTY_ARRAY unless tags&.any?
@@ -39,7 +39,7 @@ module Contrast
39
39
  finish ||= url.length
40
40
 
41
41
  properties = Contrast::Agent::Assess::Tracker.properties(args[0])
42
- properties.any_tags_between?(start, finish)
42
+ properties&.any_tags_between?(start, finish)
43
43
  end
44
44
  end
45
45
  end