contrast-agent 6.2.0 → 6.5.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (209) hide show
  1. checksums.yaml +4 -4
  2. data/.gitignore +0 -3
  3. data/.simplecov +1 -0
  4. data/Rakefile +0 -27
  5. data/ext/cs__assess_basic_object/cs__assess_basic_object.c +7 -5
  6. data/ext/cs__assess_kernel/cs__assess_kernel.c +14 -3
  7. data/ext/cs__assess_kernel/cs__assess_kernel.h +2 -0
  8. data/ext/cs__assess_marshal_module/cs__assess_marshal_module.c +10 -3
  9. data/ext/cs__assess_marshal_module/cs__assess_marshal_module.h +2 -1
  10. data/ext/cs__assess_regexp/cs__assess_regexp.c +9 -7
  11. data/ext/{cs__assess_string_interpolation26/cs__assess_string_interpolation26.c → cs__assess_string_interpolation/cs__assess_string_interpolation.c} +14 -3
  12. data/ext/{cs__assess_string_interpolation26/cs__assess_string_interpolation26.h → cs__assess_string_interpolation/cs__assess_string_interpolation.h} +1 -1
  13. data/ext/{cs__assess_string_interpolation26 → cs__assess_string_interpolation}/extconf.rb +0 -0
  14. data/ext/cs__common/cs__common.c +5 -4
  15. data/ext/cs__contrast_patch/cs__contrast_patch.c +17 -11
  16. data/lib/contrast/agent/assess/events/source_event.rb +16 -12
  17. data/lib/contrast/agent/assess/finalizers/hash.rb +1 -0
  18. data/lib/contrast/agent/assess/policy/policy_node.rb +6 -0
  19. data/lib/contrast/agent/assess/policy/propagation_method.rb +8 -42
  20. data/lib/contrast/agent/assess/policy/propagation_node.rb +8 -0
  21. data/lib/contrast/agent/assess/policy/propagator/base.rb +2 -0
  22. data/lib/contrast/agent/assess/policy/propagator/custom.rb +4 -0
  23. data/lib/contrast/agent/assess/policy/propagator/database_write.rb +5 -0
  24. data/lib/contrast/agent/assess/policy/propagator/split.rb +3 -0
  25. data/lib/contrast/agent/assess/policy/source_method.rb +7 -47
  26. data/lib/contrast/agent/assess/policy/source_node.rb +1 -0
  27. data/lib/contrast/agent/assess/policy/trigger_method.rb +9 -3
  28. data/lib/contrast/agent/assess/policy/trigger_node.rb +8 -0
  29. data/lib/contrast/agent/assess/property/evented.rb +4 -18
  30. data/lib/contrast/agent/assess/tag.rb +19 -0
  31. data/lib/contrast/agent/assess/tracker.rb +12 -0
  32. data/lib/contrast/agent/at_exit_hook.rb +8 -8
  33. data/lib/contrast/agent/inventory/database_config.rb +6 -3
  34. data/lib/contrast/agent/inventory/dependency_analysis.rb +5 -4
  35. data/lib/contrast/agent/inventory/dependency_usage_analysis.rb +11 -11
  36. data/lib/contrast/agent/inventory/policy/datastores.rb +1 -1
  37. data/lib/contrast/agent/inventory/policy/policy.rb +1 -1
  38. data/lib/contrast/agent/middleware.rb +4 -0
  39. data/lib/contrast/agent/patching/policy/after_load_patcher.rb +27 -2
  40. data/lib/contrast/agent/patching/policy/method_policy.rb +3 -3
  41. data/lib/contrast/agent/patching/policy/policy.rb +5 -0
  42. data/lib/contrast/agent/patching/policy/policy_node.rb +6 -0
  43. data/lib/contrast/agent/patching/policy/trigger_node.rb +3 -0
  44. data/lib/contrast/agent/protect/policy/applies_deserialization_rule.rb +3 -4
  45. data/lib/contrast/agent/protect/policy/applies_path_traversal_rule.rb +1 -0
  46. data/lib/contrast/agent/protect/policy/rule_applicator.rb +2 -2
  47. data/lib/contrast/agent/protect/rule/base.rb +1 -0
  48. data/lib/contrast/agent/protect/rule/no_sqli.rb +2 -0
  49. data/lib/contrast/agent/reporting/reporter.rb +32 -7
  50. data/lib/contrast/agent/reporting/reporter_heartbeat.rb +22 -18
  51. data/lib/contrast/agent/reporting/reporting_events/application_defend_activity.rb +17 -21
  52. data/lib/contrast/agent/reporting/reporting_events/application_defend_attack_sample.rb +1 -1
  53. data/lib/contrast/agent/reporting/reporting_events/application_defend_attack_sample_activity.rb +26 -3
  54. data/lib/contrast/agent/reporting/reporting_events/application_update.rb +5 -24
  55. data/lib/contrast/agent/reporting/reporting_events/architecture_component.rb +8 -1
  56. data/lib/contrast/agent/reporting/reporting_events/discovered_route.rb +8 -1
  57. data/lib/contrast/agent/reporting/reporting_events/finding.rb +7 -1
  58. data/lib/contrast/agent/reporting/reporting_events/finding_event.rb +10 -1
  59. data/lib/contrast/agent/reporting/reporting_events/finding_event_object.rb +11 -1
  60. data/lib/contrast/agent/reporting/reporting_events/finding_event_parent_object.rb +11 -1
  61. data/lib/contrast/agent/reporting/reporting_events/finding_event_property.rb +12 -1
  62. data/lib/contrast/agent/reporting/reporting_events/finding_event_signature.rb +10 -1
  63. data/lib/contrast/agent/reporting/reporting_events/finding_event_source.rb +11 -1
  64. data/lib/contrast/agent/reporting/reporting_events/finding_event_stack.rb +11 -1
  65. data/lib/contrast/agent/reporting/reporting_events/finding_event_taint_range.rb +11 -1
  66. data/lib/contrast/agent/reporting/reporting_events/finding_request.rb +11 -1
  67. data/lib/contrast/agent/reporting/reporting_events/library_discovery.rb +29 -32
  68. data/lib/contrast/agent/reporting/reporting_events/library_usage_observation.rb +13 -1
  69. data/lib/contrast/agent/reporting/reporting_events/observed_library_usage.rb +11 -8
  70. data/lib/contrast/agent/reporting/reporting_events/observed_route.rb +12 -5
  71. data/lib/contrast/agent/reporting/reporting_events/preflight_message.rb +8 -1
  72. data/lib/contrast/agent/reporting/reporting_events/reporting_event.rb +9 -1
  73. data/lib/contrast/agent/reporting/reporting_events/route_discovery.rb +10 -1
  74. data/lib/contrast/agent/reporting/reporting_events/route_discovery_observation.rb +11 -4
  75. data/lib/contrast/agent/reporting/reporting_events/server_activity.rb +0 -8
  76. data/lib/contrast/agent/reporting/reporting_utilities/audit.rb +1 -4
  77. data/lib/contrast/agent/reporting/reporting_utilities/dtm_message.rb +0 -22
  78. data/lib/contrast/agent/reporting/reporting_utilities/reporter_client.rb +1 -3
  79. data/lib/contrast/agent/reporting/reporting_utilities/reporter_client_utils.rb +1 -11
  80. data/lib/contrast/agent/request.rb +5 -7
  81. data/lib/contrast/agent/request_context.rb +16 -17
  82. data/lib/contrast/agent/request_context_extend.rb +8 -9
  83. data/lib/contrast/agent/request_handler.rb +9 -38
  84. data/lib/contrast/agent/rule_set.rb +4 -0
  85. data/lib/contrast/agent/service_heartbeat.rb +3 -4
  86. data/lib/contrast/agent/static_analysis.rb +7 -12
  87. data/lib/contrast/agent/telemetry/base.rb +35 -35
  88. data/lib/contrast/agent/telemetry/events/exceptions/telemetry_exception_base.rb +2 -0
  89. data/lib/contrast/agent/telemetry/events/exceptions/telemetry_exception_event.rb +2 -0
  90. data/lib/contrast/agent/telemetry/events/exceptions/telemetry_exception_message.rb +5 -2
  91. data/lib/contrast/agent/telemetry/events/exceptions/telemetry_exception_message_exception.rb +3 -0
  92. data/lib/contrast/agent/telemetry/events/exceptions/telemetry_exception_stack_frame.rb +3 -0
  93. data/lib/contrast/agent/telemetry/events/exceptions/telemetry_exceptions.rb +0 -1
  94. data/lib/contrast/agent/thread_watcher.rb +1 -4
  95. data/lib/contrast/agent/version.rb +1 -1
  96. data/lib/contrast/agent/worker_thread.rb +10 -0
  97. data/lib/contrast/api/communication/socket.rb +1 -0
  98. data/lib/contrast/api/decorators/message.rb +0 -6
  99. data/lib/contrast/api/decorators.rb +0 -2
  100. data/lib/contrast/api/dtm.pb.rb +1 -1
  101. data/lib/contrast/api/settings.pb.rb +1 -1
  102. data/lib/contrast/components/agent.rb +51 -13
  103. data/lib/contrast/components/assess.rb +16 -6
  104. data/lib/contrast/components/config.rb +18 -2
  105. data/lib/contrast/components/contrast_service.rb +1 -1
  106. data/lib/contrast/components/heap_dump.rb +51 -1
  107. data/lib/contrast/components/inventory.rb +19 -13
  108. data/lib/contrast/components/logger.rb +18 -0
  109. data/lib/contrast/config/assess_configuration.rb +28 -0
  110. data/lib/contrast/config/base_configuration.rb +8 -15
  111. data/lib/contrast/config/root_configuration.rb +12 -8
  112. data/lib/contrast/config/ruby_configuration.rb +2 -9
  113. data/lib/contrast/config/service_configuration.rb +4 -4
  114. data/lib/contrast/config.rb +0 -6
  115. data/lib/contrast/configuration.rb +0 -2
  116. data/lib/contrast/extension/assess/eval_trigger.rb +0 -4
  117. data/lib/contrast/extension/assess/hash.rb +3 -2
  118. data/lib/contrast/extension/assess/kernel.rb +22 -0
  119. data/lib/contrast/extension/assess/marshal.rb +16 -0
  120. data/lib/contrast/extension/assess/string.rb +21 -20
  121. data/lib/contrast/extension/object.rb +19 -0
  122. data/lib/contrast/framework/base_support.rb +8 -0
  123. data/lib/contrast/framework/manager.rb +6 -20
  124. data/lib/contrast/framework/manager_extend.rb +0 -1
  125. data/lib/contrast/framework/rails/patch/action_controller_live_buffer.rb +11 -16
  126. data/lib/contrast/framework/rails/support.rb +4 -1
  127. data/lib/contrast/logger/aliased_logging.rb +2 -0
  128. data/lib/contrast/logger/log.rb +2 -1
  129. data/lib/contrast/utils/assess/event_limit_utils.rb +96 -0
  130. data/lib/contrast/utils/assess/propagation_method_utils.rb +27 -7
  131. data/lib/contrast/utils/assess/source_method_utils.rb +0 -9
  132. data/lib/contrast/utils/log_utils.rb +2 -2
  133. data/lib/contrast/utils/lru_cache.rb +3 -0
  134. data/lib/contrast/utils/middleware_utils.rb +2 -0
  135. data/lib/contrast/utils/patching/policy/patch_utils.rb +6 -23
  136. data/lib/contrast/utils/telemetry_client.rb +7 -7
  137. data/lib/contrast.rb +37 -18
  138. data/lib/protobuf/code_generator.rb +129 -0
  139. data/lib/protobuf/decoder.rb +28 -0
  140. data/lib/protobuf/deprecation.rb +117 -0
  141. data/lib/protobuf/descriptors/google/protobuf/compiler/plugin.pb.rb +79 -0
  142. data/lib/protobuf/descriptors/google/protobuf/descriptor.pb.rb +360 -0
  143. data/lib/protobuf/descriptors.rb +3 -0
  144. data/lib/protobuf/encoder.rb +11 -0
  145. data/lib/protobuf/enum.rb +365 -0
  146. data/lib/protobuf/exceptions.rb +9 -0
  147. data/lib/protobuf/field/base_field.rb +380 -0
  148. data/lib/protobuf/field/base_field_object_definitions.rb +504 -0
  149. data/lib/protobuf/field/bool_field.rb +64 -0
  150. data/lib/protobuf/field/bytes_field.rb +67 -0
  151. data/lib/protobuf/field/double_field.rb +25 -0
  152. data/lib/protobuf/field/enum_field.rb +56 -0
  153. data/lib/protobuf/field/field_array.rb +102 -0
  154. data/lib/protobuf/field/field_hash.rb +122 -0
  155. data/lib/protobuf/field/fixed32_field.rb +25 -0
  156. data/lib/protobuf/field/fixed64_field.rb +28 -0
  157. data/lib/protobuf/field/float_field.rb +43 -0
  158. data/lib/protobuf/field/int32_field.rb +21 -0
  159. data/lib/protobuf/field/int64_field.rb +34 -0
  160. data/lib/protobuf/field/integer_field.rb +23 -0
  161. data/lib/protobuf/field/message_field.rb +51 -0
  162. data/lib/protobuf/field/sfixed32_field.rb +27 -0
  163. data/lib/protobuf/field/sfixed64_field.rb +28 -0
  164. data/lib/protobuf/field/signed_integer_field.rb +29 -0
  165. data/lib/protobuf/field/sint32_field.rb +21 -0
  166. data/lib/protobuf/field/sint64_field.rb +21 -0
  167. data/lib/protobuf/field/string_field.rb +51 -0
  168. data/lib/protobuf/field/uint32_field.rb +21 -0
  169. data/lib/protobuf/field/uint64_field.rb +21 -0
  170. data/lib/protobuf/field/varint_field.rb +77 -0
  171. data/lib/protobuf/field.rb +74 -0
  172. data/lib/protobuf/generators/base.rb +85 -0
  173. data/lib/protobuf/generators/enum_generator.rb +39 -0
  174. data/lib/protobuf/generators/extension_generator.rb +27 -0
  175. data/lib/protobuf/generators/field_generator.rb +193 -0
  176. data/lib/protobuf/generators/file_generator.rb +262 -0
  177. data/lib/protobuf/generators/group_generator.rb +122 -0
  178. data/lib/protobuf/generators/message_generator.rb +104 -0
  179. data/lib/protobuf/generators/option_generator.rb +17 -0
  180. data/lib/protobuf/generators/printable.rb +160 -0
  181. data/lib/protobuf/generators/service_generator.rb +50 -0
  182. data/lib/protobuf/lifecycle.rb +33 -0
  183. data/lib/protobuf/logging.rb +39 -0
  184. data/lib/protobuf/message/fields.rb +233 -0
  185. data/lib/protobuf/message/serialization.rb +85 -0
  186. data/lib/protobuf/message.rb +241 -0
  187. data/lib/protobuf/optionable.rb +72 -0
  188. data/lib/protobuf/tasks/compile.rake +80 -0
  189. data/lib/protobuf/tasks.rb +1 -0
  190. data/lib/protobuf/varint.rb +20 -0
  191. data/lib/protobuf/varint_pure.rb +31 -0
  192. data/lib/protobuf/version.rb +3 -0
  193. data/lib/protobuf/wire_type.rb +10 -0
  194. data/lib/protobuf.rb +91 -0
  195. data/proto/dynamic_discovery.proto +46 -0
  196. data/proto/google/protobuf/compiler/plugin.proto +183 -0
  197. data/proto/google/protobuf/descriptor.proto +911 -0
  198. data/proto/rpc.proto +71 -0
  199. data/resources/assess/policy.json +6 -23
  200. data/ruby-agent.gemspec +4 -2
  201. metadata +122 -33
  202. data/lib/contrast/agent/telemetry/events/exceptions/telemetry_exceptions_report.rb +0 -30
  203. data/lib/contrast/api/decorators/application_update.rb +0 -44
  204. data/lib/contrast/api/decorators/library.rb +0 -56
  205. data/lib/contrast/config/agent_configuration.rb +0 -63
  206. data/lib/contrast/config/heap_dump_configuration.rb +0 -59
  207. data/lib/contrast/config/inventory_configuration.rb +0 -33
  208. data/lib/contrast/config/logger_configuration.rb +0 -26
  209. data/lib/contrast/framework/platform_version.rb +0 -22
@@ -58,6 +58,22 @@ module Contrast
58
58
  end
59
59
  end
60
60
  end
61
+
62
+ # Used for aliasing
63
+ module ContrastMarshal
64
+ def cs__marshal_load source
65
+ # Do the protect
66
+ Contrast::Extension::Assess::MarshalPropagator.cs__load_protect(source) if source
67
+ # call the original
68
+ result = Marshal.load(source) # rubocop:disable Security/MarshalLoad
69
+ # Do the assess
70
+ tracked = Contrast::Agent::Assess::Tracker::PROPERTIES_HASH.tracked?(source) if source
71
+ skip = Contrast::Agent::Patching::Policy::Patch.skip_assess_analysis? if tracked
72
+ Contrast::Extension::Assess::MarshalPropagator.cs__load_assess(source, result) if skip
73
+ # return original
74
+ result
75
+ end
76
+ end
61
77
  end
62
78
  end
63
79
  end
@@ -31,31 +31,32 @@ module Contrast
31
31
  INTERPOLATION_NODE = Contrast::Agent::Assess::Policy::PropagationNode.new(NODE_HASH)
32
32
 
33
33
  class << self
34
+ # We call this method from C, and the Scope check is happening there. If we are in
35
+ # Contrast Scope the method won't be invoked.
36
+ #
37
+ # @param inputs [Array<String>] Inputs for interpolation.
38
+ # @param result [String] The result from the interpolation.
34
39
  def track_interpolation inputs, result
35
40
  return unless ::Contrast::AGENT.interpolation_enabled?
36
- return if in_contrast_scope?
37
41
  return unless inputs.any? { |input| Contrast::Agent::Assess::Tracker.tracked?(input) }
42
+ return unless (properties = Contrast::Agent::Assess::Tracker.properties!(result))
38
43
 
39
- with_contrast_scope do
40
- return unless (properties = Contrast::Agent::Assess::Tracker.properties!(result))
41
-
42
- parent_events = []
43
- offset = 0
44
- inputs.each do |input|
45
- properties.copy_from(input, result, offset)
46
- add_dynamic_sources_info(input, result)
47
- offset += input.length
48
- parent_event = Contrast::Agent::Assess::Tracker.properties(input)&.event
49
- parent_events << parent_event if parent_event
50
- end
51
- event_data = Contrast::Agent::Assess::Events::EventData.new(INTERPOLATION_NODE,
52
- result,
53
- inputs,
54
- result,
55
- inputs)
56
- properties.build_event(event_data)
57
- properties.event.instance_variable_set(:@_parent_events, parent_events)
44
+ parent_events = []
45
+ offset = 0
46
+ inputs.each do |input|
47
+ properties.copy_from(input, result, offset)
48
+ add_dynamic_sources_info(input, result)
49
+ offset += input.length
50
+ parent_event = Contrast::Agent::Assess::Tracker.properties(input)&.event
51
+ parent_events << parent_event if parent_event
58
52
  end
53
+ event_data = Contrast::Agent::Assess::Events::EventData.new(INTERPOLATION_NODE,
54
+ result,
55
+ inputs,
56
+ result,
57
+ inputs)
58
+ properties.build_event(event_data)
59
+ properties.event.instance_variable_set(:@_parent_events, parent_events)
59
60
  rescue StandardError => e
60
61
  logger.error('Unable to track interpolation', e)
61
62
  end
@@ -0,0 +1,19 @@
1
+ # Copyright (c) 2022 Contrast Security, Inc. See https://www.contrastsecurity.com/enduser-terms-0317a for more details.
2
+ # frozen_string_literal: true
3
+
4
+ # Some developers override various methods on Object, which can often involve
5
+ # changing expected method parity/behavior which in turn prevents us from being
6
+ # able to reliably use affected methods.
7
+ # We alias these method so that we always have access to them.
8
+ #
9
+ # Because we use these methods in constructing classes (e.g., calling #freeze
10
+ # on constants within class definitions) we do this aliasing ASAP.
11
+ class Object
12
+ alias_method :cs__class, :class
13
+ alias_method :cs__freeze, :freeze
14
+ alias_method :cs__frozen?, :frozen?
15
+ alias_method :cs__is_a?, :is_a?
16
+ alias_method :cs__method, :method
17
+ alias_method :cs__respond_to?, :respond_to?
18
+ alias_method :cs__singleton_class, :singleton_class
19
+ end
@@ -8,18 +8,22 @@ module Contrast
8
8
  # The API for all subclasses to implement to correctly support a given framework
9
9
  module BaseSupport
10
10
  # The top level module name used by the framework
11
+ # @raise [NoMethodError] raises error if subclass does not implement this method
11
12
  def detection_class
12
13
  raise(NoMethodError('Subclasses of BaseSupport should implement this method'))
13
14
  end
14
15
 
16
+ # @raise [NoMethodError] raises error if subclass does not implement this method
15
17
  def version
16
18
  raise(NoMethodError('Subclasses of BaseSupport should implement this method'))
17
19
  end
18
20
 
21
+ # @raise [NoMethodError] raises error if subclass does not implement this method
19
22
  def application_name
20
23
  raise(NoMethodError, 'Subclasses of BaseSupport should implement this method')
21
24
  end
22
25
 
26
+ # @raise [NoMethodError] raises error if subclass does not implement this method
23
27
  def server_type
24
28
  raise(NoMethodError, 'Subclasses of BaseSupport should implement this method')
25
29
  end
@@ -27,18 +31,22 @@ module Contrast
27
31
  # Find all the predefined routes for this application
28
32
  #
29
33
  # @return [Array<Contrast::Agent::Reporting::DiscoveredRoute>]
34
+ # @raise [NoMethodError] raises error if subclass does not implement this method
30
35
  def collect_routes
31
36
  raise(NoMethodError, 'Subclasses of BaseSupport should implement this method')
32
37
  end
33
38
 
39
+ # @raise [NoMethodError] raises error if subclass does not implement this method
34
40
  def current_route_coverage
35
41
  raise(NoMethodError, 'Subclasses of BaseSupport should implement this method')
36
42
  end
37
43
 
44
+ # @raise [NoMethodError] raises error if subclass does not implement this method
38
45
  def current_route
39
46
  raise(NoMethodError, 'Subclasses of BaseSupport should implement this method')
40
47
  end
41
48
 
49
+ # @raise [NoMethodError] raises error if subclass does not implement this method
42
50
  def retrieve_request _env
43
51
  raise(NoMethodError, 'Subclasses of BaseSupport should implement this method')
44
52
  end
@@ -7,7 +7,6 @@ require 'contrast/components/logger'
7
7
  require 'contrast/extension/module'
8
8
  require 'contrast/framework/grape/support'
9
9
  require 'contrast/framework/manager_extend'
10
- require 'contrast/framework/platform_version'
11
10
  require 'contrast/framework/rack/support'
12
11
  require 'contrast/framework/rails/support'
13
12
  require 'contrast/framework/sinatra/support'
@@ -64,16 +63,6 @@ module Contrast
64
63
  routes_for_all_frameworks
65
64
  end
66
65
 
67
- def platform_version
68
- framework_version = first_framework_result(:version, '')
69
-
70
- Contrast::Framework::PlatformVersion.from_string(framework_version)
71
- end
72
-
73
- def platform_version_string
74
- first_framework_result(:version, '')
75
- end
76
-
77
66
  def server_type
78
67
  first_framework_result(:server_type, 'rack')
79
68
  end
@@ -157,16 +146,13 @@ module Contrast
157
146
  next unless module_name == framework.detection_class
158
147
 
159
148
  @_frameworks << framework
160
- # Report the registered routes of that framework now that we know we need to find them
161
- # TODO: RUBY-1438 -- remove and build ReportingEvent directly
162
- app_update_msg = Contrast::Api::Dtm::ApplicationUpdate.build
163
- if Contrast::Agent.reporter
164
- report = Contrast::Agent::Reporting::DtmMessage.dtm_to_event(app_update_msg)
165
- Contrast::Agent.reporter.send_event(report)
166
- else
167
- Contrast::Agent.messaging_queue.send_event_eventually(app_update_msg)
149
+ report = Contrast::Agent::Reporting::ApplicationUpdate.new
150
+ # This convert here is left as it'll be easier to be replaced when the Library is being changed
151
+ report.libraries = Contrast::Agent::Inventory::DependencyAnalysis.instance.library_pb_list
152
+ [report, Contrast::Agent::Reporting::ApplicationInventory.new].each do |e|
153
+ Contrast::Agent.reporter.send_event(e)
168
154
  end
169
- Contrast::Agent.reporter.send_event(Contrast::Agent::Reporting::ApplicationInventory.new)
155
+
170
156
  logger.info('Framework detected after initialization. Enabling support.',
171
157
  framework: framework.detection_class,
172
158
  frameworks: @_frameworks)
@@ -3,7 +3,6 @@
3
3
 
4
4
  require 'contrast/components/logger'
5
5
  require 'contrast/extension/module'
6
- require 'contrast/framework/platform_version'
7
6
  require 'contrast/framework/rack/support'
8
7
  require 'contrast/framework/rails/support'
9
8
  require 'contrast/framework/grape/support'
@@ -1,6 +1,8 @@
1
1
  # Copyright (c) 2022 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/agent/inventory/dependency_usage_analysis'
5
+
4
6
  module Contrast
5
7
  module Framework
6
8
  module Rails
@@ -9,27 +11,20 @@ module Contrast
9
11
  # event on streamed responses.
10
12
  module ActionControllerLiveBuffer
11
13
  class << self
12
- # TODO: RUBY-1353
13
- # TODO: RUBY-1355
14
- # TODO: RUBY-1357
15
- # TODO: RUBY-1357
16
14
  def send_messages
17
15
  return unless (context = Contrast::Agent::REQUEST_TRACKER.current)
18
16
 
17
+ [
18
+ context.observed_route
19
+ ].each do |event|
20
+ Contrast::Agent.reporter&.send_event_immediately(event)
21
+ end
22
+
19
23
  if Contrast::Agent::Reporter.enabled?
20
- [
21
- context.new_observed_route,
22
- context.observed_library_usage,
23
- Contrast::Agent::Reporting::DtmMessage.dtm_to_event(context.server_activity),
24
- Contrast::Agent::Reporting::DtmMessage.dtm_to_event(context.activity)
25
- ].each do |event|
26
- Contrast::Agent.reporter&.send_event_immediately(event)
27
- end
24
+ event = Contrast::Agent::Reporting::DtmMessage.dtm_to_event(context.activity)
25
+ Contrast::Agent.reporter&.send_event_immediately(event)
28
26
  else
29
- Contrast::Agent.reporter.send_event_immediately(context.observed_library_usage)
30
- [context.server_activity, context.activity, context.observed_route].each do |msg|
31
- Contrast::Agent.messaging_queue&.send_event_immediately(msg)
32
- end
27
+ Contrast::Agent.messaging_queue&.send_event_immediately(context.activity)
33
28
  end
34
29
  end
35
30
 
@@ -135,8 +135,11 @@ module Contrast
135
135
  # @return [bool] whether the router is an engine or not.
136
136
  def engine_route? route
137
137
  return false unless route&.app&.app
138
+ return false unless route.app.is_a?(::ActionDispatch::Routing::Mapper::Constraints) ||
139
+ route.app.is_a?(::ActionDispatch::Routing::RouteSet::Dispatcher)
138
140
 
139
- route.app.is_a?(::ActionDispatch::Routing::Mapper::Constraints) && route.app.app < ::Rails::Engine
141
+ clazz = route.app.app.is_a?(Class) ? route.app.app : route.app.app.cs__class
142
+ clazz < ::Rails::Engine
140
143
  end
141
144
 
142
145
  # Recursively get final route traversing engines as required. Because this can only be called once, we store
@@ -88,6 +88,8 @@ module Contrast
88
88
  end
89
89
  message = Contrast::Agent::Telemetry::TelemetryException::Message.build(tags, [message_exception])
90
90
  Contrast::Agent::Telemetry::TelemetryException::Event.new(message)
91
+ rescue ArgumentError => e
92
+ debug('TelemetryException failed from aliased logging with: ', e)
91
93
  end
92
94
 
93
95
  def get_stack_trace type
@@ -134,7 +134,8 @@ module Contrast
134
134
 
135
135
  enable_trace_timing if current_level_const == ::Ougai::Logging::TRACE
136
136
 
137
- @_logger = build(path: current_path, level_const: current_level_const)
137
+ progname = Contrast::CONFIG.root.agent.logger.progname
138
+ @_logger = build(path: current_path, level_const: current_level_const, progname: progname)
138
139
  # If we're logging to a new path, then let's start it w/ our helpful
139
140
  # data gathering messages
140
141
  log_update if path_change
@@ -0,0 +1,96 @@
1
+ # Copyright (c) 2022 Contrast Security, Inc. See https://www.contrastsecurity.com/enduser-terms-0317a for more details.
2
+ # frozen_string_literal: true
3
+
4
+ require 'contrast/components/logger'
5
+
6
+ module Contrast
7
+ module Utils
8
+ module Assess
9
+ # EventLimitUtils is used to check and validate the number of source, propagation, or trigger events collected
10
+ # during the reporting time frame
11
+ module EventLimitUtils
12
+ include Contrast::Components::Logger::InstanceMethods
13
+ # Checks to see if the event limit for the policy type has been met or exceeded
14
+ # @param method_policy [Contrast::Agent::Patching::Policy::MethodPolicy] method to check for event limit
15
+ def event_limit? method_policy
16
+ return false unless (context = Contrast::Agent::REQUEST_TRACKER.current)
17
+
18
+ if method_policy.source_node
19
+ max = (::Contrast::ASSESS.max_source_events ||
20
+ Contrast::Config::AssessConfiguration::DEFAULT_MAX_SOURCE_EVENTS)
21
+ return at_limit?(method_policy, context.source_event_count, max)
22
+
23
+ end
24
+ if method_policy.propagation_node
25
+ max = (::Contrast::ASSESS.max_propagation_events ||
26
+ Contrast::Config::AssessConfiguration::DEFAULT_MAX_PROPAGATION_EVENTS)
27
+ return at_limit?(method_policy, context.propagation_event_count, max)
28
+ end
29
+
30
+ false # policy does not have limit
31
+ end
32
+
33
+ def event_limit_for_rule? rule_id
34
+ if Contrast::Utils::Timer.now_ms > threshold_time_limit
35
+ @_rule_counts = nil
36
+ @_threshold_time_limit = nil
37
+ threshold_time_limit
38
+ end
39
+ rule_counts[rule_id] += 1
40
+ # TODO: RUBY-1680 remove default
41
+ rule_counts[rule_id] >=
42
+ (::Contrast::ASSESS.max_rule_reported || Contrast::Config::AssessConfiguration::DEFAULT_MAX_RULE_REPORTED)
43
+ end
44
+
45
+ # Increments the event count for the type of event that is being tracked
46
+ #
47
+ # @param node [Contrast::Agent::Assess::Policy::PolicyNode] policy to increment
48
+ def increment_event_count node
49
+ return unless (context = Contrast::Agent::REQUEST_TRACKER.current)
50
+
51
+ context.source_event_count += 1 if node.cs__is_a?(Contrast::Agent::Assess::Policy::SourceNode)
52
+ context.propagation_event_count += 1 if node.cs__is_a?(Contrast::Agent::Assess::Policy::PropagationNode)
53
+ end
54
+
55
+ private
56
+
57
+ # helper method to check limit and log when necessary
58
+ def at_limit? method_policy, current_count, event_max
59
+ if current_count == event_max
60
+ logger.warn('Event Limit Reached:',
61
+ {
62
+ count: current_count,
63
+ max: event_max,
64
+ policy: method_policy.method_name,
65
+ node: method_policy
66
+ })
67
+ # increment to be over count for logging purposes
68
+ increment_event_count(method_policy)
69
+ return true
70
+ elsif current_count > event_max
71
+ # increment to be over count for logging purposes
72
+ increment_event_count(method_policy)
73
+ logger.warn('Event Limit Exceeded:',
74
+ {
75
+ count: current_count,
76
+ policy: method_policy.method_name,
77
+ node: method_policy
78
+ })
79
+ return true
80
+ end
81
+ false
82
+ end
83
+
84
+ def rule_counts
85
+ @_rule_counts ||= Hash.new { |h, k| h[k] = 0 }
86
+ end
87
+
88
+ # the time threshold for which to track rule counts resets when now >= threshold_time_limit
89
+ # @return [Integer]
90
+ def threshold_time_limit
91
+ @_threshold_time_limit ||= Contrast::Utils::Timer.now_ms + (::Contrast::ASSESS.time_limit_threshold || 0)
92
+ end
93
+ end
94
+ end
95
+ end
96
+ end
@@ -92,20 +92,24 @@ module Contrast
92
92
  # @param preshift [Contrast::Agent::Assess::PreShift] The capture of the state of the code just prior to
93
93
  # the invocation of the patched method.
94
94
  # @param target [Object] the thing to which to propagate
95
+ # @param propagation_data [Contrast::Agent::Assess::Events::EventData] this will hold the
96
+ # object [Object] the Object on which the method was invoked
97
+ # args [Array<Object>] the Arguments with which the method was invoked
95
98
  # @return [Boolean]
96
- def can_propagate? propagation_node, preshift, target
99
+ def can_propagate? propagation_node, preshift, target, propagation_data
97
100
  return false unless appropriate_target?(propagation_node, target)
98
101
  return true if Contrast::Utils::Assess::TrackingUtil.tracked?(target)
99
- if propagation_node.use_original_object?
100
- # return true since we don't have preshift while using the original object.
101
- return true
102
- end
103
- return false unless preshift
102
+ return false unless appropriate_source?(propagation_node, propagation_data, preshift)
104
103
 
105
104
  propagation_node.sources.each do |source|
106
105
  case source
107
106
  when Contrast::Utils::ObjectShare::OBJECT_KEY
108
- return true if Contrast::Utils::Assess::TrackingUtil.tracked?(preshift.object)
107
+ source_object = if propagation_node.use_original_object?
108
+ propagation_data.object
109
+ else
110
+ preshift.object
111
+ end
112
+ return true if Contrast::Utils::Assess::TrackingUtil.tracked?(source_object)
109
113
  else
110
114
  # has to be P, there's no ret source type (yet? ever?)
111
115
  return true if preshift.args && Contrast::Utils::Assess::TrackingUtil.tracked?(preshift.args[source])
@@ -129,6 +133,22 @@ module Contrast
129
133
 
130
134
  Contrast::Agent::Assess::Tracker.trackable?(target)
131
135
  end
136
+
137
+ # A source is appropriate if it is available for propagation
138
+ #
139
+ # @param propagation_node [Contrast::Agent::Assess::Policy::PropagationNode] the node that governs this
140
+ # propagation event.
141
+ # @param propagation_data [Contrast::Agent::Assess::Events::EventData] this will hold the
142
+ # object [Object] the Object on which the method was invoked
143
+ # args [Array<Object>] the Arguments with which the method was invoked
144
+ # @param preshift [Contrast::Agent::Assess::PreShift] The capture of the state of the code just prior to
145
+ # the invocation of the patched method.
146
+ # @return [Boolean]
147
+ def appropriate_source? propagation_node, propagation_data, preshift
148
+ return true if preshift
149
+
150
+ propagation_node.use_original_object? && propagation_data&.object
151
+ end
132
152
  end
133
153
  end
134
154
  end
@@ -7,15 +7,6 @@ module Contrast
7
7
  # This module will include all methods for some internal validations in the SourceMethod module
8
8
  # and some other module methods from the same place, so we can ease the main module
9
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
10
  # Find the name of the source
20
11
  #
21
12
  # @param source_node [Contrast::Agent::Assess::Policy::SourceNode] the node to direct applying this source
@@ -19,7 +19,7 @@ module Contrast
19
19
 
20
20
  private
21
21
 
22
- def build path: STDOUT_STR, level_const: DEFAULT_LEVEL
22
+ def build path: STDOUT_STR, level_const: DEFAULT_LEVEL, progname: PROGNAME
23
23
  logger = case path
24
24
  when STDOUT_STR, STDERR_STR
25
25
  ::Ougai::Logger.new(Object.cs__const_get(path))
@@ -27,7 +27,7 @@ module Contrast
27
27
  ::Ougai::Logger.new(path)
28
28
  end
29
29
  add_contrast_loggers(logger)
30
- logger.progname = PROGNAME
30
+ logger.progname = progname
31
31
  logger.level = level_const
32
32
  logger.formatter = Contrast::Logger::Format.new
33
33
  logger.formatter.datetime_format = DATE_TIME_FORMAT
@@ -5,6 +5,9 @@ module Contrast
5
5
  module Utils
6
6
  # A LRU(Least Recently Used) Cache store.
7
7
  class LRUCache
8
+ # Initializes new Least Recently Used Cache Store
9
+ #
10
+ # @raise [StandardError] raises error if provided capacity is invalid or less or equal to zero
8
11
  def initialize capacity = 500
9
12
  raise(StandardError('Capacity must be bigger than 0')) if capacity <= 0
10
13
 
@@ -34,6 +34,7 @@ module Contrast
34
34
  SECURITY_EXCEPTION_MARKER = 'Contrast::SecurityException'
35
35
  # We're only going to suppress SecurityExceptions indicating a blocked attack. And, only if the
36
36
  # config.agent.ruby.exceptions.capture? is set
37
+ # @raise [Contrast::SecurityException] if exceptions.capture? is allowed
37
38
  def handle_exception exception
38
39
  if security_exception?(exception)
39
40
  exception_control = ::Contrast::AGENT.exception_control
@@ -74,6 +75,7 @@ module Contrast
74
75
  true
75
76
  end
76
77
 
78
+ # @raise [Contrast::SecurityException] raises error to prevent an attack
77
79
  def application_code env
78
80
  logger.trace_with_time('application') do
79
81
  app.call(env)
@@ -7,18 +7,6 @@ module Contrast
7
7
  # This module will include all methods for different patch applies from Patch module and some other module
8
8
  # methods from the same place, so we can ease the main module
9
9
  module PatchUtils
10
- # Method to choose which replaced return from the post_patch to actually return.
11
- #
12
- # @param propagated_ret [Object, nil] The replaced return from the propagation patch.
13
- # @param source_ret [Object, nil] The replaced return from the source patch.
14
- # @param ret [Object, nil] The original return of the patched method.
15
- # @return [Object, nil] The thing to return from the post patch.
16
- def handle_return propagated_ret, source_ret, ret
17
- safe_return = propagated_ret || source_ret || ret
18
- safe_return.rewind if Contrast::Utils::IOUtil.should_rewind?(safe_return)
19
- safe_return
20
- end
21
-
22
10
  # Given a module and method, construct an expected name for the alias by which Contrast will reference the
23
11
  # original.
24
12
  #
@@ -107,7 +95,7 @@ module Contrast
107
95
  # @param object [Object] The object on which the method is invoked, typically what would be returned by self.
108
96
  # @param args [Array<Object>] The arguments passed to the method being invoked.
109
97
  def apply_inventory method_policy, method, exception, object, args
110
- return unless ::Contrast::INVENTORY.enabled?
98
+ return unless ::Contrast::INVENTORY.enable
111
99
 
112
100
  apply_trigger_only(method_policy&.inventory_node, method, exception, object, args)
113
101
  end
@@ -123,8 +111,6 @@ module Contrast
123
111
  # @param args [Array<Object>] The arguments passed to the method being invoked.
124
112
  # @param block [Proc] The block passed to the method that was invoked.
125
113
  def apply_assess method_policy, preshift, object, ret, args, block
126
- source_ret = nil
127
- propagated_ret = nil
128
114
  return ret unless method_policy && ::Contrast::ASSESS.enabled?
129
115
 
130
116
  current_context = Contrast::Agent::REQUEST_TRACKER.current
@@ -135,27 +121,24 @@ module Contrast
135
121
  Contrast::Agent::Assess::Policy::TriggerMethod.apply_trigger_rule(trigger_node, object, ret, args)
136
122
  end
137
123
  if method_policy.source_node
138
- # If we were given a frozen return, and it was the target of a source, and we have frozen sources enabled,
139
- # we'll need to replace the return. Note, this is not the default case.
140
- source_ret = Contrast::Agent::Assess::Policy::SourceMethod.apply_source(method_policy, object, ret, args)
124
+ Contrast::Agent::Assess::Policy::SourceMethod.apply_source(method_policy, object, ret, args)
141
125
  end
142
126
  if method_policy.propagation_node
143
- propagated_ret = Contrast::Agent::Assess::Policy::PropagationMethod.apply_propagation(
127
+ Contrast::Agent::Assess::Policy::PropagationMethod.apply_propagation(
144
128
  method_policy,
145
129
  preshift,
146
130
  object,
147
- source_ret || ret,
131
+ ret,
148
132
  args,
149
133
  block)
150
134
  end
151
- handle_return(propagated_ret, source_ret, ret)
152
135
  rescue StandardError => e
153
136
  logger.error('Unable to assess method call.', e)
154
- handle_return(propagated_ret, source_ret, ret)
155
137
  rescue Exception => e # rubocop:disable Lint/RescueException
156
138
  logger.error('Unable to assess method call.', e)
157
- handle_return(propagated_ret, source_ret, ret)
158
139
  raise(e)
140
+ ensure
141
+ ret.rewind if Contrast::Utils::IOUtil.should_rewind?(ret)
159
142
  end
160
143
 
161
144
  # Generic invocation of the Inventory or Protect patch which apply to the given method.
@@ -34,8 +34,8 @@ module Contrast
34
34
  def build_request event
35
35
  return unless valid_event?(event)
36
36
 
37
- string_body = if Array(event).all?(Contrast::Agent::Telemetry::TelemetryException::Event)
38
- event.map(&:to_controlled_hash).flatten!
37
+ string_body = if event.cs__is_a?(Contrast::Agent::Telemetry::TelemetryException::Event)
38
+ [event.to_controlled_hash]
39
39
  else
40
40
  [event.to_hash]
41
41
  end
@@ -73,14 +73,14 @@ module Contrast
73
73
  ready_after if status_code == 429
74
74
  end
75
75
 
76
- # This method will be responsible for validating the event
77
- # @param event[Contrast::Agent::Telemetry::Event,Contrast::Agent::Telemetry::StartupMetricsEvent,
78
- # array<Contrast::Agent::Telemetry::TelemetryException::Event>]
76
+ # This method will be responsible for validating the event. Valid if event is of a known
77
+ # Contrast::Agent::Telemetry type
78
+ #
79
+ # @param event [Object]
79
80
  def valid_event? event
80
81
  return true if event.cs__is_a?(Contrast::Agent::Telemetry::Event)
81
82
  return true if event.cs__is_a?(Contrast::Agent::Telemetry::StartupMetricsEvent)
82
- # Batch
83
- return true if Array(event).all?(Contrast::Agent::Telemetry::TelemetryException::Event)
83
+ return true if event.cs__is_a?(Contrast::Agent::Telemetry::TelemetryException::Event)
84
84
 
85
85
  false
86
86
  end