contrast-agent 4.9.0 → 4.12.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (109) hide show
  1. checksums.yaml +4 -4
  2. data/.rspec +0 -1
  3. data/.rspec_parallel +6 -0
  4. data/ext/cs__common/cs__common.c +19 -7
  5. data/ext/cs__common/cs__common.h +4 -2
  6. data/ext/cs__contrast_patch/cs__contrast_patch.c +32 -11
  7. data/ext/cs__contrast_patch/cs__contrast_patch.h +5 -4
  8. data/lib/contrast/agent/assess/contrast_event.rb +1 -2
  9. data/lib/contrast/agent/assess/contrast_object.rb +1 -4
  10. data/lib/contrast/agent/assess/finalizers/hash.rb +0 -1
  11. data/lib/contrast/agent/assess/policy/dynamic_source_factory.rb +2 -0
  12. data/lib/contrast/agent/assess/policy/patcher.rb +0 -1
  13. data/lib/contrast/agent/assess/policy/policy_scanner.rb +0 -2
  14. data/lib/contrast/agent/assess/policy/preshift.rb +29 -12
  15. data/lib/contrast/agent/assess/policy/propagation_method.rb +100 -57
  16. data/lib/contrast/agent/assess/policy/propagator/database_write.rb +2 -2
  17. data/lib/contrast/agent/assess/policy/propagator/match_data.rb +31 -11
  18. data/lib/contrast/agent/assess/policy/propagator/remove.rb +4 -9
  19. data/lib/contrast/agent/assess/policy/propagator/split.rb +3 -2
  20. data/lib/contrast/agent/assess/policy/propagator/substitution.rb +1 -0
  21. data/lib/contrast/agent/assess/policy/rewriter_patch.rb +0 -1
  22. data/lib/contrast/agent/assess/policy/source_method.rb +13 -17
  23. data/lib/contrast/agent/assess/policy/trigger/xpath.rb +0 -1
  24. data/lib/contrast/agent/assess/policy/trigger_method.rb +60 -85
  25. data/lib/contrast/agent/assess/policy/trigger_node.rb +52 -19
  26. data/lib/contrast/agent/assess/property/evented.rb +2 -1
  27. data/lib/contrast/agent/assess/property/tagged.rb +34 -25
  28. data/lib/contrast/agent/assess/rule/provider/hardcoded_value_rule.rb +0 -1
  29. data/lib/contrast/agent/deadzone/policy/policy.rb +6 -0
  30. data/lib/contrast/agent/disable_reaction.rb +1 -1
  31. data/lib/contrast/agent/exclusion_matcher.rb +0 -4
  32. data/lib/contrast/agent/inventory/database_config.rb +117 -0
  33. data/lib/contrast/agent/inventory/dependency_usage_analysis.rb +6 -5
  34. data/lib/contrast/agent/inventory/policy/datastores.rb +2 -2
  35. data/lib/contrast/agent/middleware.rb +1 -0
  36. data/lib/contrast/agent/patching/policy/after_load_patch.rb +3 -0
  37. data/lib/contrast/agent/patching/policy/after_load_patcher.rb +17 -12
  38. data/lib/contrast/agent/patching/policy/method_policy.rb +54 -9
  39. data/lib/contrast/agent/patching/policy/module_policy.rb +2 -4
  40. data/lib/contrast/agent/patching/policy/patch.rb +17 -6
  41. data/lib/contrast/agent/patching/policy/patch_status.rb +3 -7
  42. data/lib/contrast/agent/patching/policy/patcher.rb +9 -9
  43. data/lib/contrast/agent/protect/policy/applies_no_sqli_rule.rb +1 -1
  44. data/lib/contrast/agent/protect/rule/no_sqli.rb +7 -53
  45. data/lib/contrast/agent/protect/rule/sql_sample_builder.rb +137 -0
  46. data/lib/contrast/agent/protect/rule/sqli.rb +7 -70
  47. data/lib/contrast/agent/reaction_processor.rb +1 -1
  48. data/lib/contrast/agent/request.rb +9 -4
  49. data/lib/contrast/agent/request_context.rb +51 -33
  50. data/lib/contrast/agent/rule_set.rb +2 -4
  51. data/lib/contrast/agent/scope.rb +32 -20
  52. data/lib/contrast/agent/static_analysis.rb +1 -1
  53. data/lib/contrast/agent/tracepoint_hook.rb +16 -3
  54. data/lib/contrast/agent/version.rb +1 -1
  55. data/lib/contrast/agent.rb +0 -1
  56. data/lib/contrast/api/communication/messaging_queue.rb +12 -6
  57. data/lib/contrast/api/communication/service_lifecycle.rb +4 -1
  58. data/lib/contrast/api/communication/socket_client.rb +4 -4
  59. data/lib/contrast/api/decorators/agent_startup.rb +4 -4
  60. data/lib/contrast/api/decorators/application_startup.rb +6 -5
  61. data/lib/contrast/api/decorators/route_coverage.rb +24 -1
  62. data/lib/contrast/components/agent.rb +5 -2
  63. data/lib/contrast/components/assess.rb +13 -3
  64. data/lib/contrast/components/base.rb +2 -2
  65. data/lib/contrast/components/config.rb +1 -0
  66. data/lib/contrast/components/contrast_service.rb +4 -2
  67. data/lib/contrast/components/logger.rb +13 -8
  68. data/lib/contrast/components/scope.rb +9 -28
  69. data/lib/contrast/config/assess_configuration.rb +1 -0
  70. data/lib/contrast/config/base_configuration.rb +14 -6
  71. data/lib/contrast/configuration.rb +19 -15
  72. data/lib/contrast/extension/assess/array.rb +1 -11
  73. data/lib/contrast/extension/assess/eval_trigger.rb +0 -20
  74. data/lib/contrast/extension/assess/fiber.rb +0 -11
  75. data/lib/contrast/extension/assess/hash.rb +0 -10
  76. data/lib/contrast/extension/assess/kernel.rb +1 -10
  77. data/lib/contrast/extension/assess/marshal.rb +3 -11
  78. data/lib/contrast/extension/assess/regexp.rb +0 -11
  79. data/lib/contrast/extension/assess/string.rb +1 -26
  80. data/lib/contrast/extension/extension.rb +61 -0
  81. data/lib/contrast/framework/grape/support.rb +174 -0
  82. data/lib/contrast/framework/manager.rb +42 -6
  83. data/lib/contrast/framework/rack/support.rb +1 -1
  84. data/lib/contrast/framework/rails/patch/assess_configuration.rb +0 -1
  85. data/lib/contrast/framework/rails/patch/support.rb +6 -3
  86. data/lib/contrast/framework/rails/railtie.rb +1 -1
  87. data/lib/contrast/framework/rails/rewrite/active_record_named.rb +1 -0
  88. data/lib/contrast/framework/rails/support.rb +60 -13
  89. data/lib/contrast/framework/sinatra/support.rb +1 -1
  90. data/lib/contrast/logger/log.rb +89 -15
  91. data/lib/contrast/tasks/config.rb +0 -1
  92. data/lib/contrast/utils/class_util.rb +58 -44
  93. data/lib/contrast/utils/io_util.rb +43 -35
  94. data/lib/contrast/utils/lru_cache.rb +45 -0
  95. data/lib/contrast/utils/ruby_ast_rewriter.rb +16 -13
  96. data/lib/contrast/utils/tag_util.rb +2 -1
  97. data/lib/contrast.rb +1 -1
  98. data/resources/assess/policy.json +208 -7
  99. data/resources/deadzone/policy.json +91 -0
  100. data/ruby-agent.gemspec +10 -2
  101. data/service_executables/VERSION +1 -1
  102. data/service_executables/linux/contrast-service +0 -0
  103. data/service_executables/mac/contrast-service +0 -0
  104. metadata +74 -26
  105. data/ext/cs__protect_kernel/cs__protect_kernel.c +0 -47
  106. data/ext/cs__protect_kernel/cs__protect_kernel.h +0 -12
  107. data/ext/cs__protect_kernel/extconf.rb +0 -5
  108. data/lib/contrast/extension/protect/kernel.rb +0 -39
  109. data/lib/contrast/utils/inventory_util.rb +0 -113
@@ -11,10 +11,10 @@ module Contrast
11
11
  # results in new source nodes to track which columns in the database
12
12
  # have been tainted.
13
13
  class DatabaseWrite < Contrast::Agent::Assess::Policy::Propagator::Base
14
-
15
-
16
14
  class << self
17
15
  def propagate propagation_node, preshift, target
16
+ return unless Contrast::ASSESS.require_dynamic_sources?
17
+
18
18
  class_type = preshift.object.cs__class
19
19
  class_name = class_type.cs__name
20
20
  tainted_columns = {}
@@ -14,16 +14,15 @@ module Contrast
14
14
  def square_bracket_tagger propagation_node, preshift, ret, _block
15
15
  case ret
16
16
  when Array
17
- ret.each_with_index do |return_value, index|
17
+ idx = 0
18
+ while idx < ret.length
19
+ return_value = ret[idx]
20
+ index = idx
21
+ idx += 1
18
22
  next unless return_value
19
23
 
20
- target_matchdata_index = if preshift.args[0].is_a?(Range)
21
- arg_range = preshift.args[0]
22
- arg_range.to_a.empty? ? index + 1 : arg_range.to_a[index]
23
- else
24
- preshift.args[index]
25
- end
26
- square_bracket_single(target_matchdata_index, preshift, return_value, propagation_node)
24
+ square_bracket_single(target_matchdata_index(preshift, index), preshift, return_value,
25
+ propagation_node)
27
26
  end
28
27
  when String
29
28
  target_matchdata_index = preshift.args[0]
@@ -34,7 +33,11 @@ module Contrast
34
33
  end
35
34
 
36
35
  def captures_tagger propagation_node, preshift, ret, _block
37
- ret.each_with_index do |return_value, index|
36
+ idx = 0
37
+ while idx < ret.length
38
+ return_value = ret[idx]
39
+ index = idx
40
+ idx += 1
38
41
  next unless return_value
39
42
 
40
43
  targetted_index = index + 1
@@ -44,7 +47,11 @@ module Contrast
44
47
  end
45
48
 
46
49
  def to_a_tagger propagation_node, preshift, ret, _block
47
- ret.each_with_index do |return_value, index|
50
+ idx = 0
51
+ while idx < ret.length
52
+ return_value = ret[idx]
53
+ index = idx
54
+ idx += 1
48
55
  next unless return_value
49
56
 
50
57
  square_bracket_single(index, preshift, return_value, propagation_node)
@@ -53,7 +60,11 @@ module Contrast
53
60
  end
54
61
 
55
62
  def values_at_tagger propagation_node, preshift, ret, _block
56
- ret.each_with_index do |return_value, return_index|
63
+ idx = 0
64
+ while idx < ret.length
65
+ return_value = ret[idx]
66
+ return_index = idx
67
+ idx += 1
57
68
  next unless return_value
58
69
 
59
70
  original_group_arg_index = preshift.args[return_index]
@@ -64,6 +75,15 @@ module Contrast
64
75
 
65
76
  private
66
77
 
78
+ def target_matchdata_index preshift, index
79
+ if preshift.args[0].is_a?(Range)
80
+ arg_range = preshift.args[0]
81
+ arg_range.to_a.empty? ? index + 1 : arg_range.to_a[index]
82
+ else
83
+ preshift.args[index]
84
+ end
85
+ end
86
+
67
87
  def square_bracket_single argument_index, preshift, return_value, propagation_node
68
88
  original_start_index = preshift.object.begin(argument_index)
69
89
  original_end_index = preshift.object.end(argument_index)
@@ -26,7 +26,6 @@ module Contrast
26
26
  return unless (properties = Contrast::Agent::Assess::Tracker.properties!(target))
27
27
 
28
28
  source_string = source.is_a?(String) ? source : source.to_s
29
-
30
29
  # If the lengths are the same, we should just copy the tags because nothing was removed, but a new
31
30
  # instance could have been created. copy_from will handle the case where the source is the target.
32
31
  if source_string.length == target.length
@@ -34,10 +33,7 @@ module Contrast
34
33
  return
35
34
  end
36
35
 
37
- source_chars = source_string.chars
38
36
  source_idx = 0
39
-
40
- target_chars = target.chars
41
37
  target_idx = 0
42
38
 
43
39
  remove_ranges = []
@@ -45,10 +41,9 @@ module Contrast
45
41
 
46
42
  # loop over the target, the result of the delete every range of characters that it differs from the
47
43
  # source represents a section that was deleted. these sections need to have their tags updated
48
- target_len = target_chars.length
49
- while target_idx < target_len
50
- target_char = target_chars[target_idx]
51
- source_char = source_chars[source_idx]
44
+ while target_idx < target.length
45
+ target_char = target[target_idx]
46
+ source_char = source_string[source_idx]
52
47
  if target_char == source_char
53
48
  target_idx += 1
54
49
  if start
@@ -63,7 +58,7 @@ module Contrast
63
58
 
64
59
  # once we're done looping over the target, anything left over is extra from the source that was
65
60
  # deleted. tags applying to it need to be removed.
66
- remove_ranges << (source_idx...source_chars.length) if source_idx != source_chars.length
61
+ remove_ranges << (source_idx...source_string.length) if source_idx != source_string.length
67
62
 
68
63
  # handle deleting the removed ranges
69
64
  properties.delete_tags_at_ranges(remove_ranges)
@@ -17,7 +17,6 @@ module Contrast
17
17
  class Split < Contrast::Agent::Assess::Policy::Propagator::Base
18
18
  extend Contrast::Components::Scope::InstanceMethods
19
19
  extend Contrast::Components::Logger::InstanceMethods
20
- #cs__const_set('AGENT', Contrast::AGENT)
21
20
 
22
21
  SPLIT_TRACKER = Contrast::Utils::ThreadTracker.new
23
22
 
@@ -111,7 +110,9 @@ module Contrast
111
110
  # Load patch.
112
111
  def instrument_string_split
113
112
  @_instrument_string_split ||= begin
114
- require 'cs__assess_yield_track/cs__assess_yield_track' if ::Contrast::AGENT.patch_yield? && Funchook.available?
113
+ if ::Contrast::AGENT.patch_yield? && Funchook.available?
114
+ require 'cs__assess_yield_track/cs__assess_yield_track'
115
+ end
115
116
  true
116
117
  rescue StandardError => e
117
118
  logger.error('Error loading split rb_yield patch', e)
@@ -16,6 +16,7 @@ module Contrast
16
16
  # a 'get it right' state soon.
17
17
  class Substitution
18
18
  include Contrast::Components::Logger::InstanceMethods
19
+ extend Contrast::Components::Logger::InstanceMethods
19
20
 
20
21
  CAPTURE_GROUP_REGEXP = /\\[[:digit:]]/.cs__freeze
21
22
  CAPTURE_NAME_REGEXP = /\\k<[[:alpha:]]/.cs__freeze
@@ -22,7 +22,6 @@ module Contrast
22
22
  module RewriterPatch
23
23
  extend Contrast::Components::Logger::InstanceMethods
24
24
 
25
-
26
25
  class << self
27
26
  def rewrite_interpolations
28
27
  return unless agent_should_rewrite?
@@ -17,7 +17,6 @@ module Contrast
17
17
  module SourceMethod
18
18
  extend Contrast::Components::Logger::InstanceMethods
19
19
 
20
-
21
20
  PARAMETER_TYPE = 'PARAMETER'
22
21
  PARAMETER_KEY_TYPE = 'PARAMETER_KEY'
23
22
  HEADER_TYPE = 'HEADER'
@@ -26,8 +25,7 @@ module Contrast
26
25
  COOKIE_KEY_TYPE = 'COOKIE_KEY'
27
26
 
28
27
  class << self
29
- # This is called from within our woven proc. It will be called as if it were inline in the Rack
30
- # application.
28
+ # This is called from within our woven proc. It will be called as if it were inline in the Rack application.
31
29
  #
32
30
  # @param method_policy [Contrast::Agent::Patching::Policy::MethodPolicy] the policy that applies to the
33
31
  # method being called
@@ -37,29 +35,27 @@ module Contrast
37
35
  # @return [Object, nil] the tracked Return or nil if no changes were made
38
36
  def source_patchers method_policy, object, ret, args
39
37
  return unless analyze?(method_policy, object, ret, args)
38
+ return unless (source_node = method_policy.source_node)
39
+ return unless (target = determine_target(source_node, object, ret, args))
40
40
 
41
- source_node = method_policy.source_node
42
- target = determine_target(source_node, object, ret, args)
43
- restore_frozen_state = false
41
+ return_val = nil
44
42
  if target.cs__frozen? && !Contrast::Agent::Assess::Tracker.trackable?(target)
45
43
  return unless ::Contrast::ASSESS.track_frozen_sources?
46
44
  return unless source_node.targets[0] == Contrast::Utils::ObjectShare::RETURN_KEY
45
+ return unless (dup = safe_dup(ret))
46
+ return unless Contrast::Agent::Assess::Tracker.trackable?(dup)
47
47
 
48
- dup = safe_dup(ret)
49
- return unless dup
50
-
51
- restore_frozen_state = true
52
- ret = dup
53
- target = ret
54
- Contrast::Agent::Assess::Tracker.pre_freeze(ret)
55
- ret.cs__freeze
56
- # double check that we were able to finalize the replaced return
57
- return unless Contrast::Agent::Assess::Tracker.trackable?(target)
48
+ Contrast::Agent::Assess::Tracker.pre_freeze(dup)
49
+ return_val = dup.cs__freeze
50
+ target = dup
58
51
  end
52
+
59
53
  apply_source(Contrast::Agent::REQUEST_TRACKER.current, source_node, target, object, ret,
60
54
  source_node.type, nil, *args)
61
- restore_frozen_state ? ret : nil
55
+
56
+ return_val
62
57
  end
58
+ Contrast::Components::Logger.add_trace_log_timing_for(SourceMethod, :source_patchers)
63
59
 
64
60
  private
65
61
 
@@ -1,7 +1,6 @@
1
1
  # Copyright (c) 2021 Contrast Security, Inc. See https://www.contrastsecurity.com/enduser-terms-0317a for more details.
2
2
  # frozen_string_literal: true
3
3
 
4
-
5
4
  module Contrast
6
5
  module Agent
7
6
  module Assess
@@ -11,30 +11,28 @@ module Contrast
11
11
  module Agent
12
12
  module Assess
13
13
  module Policy
14
- # A trigger method is one which can perform a dangerous action, as
15
- # described by the Contrast::Agent::Assess::Policy::TriggerNode class.
16
- # Each such method will call to this module just after invocation in
17
- # order to determine if the call was done safely. In those cases where
18
- # it was not, a Finding report is issued to the Service
14
+ # A trigger method is one which can perform a dangerous action, as described by the
15
+ # Contrast::Agent::Assess::Policy::TriggerNode class. Each such method will call to this module just after
16
+ # invocation in order to determine if the call was done safely. In those cases where it was not, a Finding
17
+ # report is issued to the Service.
19
18
  module TriggerMethod
20
19
  extend Contrast::Components::Logger::InstanceMethods
21
20
 
22
- # The level of TeamServer compliance our traces meet when in the
23
- # abnormal condition of being dataflow rules without routes
21
+ # The level of TeamServer compliance our traces meet when in the abnormal condition of being dataflow rules
22
+ # without routes.
24
23
  MINIMUM_FINDING_VERSION = 3
25
- # The level of TeamServer compliance our traces meet
24
+ # The level of TeamServer compliance our traces meet.
26
25
  CURRENT_FINDING_VERSION = 4
27
26
 
28
27
  class << self
29
- # This is called from within our woven proc. It will be called as if it
30
- # were inline in the Rack application.
28
+ # This is called from within our woven proc. It will be called as if it were inline in the Rack
29
+ # application.
31
30
  #
32
- # @param trigger_node [Contrast::Agent::Assess::Policy::TriggerNode]
33
- # the node that applies to the method being called
31
+ # @param trigger_node [Contrast::Agent::Assess::Policy::TriggerNode] the node that applies to the method
32
+ # being called
34
33
  # @param object [Object] the Object on which the method was invoked
35
34
  # @param ret [Object] the Return of the invoked method
36
- # @param args [Array<Object>] the Arguments with which the method
37
- # was invoked
35
+ # @param args [Array<Object>] the Arguments with which the method was invoked
38
36
  def apply_trigger_rule trigger_node, object, ret, args
39
37
  return if trigger_node.nil?
40
38
 
@@ -52,19 +50,16 @@ module Contrast
52
50
  apply_trigger(trigger_node, source, object, ret, *args)
53
51
  end
54
52
 
55
- # This converts the source of the finding, and the events leading
56
- # up to it into a Finding
53
+ # This converts the source of the finding, and the events leading up to it into a Finding
57
54
  #
58
- # @param trigger_node [Contrast::Agent::Assess::Policy::TriggerNode]
59
- # the node to direct applying this trigger event
55
+ # @param trigger_node [Contrast::Agent::Assess::Policy::TriggerNode] the node to direct applying this
56
+ # trigger event
60
57
  # @param source [Object] the source of the Trigger Event
61
58
  # @param object [Object] the Object on which the method was invoked
62
59
  # @param ret [Object] the Return of the invoked method
63
- # @param args [Array<Object>] the Arguments with which the method
64
- # was invoked
65
- # @return [Contrast::Api::Dtm::Finding, nil] the
66
- # Contrast::Api::Dtm::Finding to send to TeamServer or nil if
67
- # conditions were not met
60
+ # @param args [Array<Object>] the Arguments with which the method was invoked
61
+ # @return [Contrast::Api::Dtm::Finding, nil] the Contrast::Api::Dtm::Finding to send to TeamServer or
62
+ # nil if conditions were not met
68
63
  def build_finding trigger_node, source, object, ret, *args
69
64
  return unless Contrast::Agent::Assess::Policy::TriggerValidation.valid?(trigger_node, object, ret, args)
70
65
 
@@ -85,13 +80,12 @@ module Contrast
85
80
  logger.error('Unable to build a finding', e, rule: trigger_node.rule_id, node_id: trigger_node.id)
86
81
  end
87
82
 
88
- # Given a finding, append it to an activity message and send it to
89
- # the Service for processing.
83
+ # Given a finding, append it to an activity message and send it to the Service for processing. If an
84
+ # activity message does not exist, b/c we're invoked outside of a request context, build an activity and
85
+ # immediately report it with the finding.
90
86
  #
91
- # @param finding [Contrast::Api::Dtm::Finding] the Finding to
92
- # report.
93
- # @param request [Contrast::Agent::Request] our wrapper around the
94
- # Rack::Request.
87
+ # @param finding [Contrast::Api::Dtm::Finding] the Finding to report.
88
+ # @param request [Contrast::Agent::Request] our wrapper around the Rack::Request.
95
89
  def report_finding finding, request = nil
96
90
  context = Contrast::Agent::REQUEST_TRACKER.current
97
91
  if context
@@ -107,9 +101,8 @@ module Contrast
107
101
  logger.debug('Attempted to report finding without request', finding: finding)
108
102
  end
109
103
 
110
- # If we're out of request context, then we need to report this
111
- # finding ourselves, so we'll send it in the one-off activity we
112
- # created.
104
+ # If we're out of request context, then we need to report this finding ourselves, so we'll send it in the
105
+ # one-off activity we created.
113
106
  Contrast::Agent.messaging_queue.send_event_eventually(activity)
114
107
  end
115
108
 
@@ -125,15 +118,12 @@ module Contrast
125
118
  env['action_controller.instance'].cs__class.included_modules.include?(ActionController::Live))
126
119
  end
127
120
 
128
- # Find the request for this finding. This assumes, for now, that if
129
- # there is an active request, then that is the request to report.
130
- # Otherwise, we'll use the first request found in the events of the
121
+ # Find the request for this finding. This assumes, for now, that if there is an active request, then that
122
+ # is the request to report. Otherwise, we'll use the first request found in the events of the
131
123
  # source_object.
132
124
  #
133
- # @param source [Object,nil] some Object used as the source of a
134
- # trigger event
135
- # @return [Contrast::Agent::Request,nil] the request from which the
136
- # dataflow on the request originated.
125
+ # @param source [Object,nil] some Object used as the source of a trigger event
126
+ # @return [Contrast::Agent::Request,nil] the request from which the dataflow on the request originated.
137
127
  def find_request source
138
128
  return Contrast::Agent::REQUEST_TRACKER.current.request if Contrast::Agent::REQUEST_TRACKER.current
139
129
  return unless (properties = Contrast::Agent::Assess::Tracker.properties(source))
@@ -150,16 +140,14 @@ module Contrast
150
140
  Contrast::Agent::FeatureState.instance
151
141
  end
152
142
 
153
- # This is our method that actually checks the taint on the object
154
- # our trigger_node targets.
143
+ # This is our method that actually checks the taint on the object our trigger_node targets.
155
144
  #
156
- # @param trigger_node [Contrast::Agent::Assess::Policy::TriggerNode]
157
- # the node to direct applying this trigger event
145
+ # @param trigger_node [Contrast::Agent::Assess::Policy::TriggerNode] the node to direct applying this
146
+ # trigger event
158
147
  # @param source [Object] the source of the Trigger Event
159
148
  # @param object [Object] the Object on which the method was invoked
160
149
  # @param ret [Object] the Return of the invoked method
161
- # @param args [Array<Object>] the Arguments with which the method
162
- # was invoked
150
+ # @param args [Array<Object>] the Arguments with which the method was invoked
163
151
  def apply_trigger trigger_node, source, object, ret, *args
164
152
  return unless trigger_node
165
153
  return if trigger_node.rule_disabled?
@@ -178,23 +166,17 @@ module Contrast
178
166
  logger.warn('Unable to apply trigger', e, node_id: trigger_node.id)
179
167
  end
180
168
 
181
- # Given the marker from the trigger_node (the pointer indicating
182
- # the entity from which the taint originated), return the entity on
183
- # which this trigger needs to operate.
169
+ # Given the marker from the trigger_node (the pointer indicating the entity from which the taint
170
+ # originated), return the entity on which this trigger needs to operate.
184
171
  #
185
- # In an effort to speed up this lookup, we've changed the marker
186
- # for parameters to be implicit - if it is not a return or an
187
- # object, it must be a parameter, which we can reference either by
188
- # index or by name.
172
+ # In an effort to speed up this lookup, we've changed the marker for parameters to be implicit - if it is
173
+ # not a return or an object, it must be a parameter, which we can reference by index.
189
174
  #
190
- # @param marker [String] the source marker that indicates which
191
- # Object
175
+ # @param marker [String] the source marker that indicates which Object
192
176
  # @param object [Object] the Object on which the method was invoked
193
177
  # @param ret [Object] the Return of the invoked method
194
- # @param args [Array<Object>] the Arguments with which the method
195
- # was invoked
196
- # @return [Object] the literal object that this Trigger Event
197
- # verifies
178
+ # @param args [Array<Object>] the Arguments with which the method was invoked
179
+ # @return [Object] the literal object that this Trigger Event verifies
198
180
  def determine_source marker, object, ret, args
199
181
  case marker
200
182
  when Contrast::Utils::ObjectShare::RETURN_KEY
@@ -217,16 +199,15 @@ module Contrast
217
199
  end
218
200
  end
219
201
 
220
- # This is our method that actually checks the taint on the object
221
- # our trigger_node targets for our Regexp based rules.
202
+ # This is our method that actually checks the taint on the object our trigger_node targets for our Regexp
203
+ # based rules.
222
204
  #
223
- # @param trigger_node [Contrast::Agent::Assess::Policy::TriggerNode]
224
- # the node to direct applying this trigger event
205
+ # @param trigger_node [Contrast::Agent::Assess::Policy::TriggerNode] the node to direct applying this
206
+ # trigger event
225
207
  # @param source [Object] the source of the Trigger Event
226
208
  # @param object [Object] the Object on which the method was invoked
227
209
  # @param ret [Object] the Return of the invoked method
228
- # @param args [Array<Object>] the Arguments with which the method
229
- # was invoked
210
+ # @param args [Array<Object>] the Arguments with which the method was invoked
230
211
  def apply_regexp_rule trigger_node, source, object, ret, *args
231
212
  return unless source.is_a?(String)
232
213
  return if trigger_node.good_value && source.match?(trigger_node.good_value)
@@ -235,16 +216,15 @@ module Contrast
235
216
  build_finding(trigger_node, source, object, ret, *args)
236
217
  end
237
218
 
238
- # This is our method that actually checks the taint on the object
239
- # our trigger_node targets for our Dataflow based rules.
219
+ # This is our method that actually checks the taint on the object our trigger_node targets for our Dataflow
220
+ # based rules.
240
221
  #
241
- # @param trigger_node [Contrast::Agent::Assess::Policy::TriggerNode]
242
- # the node to direct applying this trigger event
222
+ # @param trigger_node [Contrast::Agent::Assess::Policy::TriggerNode] the node to direct applying this
223
+ # trigger event
243
224
  # @param source [Object] the source of the Trigger Event
244
225
  # @param object [Object] the Object on which the method was invoked
245
226
  # @param ret [Object] the Return of the invoked method
246
- # @param args [Array<Object>] the Arguments with which the method
247
- # was invoked
227
+ # @param args [Array<Object>] the Arguments with which the method was invoked
248
228
  def apply_dataflow_rule trigger_node, source, object, ret, *args
249
229
  return unless source
250
230
 
@@ -266,9 +246,8 @@ module Contrast
266
246
  logger.debug('Trigger source is untrackable. Unable to inspect.',
267
247
  node_id: trigger_node.id,
268
248
  source_id: source.__id__,
269
- source_type: source.cs__class.to_s,
249
+ source_type: source.cs__class.cs__name,
270
250
  frozen: source.cs__frozen?)
271
- logger.trace(source.to_s[0..99])
272
251
  end
273
252
  end
274
253
 
@@ -287,8 +266,7 @@ module Contrast
287
266
 
288
267
  build_events finding, properties.event if properties.event
289
268
 
290
- # Google::Protobuf::Map doesn't support merge!, so we have to do this
291
- # long form
269
+ # Google::Protobuf::Map doesn't support merge!, so we have to do this long form
292
270
  source_props = properties.properties
293
271
  return unless source_props
294
272
 
@@ -304,8 +282,8 @@ module Contrast
304
282
  event.parent_events&.each do |parent_event|
305
283
  build_events(finding, parent_event)
306
284
  end
307
- # events could technically be nil, but we would have failed
308
- # the rule check before getting here. not worth the nil check
285
+ # events could technically be nil, but we would have failed the rule check before getting here. not
286
+ # worth the nil check
309
287
  finding.events << event.to_dtm_event
310
288
  end
311
289
 
@@ -324,21 +302,18 @@ module Contrast
324
302
  finding.preflight = Contrast::Utils::PreflightUtil.create_preflight(finding)
325
303
  end
326
304
 
327
- # Because our APIs are not versioned, TeamServer relies on a field,
328
- # version, to tell it what, if any, validation it can preform on
329
- # the findings we send it. Examine the finding and determine the
330
- # level to which it conforms.
305
+ # Because our APIs are not versioned, TeamServer relies on a field, version, to tell it what, if any,
306
+ # validation it can preform on the findings we send it. Examine the finding and determine the level to
307
+ # which it conforms.
331
308
  #
332
309
  # @param finding [Contrast::Api::Dtm::Finding]
333
310
  # @return [int] the version of compliance
334
311
  def determine_compliance_version finding
335
312
  return MINIMUM_FINDING_VERSION unless finding
336
- # as routes are the only variable between findings, in the case
337
- # where we couldn't determine one, any finding with a route is at
338
- # maximum compliance
313
+ # as routes are the only variable between findings, in the case where we couldn't determine one, any
314
+ # finding with a route is at maximum compliance
339
315
  return CURRENT_FINDING_VERSION if finding.routes.any?
340
- # any finding without events is not of a dataflow type and
341
- # therefore at maximum compliance
316
+ # any finding without events is not of a dataflow type and therefore at maximum compliance
342
317
  return CURRENT_FINDING_VERSION unless finding.events.any?
343
318
 
344
319
  MINIMUM_FINDING_VERSION
@@ -14,7 +14,7 @@ module Contrast
14
14
  # specifically for those methods which result in the trigger of a
15
15
  # vulnerability (indicate points in the application where uncontrolled
16
16
  # user input can do damage).
17
- class TriggerNode < PolicyNode
17
+ class TriggerNode < PolicyNode # rubocop:disable Metrics/ClassLength
18
18
  JSON_BAD_VALUE = 'bad_value'
19
19
  JSON_GOOD_VALUE = 'good_value'
20
20
  JSON_DISALLOWED_TAGS = 'disallowed_tags'
@@ -104,8 +104,7 @@ 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 = ranges_with_all_tags(Contrast::Utils::StringUtils.ret_length(source), properties,
108
- required_tags)
107
+ vulnerable_ranges = ranges_with_all_tags(properties, required_tags)
109
108
  # if there aren't any vulnerable ranges, nope out
110
109
  return false if vulnerable_ranges.empty?
111
110
 
@@ -170,49 +169,56 @@ module Contrast
170
169
 
171
170
  tags.each do |tag|
172
171
  raise(ArgumentError, "Rule #{ rule_id } had an invalid tag. #{ tag } is not a known value.") unless
173
- Contrast::Api::Decorators::TraceTaintRangeTags::VALID_TAGS.include?(tag) ||
174
- Contrast::Api::Decorators::TraceTaintRangeTags::VALID_SOURCE_TAGS.include?(tag)
172
+ Contrast::Api::Decorators::TraceTaintRangeTags::VALID_TAGS.include?(tag) ||
173
+ Contrast::Api::Decorators::TraceTaintRangeTags::VALID_SOURCE_TAGS.include?(tag)
175
174
  end
176
175
  end
177
176
 
178
177
  # Find the ranges that satisfy all of the given tags.
179
178
  #
180
- # @param length [Integer] the length of the object which may have the
181
- # given tags -- used as the maximum index to search for all of the
182
- # tags.
183
179
  # @param properties [Contrast::Agent::Assess::Properties] the
184
180
  # properties to check for the tags
185
181
  # @param required_tags [Set<String>] the list of tags on which to match
186
182
  # @return [Array<Contrast::Agent::Assess::Tag>] the ranges satisfied
187
183
  # by the given conditions
188
- def ranges_with_all_tags length, properties, required_tags
184
+ def ranges_with_all_tags properties, required_tags
189
185
  return Contrast::Utils::ObjectShare::EMPTY_ARRAY unless matches_tags?(properties, required_tags)
190
186
 
191
- ranges = []
192
187
  chunking = false
188
+ ranges = []
189
+ # find the start and end range of required tags:
190
+ search_ranges = find_required_ranges properties, required_tags
191
+ start_range = search_ranges.first
192
+ end_range = search_ranges.last + 1
193
+
193
194
  # find all the indicies on the source that have all the given tags
194
- (0..length).each do |idx|
195
- tags_at = properties.tags_at(idx).to_a
195
+ while start_range < end_range
196
+
197
+ tags_at = properties.tags_at(start_range).to_a
196
198
  # only those that have all the required tags in the tags_at
197
199
  # satisfy the requirement
198
200
  satisfied = tags_at.any? && required_tags.all? { |tag| tags_at.any? { |found| found.label == tag } }
199
201
  # if this range matches all the required tags and we're already
200
202
  # chunking, meaning the previous range matched, do nothing
201
- next if satisfied && chunking
203
+ if satisfied && chunking
204
+ start_range += 1
205
+ next
206
+ end
202
207
 
203
208
  # if we are satisfied and we were not chunking, this represents
204
209
  # the start of the next range, so create a new entry.
205
210
  if satisfied
206
- ranges << Contrast::Agent::Assess::Tag.new('required', 0, idx)
211
+ ranges << Contrast::Agent::Assess::Tag.new('required', 0, start_range)
207
212
  chunking = true
208
- # if we are chunking and not satisfied, this represents the end
209
- # of the range, meaning the last index is what satisfied the
210
- # range. Because the range is exclusive end, we can just use this
211
- # index.
213
+ # if we are chunking and not satisfied, this represents the end
214
+ # of the range, meaning the last index is what satisfied the
215
+ # range. Because the range is exclusive end, we can just use this
216
+ # index.
212
217
  elsif chunking
213
- ranges[-1]&.update_end(idx)
218
+ ranges[-1]&.update_end(start_range)
214
219
  chunking = false
215
220
  end
221
+ start_range += 1
216
222
  end
217
223
  ranges
218
224
  end
@@ -265,6 +271,33 @@ module Contrast
265
271
 
266
272
  true
267
273
  end
274
+
275
+ # Range finder helper for #ranges_with_all_tags
276
+ #
277
+ # @param properties [Contrast::Agent::Assess::Properties] the properties to check for the tags
278
+ # @param required_tags [Set<String>] the list of tags on which to match
279
+ # @return [Array] of required tags ranges to search
280
+ def find_required_ranges properties, required_tags
281
+ start_range = 0
282
+ end_range = 0
283
+ required_tags_arr = required_tags.to_a
284
+ idx = 0
285
+
286
+ while idx < required_tags_arr.length
287
+ # find the start and end range of required tags:
288
+ start_temp = properties.fetch_tag(required_tags_arr[idx])[0].start_idx
289
+ end_temp = properties.fetch_tag(required_tags_arr[idx])[0].end_idx
290
+ # first iteration only
291
+ start_range = start_temp if idx.zero?
292
+ end_range = end_temp if idx.zero?
293
+
294
+ # find the tag with smallest ranges
295
+ start_range = start_temp if start_range < start_temp
296
+ end_range = end_temp if end_range > end_temp
297
+ idx += 1
298
+ end
299
+ [start_range, end_range]
300
+ end
268
301
  end
269
302
  end
270
303
  end
@@ -50,7 +50,8 @@ module Contrast
50
50
  return unless (current_request = Contrast::Agent::REQUEST_TRACKER.current)
51
51
 
52
52
  if current_request.observed_route.sources.any? do |source|
53
- source.type == event.forced_source_type && source.name == event.forced_source_name
53
+ source.type == event.forced_source_type &&
54
+ source.name == event.forced_source_name # rubocop:disable Security/Module/Name
54
55
  end
55
56
 
56
57
  return