contrast-agent 6.8.0 → 6.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 (95) hide show
  1. checksums.yaml +4 -4
  2. data/lib/contrast/agent/assess/policy/trigger_method.rb +1 -1
  3. data/lib/contrast/agent/assess/property/evented.rb +11 -11
  4. data/lib/contrast/agent/assess.rb +0 -1
  5. data/lib/contrast/agent/excluder.rb +1 -1
  6. data/lib/contrast/agent/middleware.rb +8 -2
  7. data/lib/contrast/agent/protect/input_analyzer/worth_watching_analyzer.rb +116 -0
  8. data/lib/contrast/agent/protect/rule/base.rb +2 -2
  9. data/lib/contrast/agent/protect/rule/bot_blocker.rb +1 -1
  10. data/lib/contrast/agent/protect/rule/cmd_injection.rb +8 -7
  11. data/lib/contrast/agent/protect/rule/cmdi/cmdi_chained_command.rb +0 -5
  12. data/lib/contrast/agent/protect/rule/cmdi/cmdi_dangerous_path.rb +0 -5
  13. data/lib/contrast/agent/protect/rule/path_traversal.rb +4 -3
  14. data/lib/contrast/agent/protect/rule/sqli.rb +4 -3
  15. data/lib/contrast/agent/protect/rule/xss.rb +1 -0
  16. data/lib/contrast/agent/reporting/attack_result/rasp_rule_sample.rb +1 -1
  17. data/lib/contrast/agent/reporting/report.rb +1 -0
  18. data/lib/contrast/agent/reporting/reporter.rb +34 -0
  19. data/lib/contrast/agent/reporting/reporter_heartbeat.rb +3 -9
  20. data/lib/contrast/agent/reporting/reporting_events/application_activity.rb +1 -1
  21. data/lib/contrast/agent/reporting/reporting_events/application_defend_activity.rb +12 -7
  22. data/lib/contrast/agent/reporting/reporting_events/application_inventory_activity.rb +6 -1
  23. data/lib/contrast/agent/reporting/reporting_events/finding.rb +2 -2
  24. data/lib/contrast/agent/reporting/reporting_events/finding_event.rb +239 -93
  25. data/lib/contrast/agent/reporting/reporting_events/finding_event_signature.rb +10 -23
  26. data/lib/contrast/agent/reporting/reporting_events/finding_event_source.rb +10 -9
  27. data/lib/contrast/agent/reporting/reporting_events/observed_route.rb +12 -0
  28. data/lib/contrast/agent/reporting/reporting_events/server_reporting_event.rb +8 -0
  29. data/lib/contrast/agent/reporting/reporting_events/server_settings.rb +40 -0
  30. data/lib/contrast/agent/reporting/reporting_utilities/endpoints.rb +6 -0
  31. data/lib/contrast/agent/reporting/reporting_utilities/reporter_client.rb +43 -1
  32. data/lib/contrast/agent/reporting/reporting_utilities/reporter_client_utils.rb +8 -4
  33. data/lib/contrast/agent/reporting/reporting_utilities/response_extractor.rb +58 -4
  34. data/lib/contrast/agent/reporting/reporting_utilities/response_handler.rb +4 -3
  35. data/lib/contrast/agent/reporting/reporting_utilities/response_handler_utils.rb +76 -16
  36. data/lib/contrast/agent/reporting/server_settings_worker.rb +44 -0
  37. data/lib/contrast/agent/reporting/settings/assess_server_feature.rb +14 -2
  38. data/lib/contrast/agent/reporting/settings/helpers.rb +7 -0
  39. data/lib/contrast/agent/reporting/settings/protect_server_feature.rb +39 -2
  40. data/lib/contrast/agent/reporting/settings/rule_definition.rb +3 -0
  41. data/lib/contrast/agent/reporting/settings/security_logger.rb +77 -0
  42. data/lib/contrast/agent/reporting/settings/server_features.rb +9 -0
  43. data/lib/contrast/agent/reporting/settings/syslog.rb +34 -5
  44. data/lib/contrast/agent/request.rb +1 -0
  45. data/lib/contrast/agent/request_handler.rb +5 -10
  46. data/lib/contrast/agent/telemetry/events/exceptions/telemetry_exception_event.rb +1 -1
  47. data/lib/contrast/agent/thread_watcher.rb +35 -1
  48. data/lib/contrast/agent/version.rb +1 -1
  49. data/lib/contrast/agent.rb +6 -0
  50. data/lib/contrast/api/communication/connection_status.rb +15 -0
  51. data/lib/contrast/components/agent.rb +34 -0
  52. data/lib/contrast/components/api.rb +23 -0
  53. data/lib/contrast/components/app_context.rb +23 -3
  54. data/lib/contrast/components/assess.rb +34 -4
  55. data/lib/contrast/components/assess_rules.rb +18 -0
  56. data/lib/contrast/components/base.rb +40 -0
  57. data/lib/contrast/components/config/sources.rb +95 -0
  58. data/lib/contrast/components/config.rb +18 -1
  59. data/lib/contrast/components/heap_dump.rb +10 -0
  60. data/lib/contrast/components/inventory.rb +15 -2
  61. data/lib/contrast/components/logger.rb +18 -0
  62. data/lib/contrast/components/polling.rb +36 -0
  63. data/lib/contrast/components/protect.rb +48 -1
  64. data/lib/contrast/components/ruby_component.rb +15 -0
  65. data/lib/contrast/components/sampling.rb +70 -13
  66. data/lib/contrast/components/security_logger.rb +13 -0
  67. data/lib/contrast/components/settings.rb +74 -7
  68. data/lib/contrast/config/certification_configuration.rb +14 -0
  69. data/lib/contrast/config/config.rb +46 -0
  70. data/lib/contrast/config/diagnostics.rb +114 -0
  71. data/lib/contrast/config/diagnostics_tools.rb +98 -0
  72. data/lib/contrast/config/effective_config.rb +65 -0
  73. data/lib/contrast/config/effective_config_value.rb +32 -0
  74. data/lib/contrast/config/exception_configuration.rb +12 -0
  75. data/lib/contrast/config/protect_rule_configuration.rb +1 -1
  76. data/lib/contrast/config/protect_rules_configuration.rb +8 -7
  77. data/lib/contrast/config/request_audit_configuration.rb +13 -0
  78. data/lib/contrast/config/server_configuration.rb +41 -2
  79. data/lib/contrast/configuration.rb +28 -2
  80. data/lib/contrast/extension/assess/erb.rb +1 -1
  81. data/lib/contrast/utils/assess/event_limit_utils.rb +31 -9
  82. data/lib/contrast/utils/assess/trigger_method_utils.rb +5 -4
  83. data/lib/contrast/utils/hash_digest.rb +2 -2
  84. data/lib/contrast/utils/input_classification_base.rb +1 -2
  85. data/lib/contrast/utils/reporting/application_activity_batch_utils.rb +81 -0
  86. data/lib/contrast/utils/routes_sent.rb +60 -0
  87. data/lib/contrast/utils/telemetry_client.rb +1 -2
  88. data/lib/contrast/utils/timer.rb +16 -0
  89. data/lib/contrast.rb +3 -1
  90. data/ruby-agent.gemspec +5 -1
  91. metadata +29 -20
  92. data/lib/contrast/agent/assess/contrast_event.rb +0 -157
  93. data/lib/contrast/agent/assess/events/event_factory.rb +0 -34
  94. data/lib/contrast/agent/assess/events/source_event.rb +0 -46
  95. data/lib/contrast/agent/reporting/reporting_events/server_activity.rb +0 -36
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 2c32847e196cdcf1540bd39373f9327a46e50cb1c4be6516f2ad5664696c0221
4
- data.tar.gz: 03b9bc37cf6b8fe3fd0662f120f6f24cc9faf868cf91c7fb698ccbcb52f15aac
3
+ metadata.gz: b850f63bce180f09f998f5363f58b01e9c69db61a2f98a31db97fb59b82564d7
4
+ data.tar.gz: 811072666998fb4daf0f49d2514d875b45c18d79d29398639862f1c2144aa930
5
5
  SHA512:
6
- metadata.gz: 204a13ebf2cc20d9302d55a1d2201cc0f8f2ba35b5bf1b3392f75c0636f8ab5224de54106af8349c16da111d86f2381e894d8925eccf2df1e46e4d04b99eb7be
7
- data.tar.gz: ac50424a98754b82bab6576b7205cdb3f6243f5dc6aa9c55f817f15cfba61e1f0858c27a66efd18457442f0b4f2e8ea42cdfcbecdd67c6941edcd86c77b11164
6
+ metadata.gz: ac0f4dcea0a62d6aa000659943f2b994a02940148fffdff2901ff4ff61d27fd257170ec4f48d0b1925d569dbc20de89a8866f76a17dd1c22aa15d9c80e4e9eb1
7
+ data.tar.gz: bd2490f015d1a5c8be5f0e7a0fc60e5acdc436b03e32420cbf19542a53b760c843cd84a17a0e7a8a3794ec69e3aa909e63fabdb32f4c32d5daa48ece261176aa
@@ -1,7 +1,6 @@
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/assess/events/event_factory'
5
4
  require 'contrast/agent/assess/policy/trigger_validation/trigger_validation'
6
5
  require 'contrast/agent/excluder'
7
6
  require 'contrast/components/logger'
@@ -15,6 +14,7 @@ require 'contrast/agent/reporting/reporting_events/preflight_message'
15
14
  require 'contrast/agent/reporting/reporting_events/route_discovery'
16
15
  require 'contrast/agent/reporting/reporting_utilities/reporting_storage'
17
16
  require 'contrast/agent/reporting/reporting_utilities/build_preflight'
17
+ require 'contrast/utils/assess/event_limit_utils'
18
18
 
19
19
  module Contrast
20
20
  module Agent
@@ -1,8 +1,7 @@
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/assess/events/event_factory'
5
- require 'contrast/agent/assess/events/source_event'
4
+ require 'contrast/agent/reporting/reporting_events/finding_event'
6
5
 
7
6
  module Contrast
8
7
  module Agent
@@ -25,7 +24,7 @@ module Contrast
25
24
  # the key used to accessed if from a map or nil if a type like
26
25
  # BODY
27
26
  def build_event event_data, source_type = nil, source_name = nil
28
- @event = Contrast::Agent::Assess::Events::EventFactory.build(event_data, source_type, source_name)
27
+ @event = Contrast::Agent::Reporting::FindingEvent.new(event_data, source_type, source_name)
29
28
  report_sources(event_data.tagged, @event)
30
29
  end
31
30
 
@@ -35,21 +34,22 @@ module Contrast
35
34
  # context's observed route
36
35
  #
37
36
  # @param tagged [Object] The Target of the Event
38
- # @param event [Contrast::Agent::Assess::Events::ContrastEvent]
37
+ # @param event [Contrast::Agent::Reporting::FindingEvent]
39
38
  def report_sources tagged, event
40
39
  return unless tagged && !tagged.to_s.empty?
41
- return unless event.cs__is_a?(Contrast::Agent::Assess::Events::SourceEvent)
42
40
  return unless event.source_type
43
41
  return unless (current_request = Contrast::Agent::REQUEST_TRACKER.current)
44
42
 
45
- if current_request.observed_route.sources.any? do |source|
46
- source.type == event.source_type && source.name == event.source_name # rubocop:disable Security/Module/Name
47
- end
43
+ event.event_sources&.each do |event_source|
44
+ if current_request.observed_route.sources.any? do |source|
45
+ source.source_type == event_source.source_type && source.source_name == event_source.source_name
46
+ end
48
47
 
49
- return
50
- end
48
+ next
49
+ end
51
50
 
52
- current_request.observed_route.sources << event.event_source if event.event_source
51
+ current_request.observed_route.sources << event_source
52
+ end
53
53
  end
54
54
  end
55
55
  end
@@ -18,7 +18,6 @@ module Contrast
18
18
  # reporting / tracking
19
19
  require 'contrast/agent/assess/properties'
20
20
  require 'contrast/agent/assess/tag'
21
- require 'contrast/agent/assess/events/event_factory'
22
21
  end
23
22
  end
24
23
  end
@@ -82,7 +82,7 @@ module Contrast
82
82
  event_sources = finding.events.flat_map(&:event_sources)
83
83
  event_sources.each do |event_source|
84
84
  return false unless rule_input_exclusions.any? do |exclusion|
85
- input_match?(exclusion, event_source.type, event_source.name) # rubocop:disable Security/Module/Name
85
+ input_match?(exclusion, event_source.source_type, event_source.source_name)
86
86
  end
87
87
  end
88
88
 
@@ -15,7 +15,7 @@ require 'contrast/agent/request_handler'
15
15
  require 'contrast/agent/static_analysis'
16
16
  require 'contrast/agent/telemetry/events/startup_metrics_event'
17
17
  require 'contrast/utils/middleware_utils'
18
-
18
+ require 'contrast/utils/reporting/application_activity_batch_utils'
19
19
  require 'contrast/utils/timer'
20
20
 
21
21
  module Contrast
@@ -27,6 +27,7 @@ module Contrast
27
27
  include Contrast::Components::Logger::InstanceMethods
28
28
  include Contrast::Components::Scope::InstanceMethods
29
29
  include Contrast::Utils::MiddlewareUtils
30
+ include Contrast::Utils::Reporting::ApplicationActivityBatchUtils
30
31
 
31
32
  attr_reader :app
32
33
 
@@ -62,6 +63,7 @@ module Contrast
62
63
  # the Rack framework.
63
64
  def call env
64
65
  logger.trace_with_time('Elapsed time for Contrast::Agent::Middleware#call') do
66
+ ::Contrast::Agent::ThreadWatcher.check_before_start
65
67
  return app.call(env) unless ::Contrast::AGENT.enabled?
66
68
 
67
69
  Contrast::Agent.heapdump_util.start_thread!
@@ -173,13 +175,17 @@ module Contrast
173
175
  Contrast::Agent::FINDINGS.report_collected_findings unless Contrast::Agent::FINDINGS.collection.empty?
174
176
  # All protect rules, which are trigger but require response to be reported
175
177
  Contrast::Agent::EXPLOITS.report_recorded_exploits(context) unless Contrast::Agent::EXPLOITS.collection.empty?
178
+ # Process Worth Watching Inputs for v2 rules
179
+ Contrast::Agent.worth_watching_analyzer&.add_to_queue(context.agent_input_analysis)
176
180
 
177
181
  if Contrast::Agent.framework_manager.streaming?(env)
178
182
  context.reset_activity
179
183
  request_handler.stream_safe_postfilter
180
184
  else
181
185
  request_handler.ruleset.postfilter
182
- request_handler.report_activity
186
+ request_handler.report_observed_route
187
+ add_activity_to_batch(context.activity)
188
+ report_batch
183
189
  end
184
190
  end
185
191
  # unsuccessful attack
@@ -0,0 +1,116 @@
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/agent/worker_thread'
5
+ require 'contrast/agent/reporting/input_analysis/score_level'
6
+ require 'contrast/agent/reporting/reporting_events/application_activity'
7
+ require 'contrast/utils/input_classification_base'
8
+
9
+ module Contrast
10
+ module Agent
11
+ module Protect
12
+ # WorthWatchingInputAnalyzer Perform analysis of input tracing v2 worthwatching results in a
13
+ # separate thread, should only be run at the end of the request.
14
+ # Currently only includes: cmd_injection & sqli_injection rules
15
+ class WorthWatchingInputAnalyzer < WorkerThread
16
+ include Timeout
17
+ include Contrast::Agent::Protect::Rule::InputClassificationBase
18
+
19
+ QUEUE_SIZE = 1000.cs__freeze
20
+ AGENTLIB_TIMEOUT = 5.cs__freeze
21
+ # max size of inputs to evaluate
22
+ INPUT_BYTESIZE_THRESHOLD = 100_000.cs__freeze
23
+ REPORT_INTERVAL_SECOND = 30.cs__freeze
24
+
25
+ # Thread that will process all the InputAnalysisResults that have a score level of WORTHWATCHING and
26
+ # sends results to TeamServer
27
+ def start_thread!
28
+ return if running?
29
+
30
+ @_thread = Contrast::Agent::Thread.new do
31
+ logger.info('Starting Worth Watching Analyzer thread.')
32
+ loop do
33
+ sleep(REPORT_INTERVAL_SECOND)
34
+ next if queue.empty?
35
+
36
+ report = false
37
+ num_to_report = queue.length
38
+ activity = Contrast::Agent::Reporting::ApplicationActivity.new
39
+ num_to_report.times do
40
+ next unless (attack_result = eval_input(queue.pop))
41
+
42
+ activity.attach_defend(attack_result)
43
+ report = true
44
+ end
45
+ Contrast::Agent.reporter.send_event_immediately(activity) if report
46
+ rescue StandardError => e
47
+ logger.debug('Worth Watching Analyzer thread could not process result because of:', e)
48
+ end
49
+ end
50
+ end
51
+
52
+ # param Contrast::Agent::Reporting::InputAnalysis
53
+ def add_to_queue input_analysis
54
+ return if input_analysis&.results&.empty?
55
+
56
+ if queue.size >= QUEUE_SIZE
57
+ logger.debug('WorthWatching queue at max size, skip input_result')
58
+ return
59
+ end
60
+ input_analysis.results.select { |val| val.score_level == WORTHWATCHING }.
61
+ each do |ia_result|
62
+ queue << ia_result
63
+ end
64
+ end
65
+
66
+ private
67
+
68
+ # @param ia_result Contrast::Agent::Reporting::InputAnalysisResult the WorthWatching InputAnalysisResult
69
+ # @return [Contrast::Agent::Reporting::InputAnalysisResult, nil] InputAnalysisResult updated Result or nil
70
+ def eval_input ia_result
71
+ return if ia_result.nil?
72
+
73
+ if ia_result.value.to_s.bytesize >= INPUT_BYTESIZE_THRESHOLD
74
+ logger.debug("Skip anaylsis: Input size is larger than #{ INPUT_BYTESIZE_THRESHOLD / 1024 }KB")
75
+ return
76
+ end
77
+
78
+ begin
79
+ input_eval = Timeout.timeout(AGENTLIB_TIMEOUT) do
80
+ Contrast::AGENT_LIB.eval_input(ia_result.value,
81
+ convert_input_type(ia_result.input_type),
82
+ Contrast::AGENT_LIB.rule_set[ia_result.rule_id],
83
+ Contrast::AGENT_LIB.eval_option[:NONE])
84
+ end
85
+ score = input_eval&.score || 0
86
+ return if score <= THRESHOLD
87
+
88
+ ia_result.score_level = DEFINITEATTACK
89
+ build_attack_result(ia_result)
90
+ rescue Timeout::Error => e
91
+ logger.warn('AgentLib timed out when processing WORTHWATCHING InputAnalysisResult', e, ia_result)
92
+ nil
93
+ end
94
+ end
95
+
96
+ # @param ia_result Contrast::Agent::Reporting::InputAnalysisResult the updated InputAnalysisResult
97
+ # with a score of :DEFINITEATTACK
98
+ # @return [Contrast::Agent::Reporting::AttackResult] the attack result from
99
+ # this input
100
+ def build_attack_result ia_result
101
+ Contrast::PROTECT.rule(ia_result.rule_id).build_attack_without_match(nil, ia_result, nil)
102
+ end
103
+
104
+ def queue
105
+ @_queue ||= Queue.new
106
+ end
107
+
108
+ def delete_queue!
109
+ @_queue&.clear
110
+ @_queue&.close
111
+ @_queue = nil
112
+ end
113
+ end
114
+ end
115
+ end
116
+ end
@@ -148,14 +148,14 @@ module Contrast
148
148
  # protect rule but did not exploit the application. As such, we need
149
149
  # to build a result to report this violation to TeamServer.
150
150
  #
151
- # @param context [Contrast::Agent::RequestContext] the context of the
151
+ # @param context [Contrast::Agent::RequestContext, nil] the context of the
152
152
  # request in which this input is evaluated.
153
153
  # @param ia_result [Contrast::Agent::Reporting::InputAnalysis] the
154
154
  # analysis of the input that was determined to be an attack
155
155
  # @param result [Contrast::Agent::Reporting::AttackResult, nil] previous
156
156
  # attack result for this rule, if one exists, in the case of
157
157
  # multiple inputs being found to violate the protection criteria
158
- # @param kwargs [Hash] key - value pairs of context individual rules
158
+ # @param kwargs [Hash, nil] key - value pairs of context individual rules
159
159
  # need to build out details to send to TeamServer to tell the
160
160
  # story of the attack
161
161
  # @return [Contrast::Agent::Reporting::AttackResult] the attack result from
@@ -66,7 +66,7 @@ module Contrast
66
66
  # @param ia_result [Contrast::Agent::Reporting::InputAnalysisResult]
67
67
  # @param _candidate_string
68
68
  # @param **_kwargs
69
- # @return [Contrast::Api::Dtm::RaspRuleSample]
69
+ # @return [Contrast::Agent::Reporting::RaspRuleSample]
70
70
  def build_sample context, ia_result, _candidate_string, **_kwargs
71
71
  sample = build_base_sample(context, ia_result)
72
72
  sample.details = Contrast::Agent::Reporting::BotBlockerDetails.new
@@ -21,24 +21,25 @@ module Contrast
21
21
  include Contrast::Components::Logger::InstanceMethods
22
22
  include Contrast::Agent::Reporting::InputType
23
23
  NAME = 'cmd-injection'
24
-
25
24
  APPLICABLE_USER_INPUTS = [
26
25
  BODY, COOKIE_VALUE, HEADER, PARAMETER_NAME,
27
26
  PARAMETER_VALUE, JSON_VALUE, MULTIPART_VALUE,
28
27
  MULTIPART_FIELD_NAME, XML_VALUE, DWR_VALUE
29
28
  ].cs__freeze
30
- SUB_RULES = [
31
- Contrast::Agent::Protect::Rule::CmdiBackdoors.new,
32
- Contrast::Agent::Protect::Rule::CmdiChainedCommand.new,
33
- Contrast::Agent::Protect::Rule::CmdiDangerousPath.new
34
- ].cs__freeze
35
29
 
36
30
  def rule_name
37
31
  NAME
38
32
  end
39
33
 
34
+ # Array of sub_rules:
35
+ #
36
+ # @return [Array]
40
37
  def sub_rules
41
- SUB_RULES
38
+ @_sub_rules ||= [
39
+ Contrast::Agent::Protect::Rule::CmdiBackdoors.new,
40
+ Contrast::Agent::Protect::Rule::CmdiChainedCommand.new,
41
+ Contrast::Agent::Protect::Rule::CmdiDangerousPath.new
42
+ ].cs__freeze
42
43
  end
43
44
 
44
45
  def applicable_user_inputs
@@ -15,11 +15,6 @@ module Contrast
15
15
  class CmdiChainedCommand < Contrast::Agent::Protect::Rule::CmdiBaseRule
16
16
  NAME = 'cmd-injection-semantic-chained-commands'
17
17
 
18
- def initialize
19
- super
20
- @mode = :MONITOR
21
- end
22
-
23
18
  def rule_name
24
19
  NAME
25
20
  end
@@ -15,11 +15,6 @@ module Contrast
15
15
  class CmdiDangerousPath < Contrast::Agent::Protect::Rule::CmdiBaseRule
16
16
  NAME = 'cmd-injection-semantic-dangerous-paths'
17
17
 
18
- def initialize
19
- super
20
- @mode = :MONITOR
21
- end
22
-
23
18
  def rule_name
24
19
  NAME
25
20
  end
@@ -37,14 +37,15 @@ module Contrast
37
37
  /windows/repair/
38
38
  ].cs__freeze
39
39
 
40
- SUB_RULES = [Contrast::Agent::Protect::Rule::PathTraversalSemanticBypass.new].cs__freeze
41
-
42
40
  def rule_name
43
41
  NAME
44
42
  end
45
43
 
44
+ # Array of sub_rules
45
+ #
46
+ # @return [Array]
46
47
  def sub_rules
47
- SUB_RULES
48
+ @_sub_rules ||= [Contrast::Agent::Protect::Rule::PathTraversalSemanticBypass.new].cs__freeze
48
49
  end
49
50
 
50
51
  def applicable_user_inputs
@@ -26,8 +26,6 @@ module Contrast
26
26
 
27
27
  NAME = 'sql-injection'
28
28
 
29
- SUB_RULES = [Contrast::Agent::Protect::Rule::SqliDangerousFunctions.new].cs__freeze
30
-
31
29
  def rule_name
32
30
  NAME
33
31
  end
@@ -36,8 +34,11 @@ module Contrast
36
34
  BLOCK_MESSAGE
37
35
  end
38
36
 
37
+ # Array of sub_rules
38
+ #
39
+ # @return [Array]
39
40
  def sub_rules
40
- SUB_RULES
41
+ @_sub_rules ||= [Contrast::Agent::Protect::Rule::SqliDangerousFunctions.new].cs__freeze
41
42
  end
42
43
 
43
44
  def applicable_user_inputs
@@ -2,6 +2,7 @@
2
2
  # frozen_string_literal: true
3
3
 
4
4
  require 'contrast/agent/protect/rule/base_service'
5
+ require 'contrast/agent/reporting/input_analysis/input_type'
5
6
 
6
7
  module Contrast
7
8
  module Agent
@@ -28,7 +28,7 @@ module Contrast
28
28
  # @return [Contrast::Agent::Reporting::RaspRuleSample]
29
29
  def build context, ia_result
30
30
  sample = new
31
- sample.time_stamp = context&.timer&.start_ms
31
+ sample.time_stamp = context&.timer&.start_ms || Contrast::Utils::Timer.now_ms
32
32
  sample.user_input = build_user_input_from_ia(ia_result)
33
33
  sample.user_input.document_type = if context&.request
34
34
  Contrast::Utils::StringUtils.force_utf8(context.request.document_type)
@@ -26,5 +26,6 @@ require 'contrast/agent/reporting/reporting_events/observed_route'
26
26
  require 'contrast/agent/reporting/reporting_events/route_coverage'
27
27
  require 'contrast/agent/reporting/reporting_events/observed_library_usage'
28
28
  require 'contrast/agent/reporting/reporting_events/poll'
29
+ require 'contrast/agent/reporting/reporting_events/server_settings'
29
30
  # Sensitive data masking
30
31
  require 'contrast/agent/reporting/masker/masker'
@@ -5,6 +5,8 @@ require 'contrast/agent/worker_thread'
5
5
  require 'contrast/agent/reporting/report'
6
6
  require 'contrast/components/logger'
7
7
  require 'contrast/agent/reporting/reporting_events/agent_startup'
8
+ require 'contrast/agent/telemetry/events/exceptions/telemetry_exceptions'
9
+ require 'contrast/agent/telemetry/events/exceptions/obfuscate'
8
10
 
9
11
  module Contrast
10
12
  module Agent
@@ -13,6 +15,8 @@ module Contrast
13
15
  include Contrast::Components::Logger::InstanceMethods
14
16
  include Contrast::Utils::ObjectShare
15
17
 
18
+ MAX_QUEUE_SIZE = 1000
19
+
16
20
  class << self
17
21
  # check if we can report to TS
18
22
  #
@@ -68,6 +72,14 @@ module Contrast
68
72
  end
69
73
  return unless event
70
74
 
75
+ if queue.size >= MAX_QUEUE_SIZE
76
+ if Contrast::Agent::Telemetry::Base.enabled?
77
+ Contrast::Agent.thread_watcher.telemetry_queue.send_event(queue_limit_telemetry_event)
78
+ end
79
+
80
+ return
81
+ end
82
+
71
83
  queue << event
72
84
  end
73
85
 
@@ -126,6 +138,28 @@ module Contrast
126
138
  rescue StandardError => e
127
139
  logger.error('Could not send message to TeamServer from Reporter queue.', e)
128
140
  end
141
+
142
+ # @return [Contrast::Agent::Telemetry::TelemetryException::Event]
143
+ def queue_limit_telemetry_event
144
+ message_exception = Contrast::Agent::Telemetry::TelemetryException::MessageException.build(
145
+ 'String',
146
+ "Maximum queue size (#{ MAX_QUEUE_SIZE }) reached for reporting events", nil, stack_frame)
147
+ message = Contrast::Agent::Telemetry::TelemetryException::Message.build({}, [message_exception])
148
+ Contrast::Agent::Telemetry::TelemetryException::Event.new(message)
149
+ end
150
+
151
+ def stack_frame
152
+ stack_trace = caller_locations(20, 20)
153
+ stack_frame_type = if stack_trace.nil? || stack_trace[1].nil?
154
+ 'none'
155
+ else
156
+ Contrast::Agent::Telemetry::TelemetryException::Obfuscate.obfuscate_type(
157
+ stack_trace[1].path.delete_prefix(Dir.pwd))
158
+ end
159
+
160
+ stack_frame_function = stack_trace.nil? || stack_trace[1].nil? ? 'none' : stack_trace[1].label
161
+ Contrast::Agent::Telemetry::TelemetryException::StackFrame.build(stack_frame_function, stack_frame_type, nil)
162
+ end
129
163
  end
130
164
  end
131
165
  end
@@ -1,18 +1,16 @@
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/worker_thread'
5
- require 'contrast/agent/reporting/report'
4
+ require 'contrast/agent/reporting/reporter'
6
5
  require 'contrast/agent/inventory/dependency_usage_analysis'
7
6
  require 'contrast/agent/reporting/reporting_events/poll'
8
- require 'contrast/agent/reporting/reporting_events/server_activity'
9
7
 
10
8
  module Contrast
11
9
  module Agent
12
10
  # The ReporterHeartbeat will make sure that the process remains marked alive by TeamServer and that we periodically
13
11
  # reach out to get the latest settings for this application. It also sends out those messages which do not need to
14
12
  # be associated directly with a request, such as Server Activity and Library Observation.
15
- class ReporterHeartbeat < WorkerThread
13
+ class ReporterHeartbeat < Reporter
16
14
  # TeamServer will mark an application offline after 5 minutes. Sending this every one should be more than enough
17
15
  # to satisfy our goals.
18
16
  REFRESH_INTERVAL_SEC = 60
@@ -42,11 +40,7 @@ module Contrast
42
40
  #
43
41
  # @return [Array<Contrast::Agent::Reporting::ReportingEvent>]
44
42
  def polling_events
45
- [
46
- Contrast::Agent::Inventory::DependencyUsageAnalysis.instance.generate_library_usage,
47
- Contrast::Agent::Reporting::ServerActivity.new,
48
- poll_message
49
- ].compact
43
+ [Contrast::Agent::Inventory::DependencyUsageAnalysis.instance.generate_library_usage, poll_message].compact
50
44
  end
51
45
  end
52
46
  end
@@ -110,7 +110,7 @@ module Contrast
110
110
 
111
111
  # This is primary used for attaching new inventory reporting
112
112
  #
113
- # @param architecture [Contrast::Agent::Reporting::AttackResult]
113
+ # @param architecture [Contrast::Agent::Reporting::ArchitectureComponent]
114
114
  def attach_inventory architecture
115
115
  inventory.attach_data(architecture)
116
116
  end
@@ -28,18 +28,23 @@ module Contrast
28
28
  def attach_data attack_result
29
29
  attacker_activity = Contrast::Agent::Reporting::ApplicationDefendAttackerActivity.new
30
30
  attacker_activity.attach_data(attack_result)
31
- existing_attacker_activity = attackers.find do |existing|
32
- existing.source_forwarded_for == attacker_activity.source_forwarded_for &&
33
- existing.source_ip == attacker_activity.source_ip
34
- end
35
- rule = attack_result.rule_id
36
- if existing_attacker_activity
37
- attach_existing(existing_attacker_activity, attacker_activity, rule)
31
+
32
+ if (existing_attacker_activity = find_existing_attacker_activity(attacker_activity))
33
+ attach_existing(existing_attacker_activity, attacker_activity, attack_result.rule_id)
38
34
  else
39
35
  attackers << attacker_activity
40
36
  end
41
37
  end
42
38
 
39
+ # Find an existing attacker if it matches on source details
40
+ # @param attacker_activity [Contrast::Agent::Reporting::ApplicationDefendAttackerActivity]
41
+ def find_existing_attacker_activity new_attacker_activity
42
+ attackers.find do |existing|
43
+ existing.source_forwarded_for == new_attacker_activity.source_forwarded_for &&
44
+ existing.source_ip == new_attacker_activity.source_ip
45
+ end
46
+ end
47
+
43
48
  # @param existing_attacker_activity [Contrast::Agent::Reporting::ApplicationDefendAttackerActivity]
44
49
  # @param attacker_activity [Contrast::Agent::Reporting::ApplicationDefendAttackerActivity]
45
50
  # @param rule [String]
@@ -13,7 +13,7 @@ module Contrast
13
13
  # about the inventory of the application which was discovered during exercise of the application
14
14
  # during this activity period.
15
15
  class ApplicationInventoryActivity < Contrast::Agent::Reporting::ApplicationReportingEvent
16
- # return [Contrast::Agent::Reporting::ArchitectureComponent]
16
+ # return [Array<Contrast::Agent::Reporting::ArchitectureComponent>]
17
17
  attr_reader :components
18
18
  # @ return [Array<String>, nil] - User-Agent Header value
19
19
  attr_reader :browsers
@@ -46,6 +46,11 @@ module Contrast
46
46
  def duration
47
47
  Contrast::Utils::Timer.now_ms - (Contrast::Agent::REQUEST_TRACKER.current&.timer&.start_ms || 0)
48
48
  end
49
+
50
+ # Helper method to determine if InventoryActivity has no data
51
+ def empty?
52
+ @browsers.empty? && @components.empty?
53
+ end
49
54
  end
50
55
  end
51
56
  end
@@ -88,8 +88,8 @@ module Contrast
88
88
  event_messages = Contrast::Agent::Reporting::FindingEvent.from_source(source)
89
89
  events.concat(event_messages) if event_messages&.any?
90
90
  event_data = Contrast::Agent::Assess::Events::EventData.new(trigger_node, source, object, ret, args)
91
- contrast_event = Contrast::Agent::Assess::ContrastEvent.new(event_data)
92
- events << Contrast::Agent::Reporting::FindingEvent.convert(contrast_event)
91
+ contrast_event = Contrast::Agent::Reporting::FindingEvent.new(event_data)
92
+ events << contrast_event
93
93
  return unless request
94
94
 
95
95
  @request = Contrast::Agent::Reporting::FindingRequest.convert(request)