contrast-agent 4.11.0 → 4.14.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (145) hide show
  1. checksums.yaml +4 -4
  2. data/.simplecov +1 -0
  3. data/ext/cs__assess_module/cs__assess_module.c +48 -0
  4. data/ext/cs__assess_module/cs__assess_module.h +7 -0
  5. data/ext/cs__common/cs__common.c +24 -7
  6. data/ext/cs__common/cs__common.h +12 -2
  7. data/ext/cs__contrast_patch/cs__contrast_patch.c +48 -11
  8. data/ext/cs__contrast_patch/cs__contrast_patch.h +5 -2
  9. data/ext/cs__os_information/cs__os_information.c +31 -0
  10. data/ext/cs__os_information/cs__os_information.h +7 -0
  11. data/ext/{cs__protect_kernel → cs__os_information}/extconf.rb +0 -0
  12. data/lib/contrast/agent/assess/contrast_event.rb +1 -1
  13. data/lib/contrast/agent/assess/contrast_object.rb +1 -1
  14. data/lib/contrast/agent/assess/policy/dynamic_source_factory.rb +2 -0
  15. data/lib/contrast/agent/assess/policy/policy_node.rb +6 -6
  16. data/lib/contrast/agent/assess/policy/policy_scanner.rb +5 -0
  17. data/lib/contrast/agent/assess/policy/preshift.rb +19 -6
  18. data/lib/contrast/agent/assess/policy/propagation_method.rb +2 -116
  19. data/lib/contrast/agent/assess/policy/propagation_node.rb +4 -4
  20. data/lib/contrast/agent/assess/policy/propagator/center.rb +1 -1
  21. data/lib/contrast/agent/assess/policy/propagator/database_write.rb +2 -0
  22. data/lib/contrast/agent/assess/policy/propagator/substitution.rb +2 -154
  23. data/lib/contrast/agent/assess/policy/source_method.rb +2 -71
  24. data/lib/contrast/agent/assess/policy/trigger_method.rb +45 -110
  25. data/lib/contrast/agent/assess/policy/trigger_node.rb +62 -21
  26. data/lib/contrast/agent/assess/policy/trigger_validation/xss_validator.rb +1 -1
  27. data/lib/contrast/agent/assess/property/tagged.rb +66 -189
  28. data/lib/contrast/agent/assess/rule/provider/hardcoded_value_rule.rb +40 -6
  29. data/lib/contrast/agent/deadzone/policy/policy.rb +6 -0
  30. data/lib/contrast/agent/inventory/dependency_usage_analysis.rb +1 -0
  31. data/lib/contrast/agent/metric_telemetry_event.rb +26 -0
  32. data/lib/contrast/agent/middleware.rb +14 -62
  33. data/lib/contrast/agent/patching/policy/after_load_patcher.rb +0 -1
  34. data/lib/contrast/agent/patching/policy/method_policy.rb +3 -44
  35. data/lib/contrast/agent/patching/policy/method_policy_extend.rb +111 -0
  36. data/lib/contrast/agent/patching/policy/patch.rb +37 -238
  37. data/lib/contrast/agent/patching/policy/patcher.rb +15 -50
  38. data/lib/contrast/agent/reporting/report.rb +21 -0
  39. data/lib/contrast/agent/reporting/reporter.rb +142 -0
  40. data/lib/contrast/agent/reporting/reporting_events/finding.rb +90 -0
  41. data/lib/contrast/agent/reporting/reporting_events/preflight.rb +25 -0
  42. data/lib/contrast/agent/reporting/reporting_events/preflight_message.rb +56 -0
  43. data/lib/contrast/agent/reporting/reporting_events/reporting_event.rb +37 -0
  44. data/lib/contrast/agent/reporting/reporting_utilities/audit.rb +127 -0
  45. data/lib/contrast/agent/reporting/reporting_utilities/reporter_client.rb +168 -0
  46. data/lib/contrast/agent/reporting/reporting_utilities/reporting_storage.rb +66 -0
  47. data/lib/contrast/agent/request.rb +2 -81
  48. data/lib/contrast/agent/request_context.rb +18 -126
  49. data/lib/contrast/agent/request_context_extend.rb +138 -0
  50. data/lib/contrast/agent/request_handler.rb +7 -3
  51. data/lib/contrast/agent/response.rb +2 -73
  52. data/lib/contrast/agent/rule_set.rb +2 -4
  53. data/lib/contrast/agent/startup_metrics_telemetry_event.rb +94 -0
  54. data/lib/contrast/agent/static_analysis.rb +5 -3
  55. data/lib/contrast/agent/telemetry.rb +137 -0
  56. data/lib/contrast/agent/telemetry_event.rb +33 -0
  57. data/lib/contrast/agent/thread_watcher.rb +66 -11
  58. data/lib/contrast/agent/version.rb +1 -1
  59. data/lib/contrast/agent.rb +21 -1
  60. data/lib/contrast/api/communication/connection_status.rb +10 -7
  61. data/lib/contrast/api/communication/messaging_queue.rb +37 -3
  62. data/lib/contrast/api/communication/response_processor.rb +15 -8
  63. data/lib/contrast/api/communication/service_lifecycle.rb +13 -3
  64. data/lib/contrast/api/communication/socket.rb +6 -8
  65. data/lib/contrast/api/communication/socket_client.rb +29 -12
  66. data/lib/contrast/api/communication/speedracer.rb +37 -1
  67. data/lib/contrast/api/communication/tcp_socket.rb +4 -3
  68. data/lib/contrast/api/communication/unix_socket.rb +1 -0
  69. data/lib/contrast/api/decorators/finding.rb +45 -0
  70. data/lib/contrast/components/api.rb +90 -0
  71. data/lib/contrast/components/app_context.rb +10 -41
  72. data/lib/contrast/components/app_context_extend.rb +78 -0
  73. data/lib/contrast/components/assess.rb +7 -0
  74. data/lib/contrast/components/base.rb +23 -0
  75. data/lib/contrast/components/config.rb +92 -13
  76. data/lib/contrast/components/contrast_service.rb +11 -0
  77. data/lib/contrast/components/sampling.rb +2 -2
  78. data/lib/contrast/config/agent_configuration.rb +1 -1
  79. data/lib/contrast/config/api_configuration.rb +27 -0
  80. data/lib/contrast/config/api_proxy_configuration.rb +14 -0
  81. data/lib/contrast/config/application_configuration.rb +2 -3
  82. data/lib/contrast/config/assess_configuration.rb +3 -2
  83. data/lib/contrast/config/base_configuration.rb +17 -28
  84. data/lib/contrast/config/certification_configuration.rb +15 -0
  85. data/lib/contrast/config/env_variables.rb +18 -0
  86. data/lib/contrast/config/heap_dump_configuration.rb +6 -6
  87. data/lib/contrast/config/inventory_configuration.rb +1 -5
  88. data/lib/contrast/config/protect_rule_configuration.rb +1 -1
  89. data/lib/contrast/config/request_audit_configuration.rb +18 -0
  90. data/lib/contrast/config/root_configuration.rb +1 -0
  91. data/lib/contrast/config/ruby_configuration.rb +6 -6
  92. data/lib/contrast/config/service_configuration.rb +2 -2
  93. data/lib/contrast/config.rb +1 -1
  94. data/lib/contrast/configuration.rb +4 -2
  95. data/lib/contrast/extension/assess/array.rb +5 -7
  96. data/lib/contrast/framework/manager.rb +22 -44
  97. data/lib/contrast/framework/manager_extend.rb +50 -0
  98. data/lib/contrast/framework/rails/patch/action_controller_live_buffer.rb +9 -6
  99. data/lib/contrast/framework/rails/patch/support.rb +31 -29
  100. data/lib/contrast/framework/rails/railtie.rb +1 -1
  101. data/lib/contrast/framework/sinatra/support.rb +2 -1
  102. data/lib/contrast/logger/application.rb +4 -0
  103. data/lib/contrast/logger/log.rb +8 -103
  104. data/lib/contrast/utils/assess/propagation_method_utils.rb +129 -0
  105. data/lib/contrast/utils/assess/property/tagged_utils.rb +165 -0
  106. data/lib/contrast/utils/assess/source_method_utils.rb +83 -0
  107. data/lib/contrast/utils/assess/tracking_util.rb +20 -15
  108. data/lib/contrast/utils/assess/trigger_method_utils.rb +138 -0
  109. data/lib/contrast/utils/class_util.rb +65 -54
  110. data/lib/contrast/utils/exclude_key.rb +20 -0
  111. data/lib/contrast/utils/findings.rb +62 -0
  112. data/lib/contrast/utils/hash_digest.rb +10 -73
  113. data/lib/contrast/utils/hash_digest_extend.rb +86 -0
  114. data/lib/contrast/utils/head_dump_utils_extend.rb +74 -0
  115. data/lib/contrast/utils/heap_dump_util.rb +2 -65
  116. data/lib/contrast/utils/invalid_configuration_util.rb +29 -0
  117. data/lib/contrast/utils/io_util.rb +1 -1
  118. data/lib/contrast/utils/log_utils.rb +108 -0
  119. data/lib/contrast/utils/lru_cache.rb +4 -2
  120. data/lib/contrast/utils/metrics_hash.rb +59 -0
  121. data/lib/contrast/utils/middleware_utils.rb +87 -0
  122. data/lib/contrast/utils/net_http_base.rb +158 -0
  123. data/lib/contrast/utils/object_share.rb +1 -0
  124. data/lib/contrast/utils/os.rb +23 -0
  125. data/lib/contrast/utils/patching/policy/patch_utils.rb +232 -0
  126. data/lib/contrast/utils/patching/policy/patcher_utils.rb +54 -0
  127. data/lib/contrast/utils/request_utils.rb +88 -0
  128. data/lib/contrast/utils/response_utils.rb +97 -0
  129. data/lib/contrast/utils/substitution_utils.rb +167 -0
  130. data/lib/contrast/utils/tag_util.rb +9 -9
  131. data/lib/contrast/utils/telemetry.rb +79 -0
  132. data/lib/contrast/utils/telemetry_client.rb +90 -0
  133. data/lib/contrast/utils/telemetry_identifier.rb +130 -0
  134. data/lib/contrast.rb +19 -1
  135. data/resources/assess/policy.json +12 -6
  136. data/resources/deadzone/policy.json +86 -5
  137. data/ruby-agent.gemspec +7 -6
  138. data/service_executables/VERSION +1 -1
  139. data/service_executables/linux/contrast-service +0 -0
  140. data/service_executables/mac/contrast-service +0 -0
  141. metadata +68 -26
  142. data/ext/cs__protect_kernel/cs__protect_kernel.c +0 -47
  143. data/ext/cs__protect_kernel/cs__protect_kernel.h +0 -12
  144. data/lib/contrast/config/default_value.rb +0 -17
  145. data/lib/contrast/extension/protect/kernel.rb +0 -29
@@ -0,0 +1,165 @@
1
+ # Copyright (c) 2021 Contrast Security, Inc. See https://www.contrastsecurity.com/enduser-terms-0317a for more details.
2
+ # frozen_string_literal: true
3
+
4
+ module Contrast
5
+ module Utils
6
+ module Assess
7
+ # This module will include all methods for some internal validations in the Tagged property module
8
+ # and some other module methods from the same place, so we can ease the main module
9
+ # This module includes simple methods for the tags like
10
+ # adding tags, getting tags, deleting tags and similar
11
+ module TaggedUtils
12
+ # Is the given tag present?
13
+ # Used in testing, so found by `be_tagged`, if you're grepping for it
14
+ #
15
+ # @param label [Symbol] the tag to check for
16
+ # @return [Boolean]
17
+ def tagged? label
18
+ tracked? && tags.key?(label)
19
+ end
20
+
21
+ # Similar to #tracked?, but limited to a given range.
22
+ #
23
+ # @param start [Integer] the inclusive start index to check.
24
+ # @param finish [Integer] the exclusive end index to check.
25
+ # @return [Boolean]
26
+ def any_tags_between? start, finish
27
+ return false unless tracked?
28
+
29
+ tags.each_value do |tag_array|
30
+ return true if tag_array.any? { |tag| tag.overlaps?(start, finish) }
31
+ end
32
+ false
33
+ end
34
+
35
+ # Given a tag name and range object, add a new tag to this
36
+ # collection. If the given range touches an existing tag,
37
+ # we'll combine the two, adjusting the existing one and
38
+ # dropping this new one.
39
+ #
40
+ # @param label [String] the name of the tag
41
+ # @param range [Range] the Range that the tag covers, inclusive to
42
+ # exclusive
43
+ def add_tag label, range
44
+ length = range.end - range.begin
45
+ tag = Contrast::Agent::Assess::Tag.new(label, length, range.begin)
46
+ existing = fetch_tag(label)
47
+ tags[label] = Contrast::Utils::TagUtil.ordered_merge(existing, tag)
48
+ end
49
+
50
+ def set_tags label, tag_ranges
51
+ tags[label] = tag_ranges
52
+ end
53
+
54
+ # Returns a list of all current tags.
55
+ #
56
+ # @return [Hash<Contrast::Agent::Assess::Tag>]
57
+ def get_tags # rubocop:disable Naming/AccessorMethodName
58
+ return Contrast::Utils::ObjectShare::EMPTY_HASH unless tracked?
59
+
60
+ tags
61
+ end
62
+
63
+ # We'll use this as a helper method to retrieve tags from the hash.
64
+ # Because the hash auto-populates an empty array when we try to
65
+ # access a tag in it, we cannot use the [] method without side
66
+ # effect. To get around this, we'll use a fetch work around.
67
+ #
68
+ # @param label [Symbol] the label to look up
69
+ # @return [Array<Contrast::Agent::Assess::Tag>] all the tags with
70
+ # that label
71
+ def fetch_tag label
72
+ get_tags.fetch(label, nil) if tracked?
73
+ end
74
+
75
+ # Remove all tags with a given label
76
+ def delete_tags label
77
+ tags.delete(label) if tracked?
78
+ end
79
+
80
+ # Reset the tag hash
81
+ def clear_tags
82
+ tags.clear if tracked?
83
+ end
84
+
85
+ # Returns a list of all current tag labels, most likely to be used for
86
+ # a splat operation
87
+ def tag_keys
88
+ return Contrast::Utils::ObjectShare::EMPTY_ARRAY unless tracked?
89
+
90
+ tags.keys
91
+ end
92
+
93
+ # Calls merge to combine touching or overlapping tags
94
+ # Deletes empty tags
95
+ def cleanup_tags
96
+ return unless tracked?
97
+
98
+ Contrast::Utils::TagUtil.merge_tags(tags)
99
+ tags.delete_if { |_, value| value.empty? }
100
+ end
101
+
102
+ # Find all of the ranges that span a given index. This is used
103
+ # in propagation when we need to shift tags about. For instance, in
104
+ # the append method when we need to see if any tag at the end needs
105
+ # to be expanded out to the size of the new String.
106
+ #
107
+ # Note: Tags do not know their key, so this is only the range covered
108
+ #
109
+ # @param idx [Integer] the index to check for tags
110
+ # @return [Array<Contrast::Agent::Assess::Tag>] a set of all the Tags
111
+ # covering the given index. This is only the ranges, not the names.
112
+ def tags_at idx
113
+ return Contrast::Utils::ObjectShare::EMPTY_ARRAY unless tracked?
114
+
115
+ at = []
116
+ tags.each_value do |tag_array|
117
+ tag_array.each do |tag|
118
+ if tag.covers?(idx)
119
+ at << tag
120
+ elsif tag.above?(idx)
121
+ break
122
+ end
123
+ end
124
+ end
125
+ at
126
+ end
127
+
128
+ # given a range, select all tags in that range the selected tags are
129
+ # shifted such that the start index of the new tag (0) aligns with
130
+ # the given start index in the range
131
+ #
132
+ # current tags: 5-15
133
+ # range : 5-10
134
+ # result : 0-05
135
+ #
136
+ # Note that we disable Lint/DuplicateBranch in this branch in order
137
+ # list out all tag range cases in the proper order to make this
138
+ # easier to understand
139
+ #
140
+ # @param range [Range] the span to check, inclusive to exclusive
141
+ # @return [Hash{String => Contrast::Agent::Assess::Tag}] the hash of
142
+ # key to tags
143
+ def tags_at_range range
144
+ return Contrast::Utils::ObjectShare::EMPTY_HASH unless tracked?
145
+
146
+ at = Hash.new { |h, k| h[k] = [] }
147
+ tags.each_pair do |key, value|
148
+ add = nil
149
+ value.each do |tag|
150
+ within_range = resize_to_range(tag, range)
151
+ if within_range
152
+ add ||= []
153
+ add << within_range
154
+ end
155
+ end
156
+ next unless add&.any?
157
+
158
+ at[key] = add
159
+ end
160
+ at
161
+ end
162
+ end
163
+ end
164
+ end
165
+ end
@@ -0,0 +1,83 @@
1
+ # Copyright (c) 2021 Contrast Security, Inc. See https://www.contrastsecurity.com/enduser-terms-0317a for more details.
2
+ # frozen_string_literal: true
3
+
4
+ module Contrast
5
+ module Utils
6
+ module Assess
7
+ # This module will include all methods for some internal validations in the SourceMethod module
8
+ # and some other module methods from the same place, so we can ease the main module
9
+ module SourceMethodUtils
10
+ # Safely duplicate the target, or return nil
11
+ #
12
+ # @param target [Object] the thing to check for duplication
13
+ def safe_dup target
14
+ target.dup
15
+ rescue StandardError => _e
16
+ nil
17
+ end
18
+
19
+ # Find the name of the source
20
+ #
21
+ # @param source_node [Contrast::Agent::Assess::Policy::SourceNode] the node to direct applying this source
22
+ # event
23
+ # @param object [Object] the Object on which the method was invoked
24
+ # @param ret [Object] the Return of the invoked method
25
+ # @param args [Array<Object>] the Arguments with which the method was invoked
26
+ # @return [String, nil] the human readable name of the target to which this source event applies, or nil if
27
+ # none provided by the node
28
+ def determine_source_name source_node, object, ret, *args
29
+ return source_node.get_property('dynamic_source_name') if source_node.type == 'UNTRUSTED_DATABASE'
30
+
31
+ source_node_source = source_node.sources[0]
32
+ case source_node_source
33
+ when nil
34
+ nil
35
+ when Contrast::Utils::ObjectShare::RETURN_KEY
36
+ ret
37
+ when Contrast::Utils::ObjectShare::OBJECT_KEY
38
+ object
39
+ else
40
+ args[source_node_source]
41
+ end
42
+ end
43
+
44
+ # Determine if we should analyze this method invocation for a Source or not. We should if we have enough
45
+ # information to build the context of this invocation, we're not disabled, and we can't immediately
46
+ # determine the invocation was done safely.
47
+ #
48
+ # @param method_policy [Contrast::Agent::Patching::Policy::MethodPolicy] the policy that applies to the
49
+ # method being called
50
+ # @param object [Object] the Object on which the method was invoked
51
+ # @param ret [Object] the Return of the invoked method
52
+ # @param args [Array<Object>] the Arguments with which the method was invoked
53
+ # @return [boolean] if the invocation of this method should be analyzed
54
+ def analyze? method_policy, object, ret, args
55
+ return false unless method_policy&.source_node
56
+ return false unless ::Contrast::ASSESS.enabled?
57
+ return false unless Contrast::Agent::REQUEST_TRACKER.current&.analyze_request?
58
+
59
+ !safe_invocation?(method_policy.source_node, object, ret, args)
60
+ end
61
+
62
+ # Determine if the method was invoked safely.
63
+ #
64
+ # @param source_node [Contrast::Agent::Assess::Policy::SourceNode] the node to direct applying this source
65
+ # event
66
+ # @param _object [Object] the Object on which the method was invoked
67
+ # @param _ret [Object] the Return of the invoked method
68
+ # @param args [Array<Object>] the Arguments with which the method was invoked
69
+ # @return [boolean] if the invocation of this method was safe
70
+ def safe_invocation? source_node, _object, _ret, args
71
+ # According the the Rack Specification https://github.com/rack/rack/blob/master/SPEC.rdoc, any header
72
+ # from the Request will start with HTTP_. As such, only Headers with that key should be considered for
73
+ # tracking, as the others have come from the Framework or Middleware stashing in the ENV. Rails, for
74
+ # instance, uses action_dispatch. to store several values. Technically, you can't call
75
+ # Rack::Request#get_header without a parameter, and that parameter should be a String, but trust no one.
76
+ source_node.id == 'Assess:Source:Rack::Request::Env#get_header' &&
77
+ args&.any? &&
78
+ !args[0].to_s.start_with?('HTTP_')
79
+ end
80
+ end
81
+ end
82
+ end
83
+ end
@@ -50,14 +50,9 @@ module Contrast
50
50
 
51
51
  idx += 1
52
52
  if Contrast::Utils::DuckUtils.iterable_hash?(obj)
53
- obj.each_pair do |k, v|
54
- return true if _tracked?(k, idx) || _tracked?(v, idx)
55
- end
56
- false
53
+ handle_hash obj, idx
57
54
  elsif Contrast::Utils::DuckUtils.iterable_enumerable?(obj)
58
- obj.any? do |ele|
59
- _tracked?(ele, idx) unless obj == ele
60
- end
55
+ handle_enumerable obj, idx
61
56
  else
62
57
  Contrast::Agent::Assess::Tracker.tracked?(obj)
63
58
  end
@@ -84,15 +79,9 @@ module Contrast
84
79
 
85
80
  idx += 1
86
81
  if Contrast::Utils::DuckUtils.iterable_hash?(obj)
87
- obj.each_pair do |k, v|
88
- return true if _trackable?(k, idx)
89
- return true if _trackable?(v, idx)
90
- end
91
- false
82
+ handle_hash obj, idx
92
83
  elsif Contrast::Utils::DuckUtils.iterable_enumerable?(obj)
93
- obj.any? do |ele|
94
- _trackable?(ele, idx) unless obj == ele
95
- end
84
+ handle_enumerable obj, idx
96
85
  else
97
86
  Contrast::Agent::Assess::Tracker.trackable?(obj)
98
87
  end
@@ -103,6 +92,22 @@ module Contrast
103
92
  logger.warn('Failed to determine trackable', e, module: obj.cs__class)
104
93
  false
105
94
  end
95
+
96
+ def handle_hash obj, idx
97
+ caller_method = caller(1..1).first[/`.*'/][1..-2].to_sym
98
+ obj.each_pair do |k, v|
99
+ return true if send(caller_method, k, idx)
100
+ return true if send(caller_method, v, idx)
101
+ end
102
+ false
103
+ end
104
+
105
+ def handle_enumerable obj, idx
106
+ caller_method = caller(1..1).first[/`.*'/][1..-2].to_sym
107
+ obj.any? do |ele|
108
+ send(caller_method, ele, idx) unless obj == ele
109
+ end
110
+ end
106
111
  end
107
112
  end
108
113
  end
@@ -0,0 +1,138 @@
1
+ # Copyright (c) 2021 Contrast Security, Inc. See https://www.contrastsecurity.com/enduser-terms-0317a for more details.
2
+ # frozen_string_literal: true
3
+
4
+ module Contrast
5
+ module Utils
6
+ module Assess
7
+ # This module will include all methods for some internal validations/appliers in the TriggerMethod module
8
+ # and some other module methods from the same place, so we can ease the main module
9
+ module TriggerMethodUtils
10
+ # A request is reportable if it is not from ActionController::Live
11
+ #
12
+ # @param env [Hash] the env of the Request
13
+ # @return [Boolean]
14
+ def reportable? env
15
+ !(defined?(ActionController::Live) &&
16
+ env &&
17
+ env['action_controller.instance'].cs__class.included_modules.include?(ActionController::Live))
18
+ end
19
+
20
+ # Find the request for this finding. This assumes, for now, that if there is an active request, then that
21
+ # is the request to report. Otherwise, we'll use the first request found in the events of the
22
+ # source_object.
23
+ #
24
+ # @param source [Object,nil] some Object used as the source of a trigger event
25
+ # @return [Contrast::Agent::Request,nil] the request from which the dataflow on the request originated.
26
+ def find_request source
27
+ return Contrast::Agent::REQUEST_TRACKER.current.request if Contrast::Agent::REQUEST_TRACKER.current
28
+ return unless (properties = Contrast::Agent::Assess::Tracker.properties(source))
29
+
30
+ find_event_request(properties.event)
31
+ end
32
+
33
+ # Finds the first request along the left most tree of parent events
34
+ #
35
+ # @param event [Contrast::Agent::Assess::ContrastEvent|Contrast::Agent::Assess::Events::SourceEvent]
36
+ # @return [Contrast::Agent::Request, nil]
37
+ def find_event_request event
38
+ return event.request if event.cs__is_a?(Contrast::Agent::Assess::Events::SourceEvent)
39
+
40
+ idx = 0
41
+ while idx <= event.parent_events.length
42
+ found = find_event_request(event.parent_events[idx])
43
+ return found if found
44
+
45
+ idx += 1
46
+ return event.request if event.request
47
+ end
48
+ return unless event.cs__is_a?(Contrast::Agent::Assess::Events::SourceEvent)
49
+
50
+ event.request
51
+ end
52
+
53
+ # ===== APPLIERS =====
54
+ # This is our method that actually checks the taint on the object our trigger_node targets.
55
+ #
56
+ # @param trigger_node [Contrast::Agent::Assess::Policy::TriggerNode] the node to direct applying this
57
+ # trigger event
58
+ # @param source [Object] the source of the Trigger Event
59
+ # @param object [Object] the Object on which the method was invoked
60
+ # @param ret [Object] the Return of the invoked method
61
+ # @param args [Array<Object>] the Arguments with which the method was invoked
62
+ def apply_trigger trigger_node, source, object, ret, *args
63
+ return unless trigger_node
64
+ return if trigger_node.rule_disabled?
65
+ return if trigger_node.dataflow? && source.nil?
66
+
67
+ if trigger_node.regexp_rule?
68
+ apply_regexp_rule(trigger_node, source, object, ret, *args)
69
+ elsif trigger_node.custom_trigger?
70
+ trigger_node.apply_custom_trigger(trigger_node, source, object, ret, *args)
71
+ elsif trigger_node.dataflow?
72
+ apply_dataflow_rule(trigger_node, source, object, ret, *args)
73
+ else # trigger rule - just calling the method is dangerous
74
+ build_finding(trigger_node, source, object, ret, *args)
75
+ end
76
+ rescue StandardError => e
77
+ logger.warn('Unable to apply trigger', e, node_id: trigger_node.id)
78
+ end
79
+
80
+ # This is our method that actually checks the taint on the object our trigger_node targets for our Regexp
81
+ # based rules.
82
+ #
83
+ # @param trigger_node [Contrast::Agent::Assess::Policy::TriggerNode] the node to direct applying this
84
+ # trigger event
85
+ # @param source [Object] the source of the Trigger Event
86
+ # @param object [Object] the Object on which the method was invoked
87
+ # @param ret [Object] the Return of the invoked method
88
+ # @param args [Array<Object>] the Arguments with which the method was invoked
89
+ def apply_regexp_rule trigger_node, source, object, ret, *args
90
+ return unless source.is_a?(String)
91
+ return if trigger_node.good_value && source.match?(trigger_node.good_value)
92
+ return if trigger_node.bad_value && source !~ trigger_node.bad_value
93
+
94
+ build_finding(trigger_node, source, object, ret, *args)
95
+ end
96
+
97
+ # This is our method that actually checks the taint on the object our trigger_node targets for our Dataflow
98
+ # based rules.
99
+ #
100
+ # @param trigger_node [Contrast::Agent::Assess::Policy::TriggerNode] the node to direct applying this
101
+ # trigger event
102
+ # @param source [Object] the source of the Trigger Event
103
+ # @param object [Object] the Object on which the method was invoked
104
+ # @param ret [Object] the Return of the invoked method
105
+ # @param args [Array<Object>] the Arguments with which the method was invoked
106
+ def apply_dataflow_rule trigger_node, source, object, ret, *args # rubocop:disable Metrics/PerceivedComplexity
107
+ return unless source
108
+
109
+ if Contrast::Agent::Assess::Tracker.trackable?(source)
110
+ return unless Contrast::Agent::Assess::Tracker.tracked?(source)
111
+ return unless trigger_node.violated?(source)
112
+
113
+ build_finding(trigger_node, source, object, ret, *args)
114
+ elsif Contrast::Utils::DuckUtils.iterable_hash?(source)
115
+ return unless Contrast::Agent::Assess::Tracker.tracked?(source)
116
+
117
+ source.each_pair do |key, value|
118
+ apply_dataflow_rule(trigger_node, key, object, ret, *args)
119
+ apply_dataflow_rule(trigger_node, value, object, ret, *args)
120
+ end
121
+ elsif Contrast::Utils::DuckUtils.iterable_enumerable?(source)
122
+ return unless Contrast::Agent::Assess::Tracker.tracked?(source)
123
+
124
+ source.each do |value|
125
+ apply_dataflow_rule(trigger_node, value, object, ret, *args)
126
+ end
127
+ else
128
+ logger.debug('Trigger source is untrackable. Unable to inspect.',
129
+ node_id: trigger_node.id,
130
+ source_id: source.__id__,
131
+ source_type: source.cs__class.cs__name,
132
+ frozen: source.cs__frozen?)
133
+ end
134
+ end
135
+ end
136
+ end
137
+ end
138
+ end
@@ -9,17 +9,15 @@ module Contrast
9
9
  module Utils
10
10
  # Utility methods for exploring the complete space of Objects
11
11
  class ClassUtil
12
- @lru_cache = LRUCache.new
12
+ @lru_cache = LRUCache.new(300)
13
+ @string_cache = LRUCache.new(300)
13
14
  class << self
14
- # some classes have had things prepended to them, like Marshal in Rails
15
- # 5 and higher. Their ActiveSupport::MarshalWithAutoloading will break
16
- # our alias patching approach, as will any other prepend on something
17
- # that we touch. Prepend and Alias are inherently incompatible monkey
18
- # patching approaches. As such, we need to know if something has been
19
- # prepended to.
15
+ # some classes have had things prepended to them, like Marshal in Rails 5 and higher. Their
16
+ # ActiveSupport::MarshalWithAutoloading will break our alias patching approach, as will any other prepend on
17
+ # something that we touch. Prepend and Alias are inherently incompatible monkey patching approaches. As such,
18
+ # we need to know if something has been prepended to.
20
19
  #
21
- # @param mod [Module] the Module to check to see if it has had something
22
- # prepended
20
+ # @param mod [Module] the Module to check to see if it has had something prepended
23
21
  # @param ancestors [Array<Module>] the array of ancestors for the mod
24
22
  # @return [Boolean] if the mod has been prepended or not
25
23
  def prepended? mod, ancestors = nil
@@ -27,8 +25,13 @@ module Contrast
27
25
  ancestors[0] != mod
28
26
  end
29
27
 
30
- # return true if the given method is overwritten by one of the ancestors
31
- # in the ancestor change that comes before the given module
28
+ # return true if the given method is overwritten by one of the ancestors in the ancestor change that comes
29
+ # before the given module
30
+ #
31
+ # @param mod [Module] the Module to check to see if it has had something prepended
32
+ # @param method_policy [Contrast::Agent::Patching::Policy::MethodPolicy] the policy that holds the method we
33
+ # need to check
34
+ # @return [Boolean] if this method specifically was prepended
32
35
  def prepended_method? mod, method_policy
33
36
  target_module = determine_target_class mod, method_policy.instance_method
34
37
  ancestors = target_module.ancestors
@@ -43,49 +46,53 @@ module Contrast
43
46
  false
44
47
  end
45
48
 
46
- # Return a String representing the object invoking this method in the
47
- # form expected by our dataflow events.
49
+ # Return a String representing the object invoking this method in the form expected by our dataflow events.
50
+ # After implementing the LRU Cache, we firstly need to check if already had that object cached and if we have
51
+ # it - we can return it directly; otherwise we'll calculate and store the result before returning.
52
+ #
53
+ # TODO: RUBY-1327
54
+ # Once we move to 2.7+, we can combine the caches using ID b/c the memory location stops being the id
48
55
  #
49
56
  # @param object [Object, nil] the entity to convert to a String
50
- # @return [String] the human readable form of the String, as defined by
57
+ # @return [String, Object] the human readable form of the String, as defined by
51
58
  # https://bitbucket.org/contrastsecurity/assess-specifications/src/master/vulnerability/capture-snapshot.md
52
-
53
59
  def to_contrast_string object
54
- # After implementing the LRU Cache, we firstly need to check if already had that object cached
55
- # and if we have it - we can return it directly
56
- return @lru_cache[object.__id__] if @lru_cache.key? object.__id__
57
-
58
- # Only treat object like a string if it actually is a string+
59
- # some subclasses of String override string methods we depend on
60
- @lru_cache[object.__id__] = if object.cs__class == String
61
- cached = to_cached_string(object)
62
- return cached if cached
63
-
64
- object.dup
65
- elsif object.nil?
66
- Contrast::Utils::ObjectShare::NIL_STRING
67
- elsif object.cs__is_a?(Symbol)
68
- ":#{ object }"
69
- elsif object.cs__is_a?(Module) || object.cs__is_a?(Class)
70
- "#{ object.cs__name }@#{ object.__id__ }"
71
- elsif object.cs__is_a?(Regexp)
72
- object.source
73
- elsif use_to_s?(object)
74
- object.to_s
75
- else
76
- "#{ object.cs__class.cs__name }@#{ object.__id__ }"
77
- end
60
+ # Only treat object like a string if it actually is a string+ some subclasses of String override string
61
+ # methods we depend on
62
+ if object.cs__class == String
63
+ return @string_cache[object] if @string_cache.key? object
64
+
65
+ @string_cache[object] = to_cached_string(object) || object.dup
66
+ else
67
+ return @lru_cache[object.__id__] if @lru_cache.key? object.__id__
68
+
69
+ @lru_cache[object.__id__] = convert_object object
70
+ end
78
71
  end
79
72
 
80
- # The method const_defined? can cause autoload, which is bad for us.
81
- # The method autoload? doesn't traverse namespaces. This method lets us
82
- # provide a constant, as a String, and parse it to determine if it has
83
- # been truly truly defined, meaning it existed before this method was
84
- # invoked, not as a result of it.
73
+ def convert_object object
74
+ if object.nil?
75
+ Contrast::Utils::ObjectShare::NIL_STRING
76
+ elsif object.cs__is_a?(Symbol)
77
+ ":#{ object }"
78
+ elsif object.cs__is_a?(Module) || object.cs__is_a?(Class)
79
+ "#{ object.cs__name }@#{ object.__id__ }"
80
+ elsif object.cs__is_a?(Regexp)
81
+ object.source
82
+ elsif use_to_s?(object)
83
+ object.to_s
84
+ else
85
+ "#{ object.cs__class.cs__name }@#{ object.__id__ }"
86
+ end
87
+ end
88
+
89
+ # The method const_defined? can cause autoload, which is bad for us. The method autoload? doesn't traverse
90
+ # namespaces. This method lets us provide a constant, as a String, and parse it to determine if it has been
91
+ # truly truly defined, meaning it existed before this method was invoked, not as a result of it.
85
92
  #
86
- # This is required to handle a bug in Ruby prior to 2.7.0. When we drop
87
- # support for 2.6.X, we should remove this code.
88
- # https://bugs.ruby-lang.org/issues/10741
93
+ # TODO: RUBY-1326
94
+ # This is required to handle a bug in Ruby prior to 2.7.0. When we drop support for 2.6.X, we should remove
95
+ # this code. https://bugs.ruby-lang.org/issues/10741
89
96
  # @param name [String] the name of the constant to look up
90
97
  # @return [Boolean]
91
98
  def truly_defined? name
@@ -108,7 +115,8 @@ module Contrast
108
115
  private
109
116
 
110
117
  # Some objects have nice to_s that we can use to make them human readable. If they do, we should leverage them.
111
- # We used to do this by default, but this opened us up to danger, so we're instead using an allow list approach.
118
+ # We used to do this by default, but this opened us up to danger, so we're instead using an allow list
119
+ # approach.
112
120
  #
113
121
  # @param object [Object] something that may have a safe to_s method
114
122
  # @return [Boolean] if we should invoke to_s to represent the object
@@ -119,6 +127,11 @@ module Contrast
119
127
  false
120
128
  end
121
129
 
130
+ # Find the target class based on the instance, or module, provided. If a module, return it.
131
+ #
132
+ # @param mod [Module] the Module, or instance of a Module, that we need to check
133
+ # @param is_instance [Boolean] is the object provided an instance of a class, requiring lookup by class
134
+ # @return [Module]
122
135
  def determine_target_class mod, is_instance
123
136
  return mod if mod.singleton_class?
124
137
 
@@ -127,13 +140,11 @@ module Contrast
127
140
  mod
128
141
  end
129
142
 
130
- # If the String matches a common String in our ObjectShare, return that
131
- # rather that for use as the representation of the String rather than
132
- # forcing a duplication of the String.
143
+ # If the String matches a common String in our ObjectShare, return that rather that for use as the
144
+ # representation of the String rather than forcing a duplication of the String.
133
145
  #
134
- # @param string [String] some string of which we want a Contrast
135
- # representation.
136
- # @return [String,nil] the ObjectShare version of the String or nil
146
+ # @param string [String] some string of which we want a Contrast representation.
147
+ # @return [String, nil] the ObjectShare version of the String or nil
137
148
  def to_cached_string string
138
149
  return Contrast::Utils::ObjectShare::EMPTY_STRING if string.empty?
139
150
  return Contrast::Utils::ObjectShare::SLASH if string == Contrast::Utils::ObjectShare::SLASH
@@ -0,0 +1,20 @@
1
+ # Copyright (c) 2021 Contrast Security, Inc. See https://www.contrastsecurity.com/enduser-terms-0317a for more details.
2
+ # frozen_string_literal: true
3
+
4
+ module Contrast
5
+ module Utils
6
+ # Determine if configuration keys is excluded from logging
7
+ module ExcludeKey
8
+ EXCLUDE_FROM_LOG = %w[api api_key url service_key user_name].cs__freeze
9
+ class << self
10
+ # Check if a config key can be logged or not
11
+ #
12
+ # @param key [String] key to check
13
+ # @return[Boolean] true | false
14
+ def excludable? key
15
+ EXCLUDE_FROM_LOG.any? { |exclude_key| key.include? exclude_key }
16
+ end
17
+ end
18
+ end
19
+ end
20
+ end