contrast-agent 4.8.0 → 4.11.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (147) hide show
  1. checksums.yaml +4 -4
  2. data/.gitignore +6 -1
  3. data/.rspec +0 -1
  4. data/.rspec_parallel +6 -0
  5. data/.simplecov +1 -0
  6. data/ext/cs__contrast_patch/cs__contrast_patch.c +0 -1
  7. data/ext/cs__contrast_patch/cs__contrast_patch.h +0 -2
  8. data/lib/contrast/agent/assess/contrast_event.rb +1 -5
  9. data/lib/contrast/agent/assess/contrast_object.rb +0 -3
  10. data/lib/contrast/agent/assess/finalizers/hash.rb +2 -5
  11. data/lib/contrast/agent/assess/policy/patcher.rb +5 -4
  12. data/lib/contrast/agent/assess/policy/policy.rb +1 -1
  13. data/lib/contrast/agent/assess/policy/policy_scanner.rb +2 -6
  14. data/lib/contrast/agent/assess/policy/preshift.rb +16 -12
  15. data/lib/contrast/agent/assess/policy/propagation_method.rb +102 -59
  16. data/lib/contrast/agent/assess/policy/propagator/database_write.rb +2 -7
  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 +10 -6
  20. data/lib/contrast/agent/assess/policy/propagator/substitution.rb +3 -3
  21. data/lib/contrast/agent/assess/policy/rewriter_patch.rb +6 -7
  22. data/lib/contrast/agent/assess/policy/source_method.rb +18 -22
  23. data/lib/contrast/agent/assess/policy/trigger/xpath.rb +0 -4
  24. data/lib/contrast/agent/assess/policy/trigger_method.rb +62 -88
  25. data/lib/contrast/agent/assess/policy/trigger_node.rb +1 -1
  26. data/lib/contrast/agent/assess/property/evented.rb +2 -1
  27. data/lib/contrast/agent/assess/rule/provider/hardcoded_value_rule.rb +3 -4
  28. data/lib/contrast/agent/at_exit_hook.rb +3 -3
  29. data/lib/contrast/agent/class_reopener.rb +6 -5
  30. data/lib/contrast/agent/disable_reaction.rb +4 -5
  31. data/lib/contrast/agent/exclusion_matcher.rb +2 -7
  32. data/lib/contrast/agent/inventory/database_config.rb +117 -0
  33. data/lib/contrast/agent/inventory/dependency_analysis.rb +2 -6
  34. data/lib/contrast/agent/inventory/dependency_usage_analysis.rb +9 -10
  35. data/lib/contrast/agent/inventory/policy/datastores.rb +5 -6
  36. data/lib/contrast/agent/inventory/policy/policy.rb +1 -1
  37. data/lib/contrast/agent/middleware.rb +15 -13
  38. data/lib/contrast/agent/patching/policy/after_load_patch.rb +6 -3
  39. data/lib/contrast/agent/patching/policy/after_load_patcher.rb +21 -16
  40. data/lib/contrast/agent/patching/policy/module_policy.rb +2 -4
  41. data/lib/contrast/agent/patching/policy/patch.rb +13 -8
  42. data/lib/contrast/agent/patching/policy/patch_status.rb +3 -7
  43. data/lib/contrast/agent/patching/policy/patcher.rb +14 -14
  44. data/lib/contrast/agent/patching/policy/policy.rb +2 -4
  45. data/lib/contrast/agent/patching/policy/policy_node.rb +2 -3
  46. data/lib/contrast/agent/protect/policy/applies_no_sqli_rule.rb +1 -1
  47. data/lib/contrast/agent/protect/policy/policy.rb +1 -1
  48. data/lib/contrast/agent/protect/policy/rule_applicator.rb +3 -5
  49. data/lib/contrast/agent/protect/rule/base.rb +10 -10
  50. data/lib/contrast/agent/protect/rule/cmd_injection.rb +4 -5
  51. data/lib/contrast/agent/protect/rule/no_sqli.rb +7 -53
  52. data/lib/contrast/agent/protect/rule/path_traversal.rb +1 -5
  53. data/lib/contrast/agent/protect/rule/sql_sample_builder.rb +137 -0
  54. data/lib/contrast/agent/protect/rule/sqli.rb +7 -70
  55. data/lib/contrast/agent/reaction_processor.rb +3 -4
  56. data/lib/contrast/agent/request.rb +13 -7
  57. data/lib/contrast/agent/request_context.rb +36 -34
  58. data/lib/contrast/agent/request_handler.rb +5 -3
  59. data/lib/contrast/agent/response.rb +2 -3
  60. data/lib/contrast/agent/rewriter.rb +4 -3
  61. data/lib/contrast/agent/rule_set.rb +5 -4
  62. data/lib/contrast/agent/scope.rb +32 -20
  63. data/lib/contrast/agent/service_heartbeat.rb +2 -3
  64. data/lib/contrast/agent/static_analysis.rb +7 -6
  65. data/lib/contrast/agent/thread.rb +2 -4
  66. data/lib/contrast/agent/thread_watcher.rb +3 -4
  67. data/lib/contrast/agent/tracepoint_hook.rb +20 -7
  68. data/lib/contrast/agent/version.rb +1 -1
  69. data/lib/contrast/api/communication/messaging_queue.rb +16 -11
  70. data/lib/contrast/api/communication/response_processor.rb +11 -11
  71. data/lib/contrast/api/communication/service_lifecycle.rb +9 -5
  72. data/lib/contrast/api/communication/socket_client.rb +18 -14
  73. data/lib/contrast/api/communication/speedracer.rb +5 -6
  74. data/lib/contrast/api/decorators/address.rb +2 -3
  75. data/lib/contrast/api/decorators/agent_startup.rb +7 -9
  76. data/lib/contrast/api/decorators/application_startup.rb +9 -10
  77. data/lib/contrast/api/decorators/application_update.rb +0 -4
  78. data/lib/contrast/api/decorators/http_request.rb +3 -7
  79. data/lib/contrast/api/decorators/instrumentation_mode.rb +3 -5
  80. data/lib/contrast/api/decorators/message.rb +7 -7
  81. data/lib/contrast/api/decorators/route_coverage.rb +24 -1
  82. data/lib/contrast/api/decorators/trace_event_object.rb +2 -3
  83. data/lib/contrast/components/agent.rb +13 -15
  84. data/lib/contrast/components/app_context.rb +7 -11
  85. data/lib/contrast/components/assess.rb +19 -16
  86. data/lib/contrast/components/base.rb +40 -0
  87. data/lib/contrast/components/config.rb +1 -2
  88. data/lib/contrast/components/contrast_service.rb +8 -11
  89. data/lib/contrast/components/heap_dump.rb +5 -4
  90. data/lib/contrast/components/inventory.rb +2 -7
  91. data/lib/contrast/components/logger.rb +14 -10
  92. data/lib/contrast/components/protect.rb +10 -13
  93. data/lib/contrast/components/sampling.rb +5 -5
  94. data/lib/contrast/components/scope.rb +9 -32
  95. data/lib/contrast/components/settings.rb +1 -5
  96. data/lib/contrast/config/base_configuration.rb +14 -6
  97. data/lib/contrast/configuration.rb +22 -19
  98. data/lib/contrast/extension/assess/array.rb +3 -15
  99. data/lib/contrast/extension/assess/eval_trigger.rb +2 -23
  100. data/lib/contrast/extension/assess/fiber.rb +6 -16
  101. data/lib/contrast/extension/assess/hash.rb +3 -13
  102. data/lib/contrast/extension/assess/kernel.rb +3 -14
  103. data/lib/contrast/extension/assess/marshal.rb +6 -14
  104. data/lib/contrast/extension/assess/regexp.rb +5 -15
  105. data/lib/contrast/extension/assess/string.rb +6 -31
  106. data/lib/contrast/extension/extension.rb +61 -0
  107. data/lib/contrast/extension/kernel.rb +2 -4
  108. data/lib/contrast/extension/protect/kernel.rb +0 -15
  109. data/lib/contrast/framework/grape/support.rb +174 -0
  110. data/lib/contrast/framework/manager.rb +44 -9
  111. data/lib/contrast/framework/rack/patch/session_cookie.rb +6 -6
  112. data/lib/contrast/framework/rack/support.rb +1 -1
  113. data/lib/contrast/framework/rails/patch/assess_configuration.rb +5 -8
  114. data/lib/contrast/framework/rails/patch/support.rb +43 -36
  115. data/lib/contrast/framework/rails/railtie.rb +8 -6
  116. data/lib/contrast/framework/rails/rewrite/active_record_named.rb +4 -4
  117. data/lib/contrast/framework/rails/support.rb +60 -13
  118. data/lib/contrast/framework/sinatra/support.rb +1 -1
  119. data/lib/contrast/funchook/funchook.rb +4 -3
  120. data/lib/contrast/logger/application.rb +1 -6
  121. data/lib/contrast/logger/log.rb +103 -13
  122. data/lib/contrast/logger/request.rb +0 -4
  123. data/lib/contrast/tasks/config.rb +0 -1
  124. data/lib/contrast/tasks/service.rb +1 -6
  125. data/lib/contrast/utils/assess/sampling_util.rb +2 -3
  126. data/lib/contrast/utils/assess/tracking_util.rb +2 -4
  127. data/lib/contrast/utils/class_util.rb +26 -19
  128. data/lib/contrast/utils/heap_dump_util.rb +5 -3
  129. data/lib/contrast/utils/invalid_configuration_util.rb +4 -3
  130. data/lib/contrast/utils/io_util.rb +46 -40
  131. data/lib/contrast/utils/job_servers_running.rb +4 -3
  132. data/lib/contrast/utils/lru_cache.rb +43 -0
  133. data/lib/contrast/utils/os.rb +2 -3
  134. data/lib/contrast/utils/ruby_ast_rewriter.rb +16 -13
  135. data/lib/contrast/utils/string_utils.rb +2 -3
  136. data/lib/contrast/utils/tag_util.rb +26 -19
  137. data/lib/contrast.rb +24 -14
  138. data/resources/assess/policy.json +197 -2
  139. data/resources/deadzone/policy.json +10 -0
  140. data/ruby-agent.gemspec +13 -3
  141. data/service_executables/VERSION +1 -1
  142. data/service_executables/linux/contrast-service +0 -0
  143. data/service_executables/mac/contrast-service +0 -0
  144. metadata +91 -25
  145. data/lib/contrast/components/interface.rb +0 -196
  146. data/lib/contrast/delegators/input_analysis.rb +0 -12
  147. data/lib/contrast/utils/inventory_util.rb +0 -114
@@ -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)
@@ -2,7 +2,9 @@
2
2
  # frozen_string_literal: true
3
3
 
4
4
  require 'contrast/agent/assess/policy/preshift'
5
- require 'contrast/components/interface'
5
+ require 'contrast/components/agent'
6
+ require 'contrast/components/logger'
7
+ require 'contrast/components/scope'
6
8
  require 'contrast/utils/thread_tracker'
7
9
 
8
10
  module Contrast
@@ -13,9 +15,8 @@ module Contrast
13
15
  # This class is specifically for String#split & String#grapheme_clusters propagation
14
16
  # it propagates tag ranges from a string to elements within an untracked array
15
17
  class Split < Contrast::Agent::Assess::Policy::Propagator::Base
16
- include Contrast::Components::Interface
17
-
18
- access_component :agent, :logging, :scope
18
+ extend Contrast::Components::Scope::InstanceMethods
19
+ extend Contrast::Components::Logger::InstanceMethods
19
20
 
20
21
  SPLIT_TRACKER = Contrast::Utils::ThreadTracker.new
21
22
 
@@ -29,8 +30,9 @@ module Contrast
29
30
  # patched method.
30
31
  # @param target [Array, String] the target to which to propagate.
31
32
  # @return [nil] so as not to risk changing the result of the propagation.
32
-
33
33
  def propagate propagation_node, preshift, target
34
+ return unless target.is_a?(Array) # apply_post_patch is called, but split with block returns a string.
35
+
34
36
  logger.trace('Propagation detected', node_id: propagation_node.id, target_id: target.__id__)
35
37
 
36
38
  source = find_source(propagation_node.sources[0], preshift)
@@ -108,7 +110,9 @@ module Contrast
108
110
  # Load patch.
109
111
  def instrument_string_split
110
112
  @_instrument_string_split ||= begin
111
- require 'cs__assess_yield_track/cs__assess_yield_track' if 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
112
116
  true
113
117
  rescue StandardError => e
114
118
  logger.error('Error loading split rb_yield patch', e)
@@ -1,7 +1,7 @@
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
- require 'contrast/components/interface'
4
+ require 'contrast/components/logger'
5
5
  require 'contrast/utils/duck_utils'
6
6
 
7
7
  module Contrast
@@ -15,8 +15,8 @@ module Contrast
15
15
  # in a 'get it work' state. hopefully, we'll be in
16
16
  # a 'get it right' state soon.
17
17
  class Substitution
18
- include Contrast::Components::Interface
19
- access_component :logging
18
+ include Contrast::Components::Logger::InstanceMethods
19
+ extend Contrast::Components::Logger::InstanceMethods
20
20
 
21
21
  CAPTURE_GROUP_REGEXP = /\\[[:digit:]]/.cs__freeze
22
22
  CAPTURE_NAME_REGEXP = /\\k<[[:alpha:]]/.cs__freeze
@@ -6,7 +6,7 @@ return unless RUBY_VERSION < '2.6.0' # TODO: RUBY-714 remove guard w/ EOL of 2.5
6
6
  require 'contrast/agent/patching/policy/patch_status'
7
7
  require 'contrast/agent/module_data'
8
8
  require 'contrast/agent/rewriter'
9
- require 'contrast/components/interface'
9
+ require 'contrast/components/logger'
10
10
  require 'contrast/utils/object_share'
11
11
 
12
12
  module Contrast
@@ -20,8 +20,7 @@ module Contrast
20
20
  # @deprecated Changes to this class are discouraged as this approach is
21
21
  # being phased out with support for those language versions.
22
22
  module RewriterPatch
23
- include Contrast::Components::Interface
24
- access_component :agent, :analysis, :logging
23
+ extend Contrast::Components::Logger::InstanceMethods
25
24
 
26
25
  class << self
27
26
  def rewrite_interpolations
@@ -72,16 +71,16 @@ module Contrast
72
71
  end
73
72
 
74
73
  def agent_should_rewrite?
75
- return false unless ASSESS.enabled?
76
- return false unless AGENT.rewrite_interpolation?
77
- return false unless AGENT.interpolation_enabled?
74
+ return false unless ::Contrast::ASSESS.enabled?
75
+ return false unless ::Contrast::AGENT.rewrite_interpolation?
76
+ return false unless ::Contrast::AGENT.interpolation_enabled?
78
77
 
79
78
  true
80
79
  end
81
80
 
82
81
  def should_rewrite? mod
83
82
  return false unless agent_should_rewrite?
84
- return false if AGENT.skip_instrumentation? mod.cs__name
83
+ return false if ::Contrast::AGENT.skip_instrumentation? mod.cs__name
85
84
  return false if mod.cs__frozen?
86
85
  return false if mod.singleton_class?
87
86
  return false if mid_defining?(mod)
@@ -3,7 +3,7 @@
3
3
 
4
4
  require 'set'
5
5
  require 'contrast/agent/assess/policy/source_validation/source_validation'
6
- require 'contrast/components/interface'
6
+ require 'contrast/components/logger'
7
7
  require 'contrast/utils/object_share'
8
8
  require 'contrast/utils/sha256_builder'
9
9
 
@@ -15,8 +15,7 @@ module Contrast
15
15
  # actions we should take in order to mark data as User Input and treat it as untrusted, starting the dataflows
16
16
  # used in Assess vulnerability detection.
17
17
  module SourceMethod
18
- include Contrast::Components::Interface
19
- access_component :analysis, :logging
18
+ extend Contrast::Components::Logger::InstanceMethods
20
19
 
21
20
  PARAMETER_TYPE = 'PARAMETER'
22
21
  PARAMETER_KEY_TYPE = 'PARAMETER_KEY'
@@ -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
- return unless ASSESS.track_frozen_sources?
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
 
@@ -131,7 +127,7 @@ module Contrast
131
127
  # @param hash [Hash] the hash to which the key belongs.
132
128
  # @return [Boolean] whether replace the key in the hash or not.
133
129
  def replace_hash_key? key, hash
134
- ASSESS.track_frozen_sources? &&
130
+ ::Contrast::ASSESS.track_frozen_sources? &&
135
131
  !hash.cs__frozen? &&
136
132
  key.is_a?(String) &&
137
133
  !Contrast::Agent::Assess::Tracker.trackable?(key)
@@ -215,7 +211,7 @@ module Contrast
215
211
  # @return [boolean] if the invocation of this method should be analyzed
216
212
  def analyze? method_policy, object, ret, args
217
213
  return false unless method_policy&.source_node
218
- return false unless ASSESS.enabled?
214
+ return false unless ::Contrast::ASSESS.enabled?
219
215
  return false unless Contrast::Agent::REQUEST_TRACKER.current&.analyze_request?
220
216
 
221
217
  !safe_invocation?(method_policy.source_node, object, ret, args)
@@ -1,8 +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
- require 'contrast/components/interface'
5
-
6
4
  module Contrast
7
5
  module Agent
8
6
  module Assess
@@ -16,8 +14,6 @@ module Contrast
16
14
  # these objects to see if we were tracking on any of them and report
17
15
  # a finding if so.
18
16
  class Xpath
19
- include Contrast::Components::Interface
20
-
21
17
  class << self
22
18
  def xpath_expression_trigger trigger_node, _source, object, ret, *args
23
19
  return ret unless args
@@ -3,7 +3,7 @@
3
3
 
4
4
  require 'contrast/agent/assess/events/event_factory'
5
5
  require 'contrast/agent/assess/policy/trigger_validation/trigger_validation'
6
- require 'contrast/components/interface'
6
+ require 'contrast/components/logger'
7
7
  require 'contrast/utils/object_share'
8
8
  require 'contrast/utils/sha256_builder'
9
9
 
@@ -11,31 +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
- include Contrast::Components::Interface
21
- access_component :analysis, :logging
19
+ extend Contrast::Components::Logger::InstanceMethods
22
20
 
23
- # The level of TeamServer compliance our traces meet when in the
24
- # 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.
25
23
  MINIMUM_FINDING_VERSION = 3
26
- # The level of TeamServer compliance our traces meet
24
+ # The level of TeamServer compliance our traces meet.
27
25
  CURRENT_FINDING_VERSION = 4
28
26
 
29
27
  class << self
30
- # This is called from within our woven proc. It will be called as if it
31
- # 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.
32
30
  #
33
- # @param trigger_node [Contrast::Agent::Assess::Policy::TriggerNode]
34
- # 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
35
33
  # @param object [Object] the Object on which the method was invoked
36
34
  # @param ret [Object] the Return of the invoked method
37
- # @param args [Array<Object>] the Arguments with which the method
38
- # was invoked
35
+ # @param args [Array<Object>] the Arguments with which the method was invoked
39
36
  def apply_trigger_rule trigger_node, object, ret, args
40
37
  return if trigger_node.nil?
41
38
 
@@ -53,19 +50,16 @@ module Contrast
53
50
  apply_trigger(trigger_node, source, object, ret, *args)
54
51
  end
55
52
 
56
- # This converts the source of the finding, and the events leading
57
- # up to it into a Finding
53
+ # This converts the source of the finding, and the events leading up to it into a Finding
58
54
  #
59
- # @param trigger_node [Contrast::Agent::Assess::Policy::TriggerNode]
60
- # 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
61
57
  # @param source [Object] the source of the Trigger Event
62
58
  # @param object [Object] the Object on which the method was invoked
63
59
  # @param ret [Object] the Return of the invoked method
64
- # @param args [Array<Object>] the Arguments with which the method
65
- # was invoked
66
- # @return [Contrast::Api::Dtm::Finding, nil] the
67
- # Contrast::Api::Dtm::Finding to send to TeamServer or nil if
68
- # 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
69
63
  def build_finding trigger_node, source, object, ret, *args
70
64
  return unless Contrast::Agent::Assess::Policy::TriggerValidation.valid?(trigger_node, object, ret, args)
71
65
 
@@ -86,13 +80,12 @@ module Contrast
86
80
  logger.error('Unable to build a finding', e, rule: trigger_node.rule_id, node_id: trigger_node.id)
87
81
  end
88
82
 
89
- # Given a finding, append it to an activity message and send it to
90
- # 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.
91
86
  #
92
- # @param finding [Contrast::Api::Dtm::Finding] the Finding to
93
- # report.
94
- # @param request [Contrast::Agent::Request] our wrapper around the
95
- # 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.
96
89
  def report_finding finding, request = nil
97
90
  context = Contrast::Agent::REQUEST_TRACKER.current
98
91
  if context
@@ -108,9 +101,8 @@ module Contrast
108
101
  logger.debug('Attempted to report finding without request', finding: finding)
109
102
  end
110
103
 
111
- # If we're out of request context, then we need to report this
112
- # finding ourselves, so we'll send it in the one-off activity we
113
- # 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.
114
106
  Contrast::Agent.messaging_queue.send_event_eventually(activity)
115
107
  end
116
108
 
@@ -126,15 +118,12 @@ module Contrast
126
118
  env['action_controller.instance'].cs__class.included_modules.include?(ActionController::Live))
127
119
  end
128
120
 
129
- # Find the request for this finding. This assumes, for now, that if
130
- # there is an active request, then that is the request to report.
131
- # 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
132
123
  # source_object.
133
124
  #
134
- # @param source [Object,nil] some Object used as the source of a
135
- # trigger event
136
- # @return [Contrast::Agent::Request,nil] the request from which the
137
- # 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.
138
127
  def find_request source
139
128
  return Contrast::Agent::REQUEST_TRACKER.current.request if Contrast::Agent::REQUEST_TRACKER.current
140
129
  return unless (properties = Contrast::Agent::Assess::Tracker.properties(source))
@@ -151,16 +140,14 @@ module Contrast
151
140
  Contrast::Agent::FeatureState.instance
152
141
  end
153
142
 
154
- # This is our method that actually checks the taint on the object
155
- # our trigger_node targets.
143
+ # This is our method that actually checks the taint on the object our trigger_node targets.
156
144
  #
157
- # @param trigger_node [Contrast::Agent::Assess::Policy::TriggerNode]
158
- # 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
159
147
  # @param source [Object] the source of the Trigger Event
160
148
  # @param object [Object] the Object on which the method was invoked
161
149
  # @param ret [Object] the Return of the invoked method
162
- # @param args [Array<Object>] the Arguments with which the method
163
- # was invoked
150
+ # @param args [Array<Object>] the Arguments with which the method was invoked
164
151
  def apply_trigger trigger_node, source, object, ret, *args
165
152
  return unless trigger_node
166
153
  return if trigger_node.rule_disabled?
@@ -179,23 +166,17 @@ module Contrast
179
166
  logger.warn('Unable to apply trigger', e, node_id: trigger_node.id)
180
167
  end
181
168
 
182
- # Given the marker from the trigger_node (the pointer indicating
183
- # the entity from which the taint originated), return the entity on
184
- # 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.
185
171
  #
186
- # In an effort to speed up this lookup, we've changed the marker
187
- # for parameters to be implicit - if it is not a return or an
188
- # object, it must be a parameter, which we can reference either by
189
- # 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.
190
174
  #
191
- # @param marker [String] the source marker that indicates which
192
- # Object
175
+ # @param marker [String] the source marker that indicates which Object
193
176
  # @param object [Object] the Object on which the method was invoked
194
177
  # @param ret [Object] the Return of the invoked method
195
- # @param args [Array<Object>] the Arguments with which the method
196
- # was invoked
197
- # @return [Object] the literal object that this Trigger Event
198
- # 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
199
180
  def determine_source marker, object, ret, args
200
181
  case marker
201
182
  when Contrast::Utils::ObjectShare::RETURN_KEY
@@ -218,16 +199,15 @@ module Contrast
218
199
  end
219
200
  end
220
201
 
221
- # This is our method that actually checks the taint on the object
222
- # 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.
223
204
  #
224
- # @param trigger_node [Contrast::Agent::Assess::Policy::TriggerNode]
225
- # 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
226
207
  # @param source [Object] the source of the Trigger Event
227
208
  # @param object [Object] the Object on which the method was invoked
228
209
  # @param ret [Object] the Return of the invoked method
229
- # @param args [Array<Object>] the Arguments with which the method
230
- # was invoked
210
+ # @param args [Array<Object>] the Arguments with which the method was invoked
231
211
  def apply_regexp_rule trigger_node, source, object, ret, *args
232
212
  return unless source.is_a?(String)
233
213
  return if trigger_node.good_value && source.match?(trigger_node.good_value)
@@ -236,16 +216,15 @@ module Contrast
236
216
  build_finding(trigger_node, source, object, ret, *args)
237
217
  end
238
218
 
239
- # This is our method that actually checks the taint on the object
240
- # 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.
241
221
  #
242
- # @param trigger_node [Contrast::Agent::Assess::Policy::TriggerNode]
243
- # 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
244
224
  # @param source [Object] the source of the Trigger Event
245
225
  # @param object [Object] the Object on which the method was invoked
246
226
  # @param ret [Object] the Return of the invoked method
247
- # @param args [Array<Object>] the Arguments with which the method
248
- # was invoked
227
+ # @param args [Array<Object>] the Arguments with which the method was invoked
249
228
  def apply_dataflow_rule trigger_node, source, object, ret, *args
250
229
  return unless source
251
230
 
@@ -267,9 +246,8 @@ module Contrast
267
246
  logger.debug('Trigger source is untrackable. Unable to inspect.',
268
247
  node_id: trigger_node.id,
269
248
  source_id: source.__id__,
270
- source_type: source.cs__class.to_s,
249
+ source_type: source.cs__class.cs__name,
271
250
  frozen: source.cs__frozen?)
272
- logger.trace(source.to_s[0..99])
273
251
  end
274
252
  end
275
253
 
@@ -288,8 +266,7 @@ module Contrast
288
266
 
289
267
  build_events finding, properties.event if properties.event
290
268
 
291
- # Google::Protobuf::Map doesn't support merge!, so we have to do this
292
- # long form
269
+ # Google::Protobuf::Map doesn't support merge!, so we have to do this long form
293
270
  source_props = properties.properties
294
271
  return unless source_props
295
272
 
@@ -305,8 +282,8 @@ module Contrast
305
282
  event.parent_events&.each do |parent_event|
306
283
  build_events(finding, parent_event)
307
284
  end
308
- # events could technically be nil, but we would have failed
309
- # 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
310
287
  finding.events << event.to_dtm_event
311
288
  end
312
289
 
@@ -325,21 +302,18 @@ module Contrast
325
302
  finding.preflight = Contrast::Utils::PreflightUtil.create_preflight(finding)
326
303
  end
327
304
 
328
- # Because our APIs are not versioned, TeamServer relies on a field,
329
- # version, to tell it what, if any, validation it can preform on
330
- # the findings we send it. Examine the finding and determine the
331
- # 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.
332
308
  #
333
309
  # @param finding [Contrast::Api::Dtm::Finding]
334
310
  # @return [int] the version of compliance
335
311
  def determine_compliance_version finding
336
312
  return MINIMUM_FINDING_VERSION unless finding
337
- # as routes are the only variable between findings, in the case
338
- # where we couldn't determine one, any finding with a route is at
339
- # 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
340
315
  return CURRENT_FINDING_VERSION if finding.routes.any?
341
- # any finding without events is not of a dataflow type and
342
- # therefore at maximum compliance
316
+ # any finding without events is not of a dataflow type and therefore at maximum compliance
343
317
  return CURRENT_FINDING_VERSION unless finding.events.any?
344
318
 
345
319
  MINIMUM_FINDING_VERSION
@@ -68,7 +68,7 @@ module Contrast
68
68
  end
69
69
 
70
70
  def rule_disabled?
71
- ASSESS.rule_disabled?(rule_id)
71
+ ::Contrast::ASSESS.rule_disabled?(rule_id)
72
72
  end
73
73
 
74
74
  # Indicate if this is a dataflow based trigger, meaning it has a proper
@@ -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