contrast-agent 3.8.5 → 3.9.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (153) hide show
  1. checksums.yaml +4 -4
  2. data/ext/cs__assess_array/cs__assess_array.c +1 -1
  3. data/ext/cs__assess_module/cs__assess_module.c +0 -1
  4. data/ext/cs__assess_yield_track/cs__assess_yield_track.c +34 -0
  5. data/ext/cs__assess_yield_track/cs__assess_yield_track.h +12 -0
  6. data/ext/{cs__scope → cs__assess_yield_track}/extconf.rb +0 -0
  7. data/ext/cs__common/cs__common.c +6 -6
  8. data/ext/cs__common/cs__common.h +3 -1
  9. data/ext/cs__contrast_patch/cs__contrast_patch.c +142 -119
  10. data/ext/cs__contrast_patch/cs__contrast_patch.h +3 -0
  11. data/funchook/autom4te.cache/requests +48 -48
  12. data/funchook/config.log +2 -2
  13. data/lib/contrast/agent.rb +15 -5
  14. data/lib/contrast/agent/assess.rb +0 -1
  15. data/lib/contrast/agent/assess/contrast_event.rb +9 -8
  16. data/lib/contrast/agent/assess/policy/dynamic_source_factory.rb +68 -18
  17. data/lib/contrast/agent/assess/policy/policy.rb +0 -14
  18. data/lib/contrast/agent/assess/policy/policy_scanner.rb +1 -1
  19. data/lib/contrast/agent/assess/policy/preshift.rb +1 -1
  20. data/lib/contrast/agent/assess/policy/propagation_method.rb +4 -2
  21. data/lib/contrast/agent/assess/policy/propagator/custom.rb +1 -1
  22. data/lib/contrast/agent/assess/policy/propagator/database_write.rb +1 -1
  23. data/lib/contrast/agent/assess/policy/propagator/splat.rb +2 -2
  24. data/lib/contrast/agent/assess/policy/propagator/split.rb +166 -1
  25. data/lib/contrast/agent/assess/policy/rewriter_patch.rb +1 -0
  26. data/lib/contrast/agent/assess/policy/source_method.rb +199 -140
  27. data/lib/contrast/agent/assess/policy/source_validation/cross_site_validator.rb +30 -0
  28. data/lib/contrast/agent/assess/policy/source_validation/source_validation.rb +36 -0
  29. data/lib/contrast/agent/assess/policy/trigger_method.rb +238 -153
  30. data/lib/contrast/agent/assess/policy/trigger_node.rb +54 -9
  31. data/lib/contrast/agent/assess/policy/trigger_validation/trigger_validation.rb +13 -0
  32. data/lib/contrast/agent/assess/properties.rb +29 -0
  33. data/lib/contrast/agent/assess/rule/csrf/csrf_applicator.rb +35 -31
  34. data/lib/contrast/agent/assess/rule/provider/hardcoded_value_rule.rb +1 -1
  35. data/lib/contrast/agent/class_reopener.rb +98 -55
  36. data/lib/contrast/agent/feature_state.rb +1 -1
  37. data/lib/contrast/agent/inventory/policy/policy.rb +1 -1
  38. data/lib/contrast/agent/logger_manager.rb +2 -2
  39. data/lib/contrast/agent/middleware.rb +1 -3
  40. data/lib/contrast/agent/patching/policy/after_load_patch.rb +40 -4
  41. data/lib/contrast/agent/patching/policy/after_load_patcher.rb +33 -8
  42. data/lib/contrast/agent/patching/policy/method_policy.rb +20 -7
  43. data/lib/contrast/agent/patching/policy/patch.rb +54 -23
  44. data/lib/contrast/agent/patching/policy/patch_status.rb +0 -2
  45. data/lib/contrast/agent/patching/policy/patcher.rb +10 -11
  46. data/lib/contrast/agent/patching/policy/policy.rb +4 -0
  47. data/lib/contrast/agent/patching/policy/policy_node.rb +14 -1
  48. data/lib/contrast/agent/patching/policy/trigger_node.rb +2 -1
  49. data/lib/contrast/agent/protect/policy/policy.rb +6 -6
  50. data/lib/contrast/agent/protect/rule/base.rb +1 -1
  51. data/lib/contrast/agent/protect/rule/deserialization.rb +3 -25
  52. data/lib/contrast/agent/protect/rule/sqli.rb +1 -1
  53. data/lib/contrast/agent/railtie.rb +11 -5
  54. data/lib/contrast/agent/request.rb +1 -19
  55. data/lib/contrast/agent/request_context.rb +1 -1
  56. data/lib/contrast/agent/rewriter.rb +4 -3
  57. data/lib/contrast/agent/scope.rb +116 -19
  58. data/lib/contrast/agent/service_heartbeat.rb +5 -2
  59. data/lib/contrast/agent/settings_state.rb +12 -8
  60. data/lib/contrast/agent/version.rb +1 -1
  61. data/lib/contrast/api.rb +1 -0
  62. data/lib/contrast/api/speedracer.rb +2 -2
  63. data/lib/contrast/components/agent.rb +26 -7
  64. data/lib/contrast/components/app_context.rb +8 -45
  65. data/lib/contrast/components/contrast_service.rb +3 -4
  66. data/lib/contrast/components/interface.rb +1 -1
  67. data/lib/contrast/components/scope.rb +56 -26
  68. data/lib/contrast/config/ruby_configuration.rb +8 -3
  69. data/lib/contrast/delegators.rb +9 -0
  70. data/lib/contrast/delegators/application_update.rb +32 -0
  71. data/lib/contrast/extensions/framework/rack/cookie.rb +24 -0
  72. data/lib/contrast/extensions/framework/rack/request.rb +24 -0
  73. data/lib/contrast/extensions/framework/rack/response.rb +23 -0
  74. data/lib/contrast/extensions/framework/rails/action_controller_railties_helper_inherited.rb +20 -0
  75. data/lib/contrast/extensions/framework/rails/active_record.rb +26 -0
  76. data/lib/contrast/extensions/framework/rails/active_record_named.rb +53 -0
  77. data/lib/contrast/extensions/framework/rails/active_record_time_zone_inherited.rb +21 -0
  78. data/lib/contrast/extensions/framework/rails/buffer.rb +28 -0
  79. data/lib/contrast/extensions/framework/rails/configuration.rb +27 -0
  80. data/lib/contrast/extensions/framework/sinatra/base.rb +59 -0
  81. data/lib/contrast/{core_extensions → extensions/ruby_core}/assess.rb +12 -11
  82. data/lib/contrast/{core_extensions → extensions/ruby_core}/assess/array.rb +4 -3
  83. data/lib/contrast/{core_extensions → extensions/ruby_core}/assess/assess_extension.rb +0 -2
  84. data/lib/contrast/{core_extensions → extensions/ruby_core}/assess/basic_object.rb +1 -1
  85. data/lib/contrast/{core_extensions → extensions/ruby_core}/assess/erb.rb +0 -0
  86. data/lib/contrast/{core_extensions → extensions/ruby_core}/assess/exec_trigger.rb +0 -0
  87. data/lib/contrast/{core_extensions → extensions/ruby_core}/assess/fiber.rb +3 -4
  88. data/lib/contrast/{core_extensions → extensions/ruby_core}/assess/hash.rb +0 -0
  89. data/lib/contrast/{core_extensions → extensions/ruby_core}/assess/kernel.rb +1 -1
  90. data/lib/contrast/{core_extensions → extensions/ruby_core}/assess/module.rb +1 -1
  91. data/lib/contrast/{core_extensions → extensions/ruby_core}/assess/regexp.rb +0 -0
  92. data/lib/contrast/{core_extensions → extensions/ruby_core}/assess/string.rb +0 -0
  93. data/lib/contrast/{core_extensions → extensions/ruby_core}/assess/tilt_template_trigger.rb +0 -0
  94. data/lib/contrast/extensions/ruby_core/assess/xpath_library_trigger.rb +40 -0
  95. data/lib/contrast/{core_extensions → extensions/ruby_core}/delegator.rb +0 -0
  96. data/lib/contrast/{core_extensions → extensions/ruby_core}/eval_trigger.rb +1 -1
  97. data/lib/contrast/{core_extensions → extensions/ruby_core}/inventory.rb +0 -0
  98. data/lib/contrast/{core_extensions → extensions/ruby_core}/inventory/datastores.rb +1 -1
  99. data/lib/contrast/extensions/ruby_core/module.rb +17 -0
  100. data/lib/contrast/{core_extensions → extensions/ruby_core}/protect.rb +0 -0
  101. data/lib/contrast/{core_extensions → extensions/ruby_core}/protect/applies_command_injection_rule.rb +8 -6
  102. data/lib/contrast/{core_extensions → extensions/ruby_core}/protect/applies_deserialization_rule.rb +7 -5
  103. data/lib/contrast/{core_extensions → extensions/ruby_core}/protect/applies_no_sqli_rule.rb +5 -3
  104. data/lib/contrast/{core_extensions → extensions/ruby_core}/protect/applies_path_traversal_rule.rb +31 -27
  105. data/lib/contrast/{core_extensions → extensions/ruby_core}/protect/applies_sqli_rule.rb +5 -3
  106. data/lib/contrast/{core_extensions → extensions/ruby_core}/protect/applies_xxe_rule.rb +9 -7
  107. data/lib/contrast/{core_extensions → extensions/ruby_core}/protect/kernel.rb +0 -0
  108. data/lib/contrast/{core_extensions → extensions/ruby_core}/protect/psych.rb +1 -1
  109. data/lib/contrast/{core_extensions → extensions/ruby_core}/thread.rb +0 -0
  110. data/lib/contrast/framework/base_support.rb +63 -0
  111. data/lib/contrast/framework/manager.rb +109 -0
  112. data/lib/contrast/framework/platform_version.rb +21 -0
  113. data/lib/contrast/framework/rails_support.rb +88 -0
  114. data/lib/contrast/framework/sinatra_application_helper.rb +49 -0
  115. data/lib/contrast/framework/sinatra_support.rb +94 -0
  116. data/lib/contrast/framework/view_technologies_descriptor.rb +20 -0
  117. data/lib/contrast/utils/assess/tracking_util.rb +2 -4
  118. data/lib/contrast/utils/class_util.rb +92 -37
  119. data/lib/contrast/utils/duck_utils.rb +59 -39
  120. data/lib/contrast/utils/environment_util.rb +5 -75
  121. data/lib/contrast/utils/freeze_util.rb +3 -7
  122. data/lib/contrast/utils/invalid_configuration_util.rb +5 -5
  123. data/lib/contrast/utils/job_servers_running.rb +39 -0
  124. data/lib/contrast/utils/ruby_ast_rewriter.rb +2 -2
  125. data/lib/contrast/utils/service_response_util.rb +0 -6
  126. data/lib/contrast/utils/sinatra_helper.rb +6 -0
  127. data/lib/contrast/utils/stack_trace_utils.rb +1 -1
  128. data/resources/assess/policy.json +74 -23
  129. data/resources/inventory/policy.json +1 -1
  130. data/resources/protect/policy.json +11 -9
  131. data/resources/rubocops/object/frozen_cop.rb +1 -1
  132. data/ruby-agent.gemspec +2 -0
  133. data/service_executables/VERSION +1 -1
  134. data/service_executables/linux/contrast-service +0 -0
  135. data/service_executables/mac/contrast-service +0 -0
  136. metadata +94 -57
  137. data/ext/cs__scope/cs__scope.c +0 -96
  138. data/ext/cs__scope/cs__scope.h +0 -33
  139. data/lib/contrast/agent/assess/class_reverter.rb +0 -82
  140. data/lib/contrast/agent/patching/policy/policy_unpatcher.rb +0 -28
  141. data/lib/contrast/core_extensions/module.rb +0 -42
  142. data/lib/contrast/core_extensions/object.rb +0 -27
  143. data/lib/contrast/rails_extensions/assess/action_controller_inheritance.rb +0 -48
  144. data/lib/contrast/rails_extensions/assess/active_record.rb +0 -32
  145. data/lib/contrast/rails_extensions/assess/active_record_named.rb +0 -61
  146. data/lib/contrast/rails_extensions/assess/configuration.rb +0 -26
  147. data/lib/contrast/rails_extensions/buffer.rb +0 -30
  148. data/lib/contrast/rails_extensions/rack.rb +0 -45
  149. data/lib/contrast/sinatra_extensions/assess/cookie.rb +0 -26
  150. data/lib/contrast/sinatra_extensions/inventory/sinatra_base.rb +0 -59
  151. data/lib/contrast/utils/operating_environment.rb +0 -38
  152. data/lib/contrast/utils/path_util.rb +0 -151
  153. data/lib/contrast/utils/scope_util.rb +0 -99
@@ -0,0 +1,30 @@
1
+ # Copyright (c) 2020 Contrast Security, Inc. See https://www.contrastsecurity.com/enduser-terms-0317a for more details.
2
+ # frozen_string_literal: true
3
+
4
+ cs__scoped_require 'contrast/agent/assess/policy/source_method'
5
+
6
+ module Contrast
7
+ module Agent
8
+ module Assess
9
+ module Policy
10
+ module SourceValidation
11
+ # Validator used to assert a CROSS_SITE tag is actually applicable to
12
+ # the given method before applying the tag to its target
13
+ module CrossSiteValidator
14
+ # prevent the application of a tag if it is from a source known to
15
+ # not apply a tag in a provided context.
16
+ # https://bitbucket.org/contrastsecurity/assess-specifications/src/master/rules/dataflow/reflected_xss.md
17
+ def self.valid? tag, source_type, source_name
18
+ return true unless tag == 'CROSS_SITE'
19
+ return false if source_type == Contrast::Agent::Assess::Policy::SourceMethod::HEADER_KEY_TYPE
20
+ return true unless source_type == Contrast::Agent::Assess::Policy::SourceMethod::HEADER_TYPE
21
+ return false unless source_name
22
+
23
+ source_name.casecmp?('referer')
24
+ end
25
+ end
26
+ end
27
+ end
28
+ end
29
+ end
30
+ end
@@ -0,0 +1,36 @@
1
+ # Copyright (c) 2020 Contrast Security, Inc. See https://www.contrastsecurity.com/enduser-terms-0317a for more details.
2
+ # frozen_string_literal: true
3
+
4
+ cs__scoped_require 'contrast/agent/assess/policy/source_validation/cross_site_validator'
5
+
6
+ module Contrast
7
+ module Agent
8
+ module Assess
9
+ module Policy
10
+ # Some of our sources require validation to prevent them from applying
11
+ # certain tags in a given context. This provides a single place from
12
+ # which those validations can be called.
13
+ module SourceValidation
14
+ VALIDATORS = [
15
+ Contrast::Agent::Assess::Policy::SourceValidation::CrossSiteValidator
16
+ ].cs__freeze
17
+
18
+ # Determines if the conditions in which this source was called are
19
+ # valid and should result in the application of a tag
20
+ #
21
+ # @param tag [String] the tag which the Source is attempting to apply
22
+ # @param source_type [String] the type of the source
23
+ # @param source_name [String] the name of the source
24
+ # @return [Boolean] if the conditions are valid for the application
25
+ # of a tag
26
+ def self.valid? tag, source_type, source_name
27
+ VALIDATORS.each do |validator|
28
+ return false unless validator.valid?(tag, source_type, source_name)
29
+ end
30
+ true
31
+ end
32
+ end
33
+ end
34
+ end
35
+ end
36
+ end
@@ -23,184 +23,269 @@ module Contrast
23
23
  # The level of TeamServer compliance our traces meet
24
24
  CURRENT_FINDING_VERSION = 2
25
25
 
26
- def self.settings
27
- Contrast::Agent::FeatureState.instance
28
- end
26
+ class << self
27
+ # This is called from within our woven proc. It will be called as if it
28
+ # were inline in the Rack application.
29
+ #
30
+ # @param trigger_node [Contrast::Agent::Assess::Policy::TriggerNode]
31
+ # the node that applies to the method being called
32
+ # @param object [Object] the Object on which the method was invoked
33
+ # @param ret [Object] the Return of the invoked method
34
+ # @param args [Array<Object>] the Arguments with which the method
35
+ # was invoked
36
+ def apply_trigger_rule trigger_node, object, ret, args
37
+ return if trigger_node.nil?
38
+
39
+ current_context = Contrast::Agent::REQUEST_TRACKER.current
40
+ return unless current_context&.analyze_request? && ASSESS.enabled?
29
41
 
30
- def self.apply_trigger_rule trigger_node, object, ret, args
31
- return if trigger_node.nil?
32
-
33
- current_context = Contrast::Agent::REQUEST_TRACKER.current
34
- return unless current_context&.analyze_request? && ASSESS.enabled?
35
-
36
- if trigger_node.sources&.any?
37
- trigger_node.sources.each do |marker|
38
- source = determine_source(marker, object, ret, args)
39
- TriggerMethod.cs__apply_trigger(current_context,
40
- trigger_node,
41
- source,
42
- object,
43
- ret,
44
- 1,
45
- *args)
42
+ if trigger_node.sources&.any?
43
+ trigger_node.sources.each do |marker|
44
+ source = determine_source(marker, object, ret, args)
45
+ apply_trigger(current_context,
46
+ trigger_node,
47
+ source,
48
+ object,
49
+ ret,
50
+ 1,
51
+ *args)
52
+ end
53
+ else
54
+ apply_trigger(current_context,
55
+ trigger_node,
56
+ nil,
57
+ object,
58
+ ret,
59
+ 1,
60
+ *args)
46
61
  end
47
- else
48
- TriggerMethod.cs__apply_trigger(current_context,
49
- trigger_node,
50
- nil,
51
- object,
52
- ret,
53
- 1,
54
- *args)
55
62
  end
56
- end
57
63
 
58
- def self.cs__apply_trigger context, trigger_node, source, object, ret, invoked, *args
59
- return unless context && trigger_node
60
- return if trigger_node.rule_disabled?
61
- return if trigger_node.dataflow? && source.nil?
62
-
63
- invoked += 1
64
- if trigger_node.regexp_rule?
65
- apply_regexp_rule(context, trigger_node, source, object, ret, invoked, *args)
66
- elsif trigger_node.custom_trigger?
67
- trigger_node.apply_custom_trigger(context, trigger_node, source, object, ret, invoked, *args)
68
- elsif trigger_node.dataflow?
69
- apply_dataflow_rule(context, trigger_node, source, object, ret, invoked, *args)
70
- else # trigger rule - just calling the method is dangerous
71
- build_finding(context, trigger_node, source, object, ret, invoked, *args)
64
+ def apply_eval_trigger context, trigger_node, source, object, ret, invoked, *args
65
+ apply_trigger(context, trigger_node, source, object, ret, invoked, *args)
72
66
  end
73
- rescue StandardError => e
74
- logger.warn(e, 'Unable to apply trigger.')
75
- end
76
67
 
77
- # Given the marker from the trigger_node (the pointer indicating the entity
78
- # from which the taint originated), return the entity on which this
79
- # trigger needs to operate.
80
- #
81
- # In an effort to speed up this lookup, we've changed the marker for
82
- # parameters to be implicit - if it is not a return or an object, it
83
- # must be a parameter, which we can reference either by index or by
84
- # name.
85
- def self.determine_source marker, object, ret, args
86
- case marker
87
- when Contrast::Utils::ObjectShare::RETURN_KEY
88
- ret
89
- when Contrast::Utils::ObjectShare::OBJECT_KEY
90
- object
91
- else # 'P'
92
- if marker.is_a?(Integer)
93
- args[marker]
94
- else
95
- arg = nil
96
- args.each do |search|
97
- next unless search.is_a?(Hash)
68
+ # This converts the source of the finding, and the events leading
69
+ # up to it into a Finding
70
+ #
71
+ # @param context [Contrast::Utils::ThreadTracker] the current request
72
+ # context
73
+ # @param trigger_node [Contrast::Agent::Assess::Policy::TriggerNode]
74
+ # the node to direct applying this trigger event
75
+ # @param source [Object] the source of the Trigger Event
76
+ # @param object [Object] the Object on which the method was invoked
77
+ # @param ret [Object] the Return of the invoked method
78
+ # @param invoked [Integer] the depth of this invocation from
79
+ # application code; often a lie.
80
+ # @param args [Array<Object>] the Arguments with which the method
81
+ # was invoked
82
+ # @return [Contrast::Api::Dtm::Finding, nil] the
83
+ # Contrast::Api::Dtm::Finding to send to TeamServer or nil if
84
+ # conditions were not met
85
+ def build_finding context, trigger_node, source, object, ret, invoked, *args
86
+ return unless Contrast::Agent::Assess::Policy::TriggerValidation.valid?(trigger_node, object, ret, args)
87
+
88
+ request = context.request
89
+ env = request.env
90
+ return if defined?(ActionController::Live) &&
91
+ env &&
92
+ env['action_controller.instance'].cs__class.included_modules.include?(ActionController::Live)
93
+
94
+ finding = Contrast::Api::Dtm::Finding.new
95
+ finding.rule_id = Contrast::Utils::StringUtils.protobuf_safe_string(trigger_node.rule_id)
96
+ finding.session_id = Contrast::Agent::FeatureState.instance.current_session_id
97
+ finding.version = CURRENT_FINDING_VERSION
98
+
99
+ build_from_source(finding, source)
100
+ trigger_event = Contrast::Agent::Assess::ContrastEvent.new(trigger_node, source, object, ret, args, invoked + 1).to_dtm_event
101
+ finding.events << trigger_event
102
+ build_hash(finding, source)
103
+ build_tags(context)
104
+ finding.routes << context.route if context.route
105
+ context.activity.findings << finding
106
+ logger.debug(nil, "Trigger #{ trigger_node.id } detected: #{ source.__id__ } triggered #{ trigger_node.rule_id }")
107
+ rescue StandardError => e
108
+ logger.error(e, "Unable to build a finding for #{ trigger_node.id }")
109
+ end
110
+
111
+ private
112
+
113
+ def settings
114
+ Contrast::Agent::FeatureState.instance
115
+ end
116
+
117
+ # This is our method that actually checks the taint on the object
118
+ # our trigger_node targets.
119
+ #
120
+ # @param context [Contrast::Utils::ThreadTracker] the current request
121
+ # context
122
+ # @param trigger_node [Contrast::Agent::Assess::Policy::TriggerNode]
123
+ # the node to direct applying this trigger event
124
+ # @param source [Object] the source of the Trigger Event
125
+ # @param object [Object] the Object on which the method was invoked
126
+ # @param ret [Object] the Return of the invoked method
127
+ # @param invoked [Integer] the depth of this invocation from
128
+ # application code; often a lie.
129
+ # @param args [Array<Object>] the Arguments with which the method
130
+ # was invoked
131
+ def apply_trigger context, trigger_node, source, object, ret, invoked, *args
132
+ return unless context && trigger_node
133
+ return if trigger_node.rule_disabled?
134
+ return if trigger_node.dataflow? && source.nil?
135
+
136
+ invoked += 1
137
+ if trigger_node.regexp_rule?
138
+ apply_regexp_rule(context, trigger_node, source, object, ret, invoked, *args)
139
+ elsif trigger_node.custom_trigger?
140
+ trigger_node.apply_custom_trigger(context, trigger_node, source, object, ret, invoked, *args)
141
+ elsif trigger_node.dataflow?
142
+ apply_dataflow_rule(context, trigger_node, source, object, ret, invoked, *args)
143
+ else # trigger rule - just calling the method is dangerous
144
+ build_finding(context, trigger_node, source, object, ret, invoked, *args)
145
+ end
146
+ rescue StandardError => e
147
+ logger.warn(e, 'Unable to apply trigger.')
148
+ end
98
149
 
99
- arg = search[marker]
100
- break if arg
150
+ # Given the marker from the trigger_node (the pointer indicating
151
+ # the entity from which the taint originated), return the entity on
152
+ # which this trigger needs to operate.
153
+ #
154
+ # In an effort to speed up this lookup, we've changed the marker
155
+ # for parameters to be implicit - if it is not a return or an
156
+ # object, it must be a parameter, which we can reference either by
157
+ # index or by name.
158
+ #
159
+ # @param marker [String] the source marker that indicates which
160
+ # Object
161
+ # @param object [Object] the Object on which the method was invoked
162
+ # @param ret [Object] the Return of the invoked method
163
+ # @param args [Array<Object>] the Arguments with which the method
164
+ # was invoked
165
+ # @return [Object] the literal object that this Trigger Event
166
+ # verifies
167
+ def determine_source marker, object, ret, args
168
+ case marker
169
+ when Contrast::Utils::ObjectShare::RETURN_KEY
170
+ ret
171
+ when Contrast::Utils::ObjectShare::OBJECT_KEY
172
+ object
173
+ else # 'P'
174
+ if marker.is_a?(Integer)
175
+ args[marker]
176
+ else
177
+ arg = nil
178
+ args.each do |search|
179
+ next unless search.is_a?(Hash)
180
+
181
+ arg = search[marker]
182
+ break if arg
183
+ end
184
+ arg
101
185
  end
102
- arg
103
186
  end
104
187
  end
105
- end
106
188
 
107
- def self.apply_regexp_rule context, trigger_node, source, object, ret, invoked, *args
108
- return unless source.is_a?(String)
109
- return if trigger_node.good_value && source.match?(trigger_node.good_value)
110
- return if trigger_node.bad_value && source !~ trigger_node.bad_value
189
+ # This is our method that actually checks the taint on the object
190
+ # our trigger_node targets for our Regexp based rules.
191
+ #
192
+ # @param context [Contrast::Utils::ThreadTracker] the current request
193
+ # context
194
+ # @param trigger_node [Contrast::Agent::Assess::Policy::TriggerNode]
195
+ # the node to direct applying this trigger event
196
+ # @param source [Object] the source of the Trigger Event
197
+ # @param object [Object] the Object on which the method was invoked
198
+ # @param ret [Object] the Return of the invoked method
199
+ # @param invoked [Integer] the depth of this invocation from
200
+ # application code; often a lie.
201
+ # @param args [Array<Object>] the Arguments with which the method
202
+ # was invoked
203
+ def apply_regexp_rule context, trigger_node, source, object, ret, invoked, *args
204
+ return unless source.is_a?(String)
205
+ return if trigger_node.good_value && source.match?(trigger_node.good_value)
206
+ return if trigger_node.bad_value && source !~ trigger_node.bad_value
111
207
 
112
- invoked += 1
113
- build_finding(context, trigger_node, source, object, ret, invoked, *args)
114
- end
208
+ invoked += 1
209
+ build_finding(context, trigger_node, source, object, ret, invoked, *args)
210
+ end
115
211
 
116
- def self.apply_dataflow_rule context, trigger_node, source, object, ret, invoked, *args
117
- return unless source
212
+ # This is our method that actually checks the taint on the object
213
+ # our trigger_node targets for our Dataflow based rules.
214
+ #
215
+ # @param context [Contrast::Utils::ThreadTracker] the current request
216
+ # context
217
+ # @param trigger_node [Contrast::Agent::Assess::Policy::TriggerNode]
218
+ # the node to direct applying this trigger event
219
+ # @param source [Object] the source of the Trigger Event
220
+ # @param object [Object] the Object on which the method was invoked
221
+ # @param ret [Object] the Return of the invoked method
222
+ # @param invoked [Integer] the depth of this invocation from
223
+ # application code; often a lie.
224
+ # @param args [Array<Object>] the Arguments with which the method
225
+ # was invoked
226
+ def apply_dataflow_rule context, trigger_node, source, object, ret, invoked, *args
227
+ return unless source
118
228
 
119
- invoked += 1
120
- if Contrast::Utils::DuckUtils.quacks_to?(source, :cs__properties)
121
- return unless source.cs__tracked?
122
- return unless trigger_node.violated?(source)
229
+ invoked += 1
230
+ if Contrast::Utils::DuckUtils.quacks_to?(source, :cs__properties)
231
+ return unless source.cs__tracked?
232
+ return unless trigger_node.violated?(source)
123
233
 
124
- build_finding(context, trigger_node, source, object, ret, invoked, *args)
125
- elsif Contrast::Utils::DuckUtils.quacks_like_tracked_hash?(source)
126
- invoked += 2 # the each & the block
127
- source.each_pair do |key, value|
128
- apply_dataflow_rule(context, trigger_node, key, object, ret, invoked, *args)
129
- apply_dataflow_rule(context, trigger_node, value, object, ret, invoked, *args)
130
- end
131
- elsif Contrast::Utils::DuckUtils.quacks_like_tracked_enumerable?(source)
132
- invoked += 2 # the each & the block
133
- source.each do |value|
134
- apply_dataflow_rule(context, trigger_node, value, object, ret, invoked, *args)
234
+ build_finding(context, trigger_node, source, object, ret, invoked, *args)
235
+ elsif Contrast::Utils::DuckUtils.iterable_hash?(source)
236
+ invoked += 2 # the each & the block
237
+ source.each_pair do |key, value|
238
+ apply_dataflow_rule(context, trigger_node, key, object, ret, invoked, *args)
239
+ apply_dataflow_rule(context, trigger_node, value, object, ret, invoked, *args)
240
+ end
241
+ elsif Contrast::Utils::DuckUtils.iterable_enumerable?(source)
242
+ invoked += 2 # the each & the block
243
+ source.each do |value|
244
+ apply_dataflow_rule(context, trigger_node, value, object, ret, invoked, *args)
245
+ end
246
+ else
247
+ logger.warn(
248
+ nil,
249
+ "Target is a #{ source.cs__class.name } -- not sure how to inspect: #{ trigger_node.inspect }")
250
+ logger.debug(nil, source.to_s[0..99])
135
251
  end
136
- else
137
- logger.warn(
138
- nil,
139
- "Target is a #{ source.cs__class.name } -- not sure how to inspect: #{ trigger_node.inspect }")
140
- logger.debug(nil, source.to_s[0..99])
141
252
  end
142
- end
143
253
 
144
- def self.build_finding context, trigger_node, source, object, ret, invoked, *args
145
- return unless Contrast::Agent::Assess::Policy::TriggerValidation.valid?(trigger_node, object, ret, args)
146
-
147
- request = context.request
148
- env = request.env
149
- return if defined?(ActionController::Live) &&
150
- env &&
151
- env['action_controller.instance'].cs__class.included_modules.include?(ActionController::Live)
152
-
153
- finding = Contrast::Api::Dtm::Finding.new
154
- finding.rule_id = Contrast::Utils::StringUtils.protobuf_safe_string(trigger_node.rule_id)
155
- finding.session_id = Contrast::Agent::FeatureState.instance.current_session_id
156
- finding.version = CURRENT_FINDING_VERSION
157
-
158
- build_from_source(finding, source)
159
- trigger_event = Contrast::Agent::Assess::ContrastEvent.new(trigger_node, source, object, ret, args, invoked + 1).to_dtm_event
160
- finding.events << trigger_event
161
- build_hash(finding, source)
162
- build_tags(context)
163
- finding.routes << context.route if context.route
164
- context.activity.findings << finding
165
- logger.debug(nil, "Trigger #{ trigger_node.id } detected: #{ source.__id__ } triggered #{ trigger_node.rule_id }")
166
- rescue StandardError => e
167
- logger.error(e, "Unable to build a finding for #{ trigger_node.id }")
168
- end
254
+ def build_from_source finding, source
255
+ return unless source
256
+ return unless Contrast::Utils::DuckUtils.quacks_to?(
257
+ source,
258
+ :cs__properties)
259
+ return unless source.cs__properties
169
260
 
170
- def self.build_from_source finding, source
171
- return unless source
172
- return unless Contrast::Utils::DuckUtils.quacks_to?(
173
- source,
174
- :cs__properties)
175
- return unless source.cs__properties
176
-
177
- # events could technically be nil, but we would have failed
178
- # the rule check before getting here. not worth the nil check
179
- source.cs__properties.events.each do |event|
180
- finding.events << event.to_dtm_event
181
- end
261
+ # events could technically be nil, but we would have failed
262
+ # the rule check before getting here. not worth the nil check
263
+ source.cs__properties.events.each do |event|
264
+ finding.events << event.to_dtm_event
265
+ end
182
266
 
183
- # Google::Protobuf::Map doesn't support merge!, so we have to do this
184
- # long form
185
- source_props = source.cs__properties.properties
186
- return unless source_props
267
+ # Google::Protobuf::Map doesn't support merge!, so we have to do this
268
+ # long form
269
+ source_props = source.cs__properties.properties
270
+ return unless source_props
187
271
 
188
- source_props.each_pair do |key, value|
189
- key = Contrast::Utils::StringUtils.force_utf8(key)
190
- finding.properties[key] = Contrast::Utils::StringUtils.force_utf8(value)
272
+ source_props.each_pair do |key, value|
273
+ key = Contrast::Utils::StringUtils.force_utf8(key)
274
+ finding.properties[key] = Contrast::Utils::StringUtils.force_utf8(value)
275
+ end
191
276
  end
192
- end
193
277
 
194
- def self.build_hash finding, source
195
- hash_code = Contrast::Utils::HashDigest.generate_event_hash(finding, source)
196
- finding.hash_code = Contrast::Utils::StringUtils.force_utf8(hash_code)
197
- finding.preflight = Contrast::Utils::PreflightUtil.create_preflight(finding)
198
- end
278
+ def build_hash finding, source
279
+ hash_code = Contrast::Utils::HashDigest.generate_event_hash(finding, source)
280
+ finding.hash_code = Contrast::Utils::StringUtils.force_utf8(hash_code)
281
+ finding.preflight = Contrast::Utils::PreflightUtil.create_preflight(finding)
282
+ end
199
283
 
200
- def self.build_tags context
201
- return unless ASSESS.tags
284
+ def build_tags context
285
+ return unless ASSESS.tags
202
286
 
203
- context.activity.finding_tags = Contrast::Utils::StringUtils.force_utf8(ASSESS.tags)
287
+ context.activity.finding_tags = Contrast::Utils::StringUtils.force_utf8(ASSESS.tags)
288
+ end
204
289
  end
205
290
  end
206
291
  end