contrast-agent 5.1.0 → 5.2.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 (119) hide show
  1. checksums.yaml +4 -4
  2. data/ext/cs__assess_kernel/cs__assess_kernel.c +7 -4
  3. data/ext/cs__assess_module/cs__assess_module.c +7 -7
  4. data/ext/cs__common/cs__common.c +4 -0
  5. data/ext/cs__common/cs__common.h +1 -0
  6. data/ext/cs__contrast_patch/cs__contrast_patch.c +52 -27
  7. data/ext/cs__contrast_patch/cs__contrast_patch.h +2 -0
  8. data/ext/cs__scope/cs__scope.c +747 -0
  9. data/ext/cs__scope/cs__scope.h +88 -0
  10. data/ext/cs__scope/extconf.rb +5 -0
  11. data/lib/contrast/agent/assess/contrast_event.rb +20 -13
  12. data/lib/contrast/agent/assess/contrast_object.rb +4 -1
  13. data/lib/contrast/agent/assess/policy/propagation_node.rb +2 -5
  14. data/lib/contrast/agent/assess/policy/propagator/match_data.rb +2 -0
  15. data/lib/contrast/agent/assess/policy/trigger_method.rb +4 -1
  16. data/lib/contrast/agent/assess/rule/response/{autocomplete_rule.rb → auto_complete_rule.rb} +4 -3
  17. data/lib/contrast/agent/assess/rule/response/base_rule.rb +12 -79
  18. data/lib/contrast/agent/assess/rule/response/body_rule.rb +109 -0
  19. data/lib/contrast/agent/assess/rule/response/cache_control_header_rule.rb +157 -0
  20. data/lib/contrast/agent/assess/rule/response/click_jacking_header_rule.rb +26 -0
  21. data/lib/contrast/agent/assess/rule/response/csp_header_insecure_rule.rb +14 -15
  22. data/lib/contrast/agent/assess/rule/response/csp_header_missing_rule.rb +5 -25
  23. data/lib/contrast/agent/assess/rule/response/framework/rails_support.rb +29 -0
  24. data/lib/contrast/agent/assess/rule/response/header_rule.rb +70 -0
  25. data/lib/contrast/agent/assess/rule/response/hsts_header_rule.rb +12 -36
  26. data/lib/contrast/agent/assess/rule/response/parameters_pollution_rule.rb +2 -1
  27. data/lib/contrast/agent/assess/rule/response/x_content_type_header_rule.rb +26 -0
  28. data/lib/contrast/agent/assess/rule/response/x_xss_protection_header_rule.rb +36 -0
  29. data/lib/contrast/agent/middleware.rb +1 -0
  30. data/lib/contrast/agent/patching/policy/after_load_patcher.rb +1 -3
  31. data/lib/contrast/agent/patching/policy/patch.rb +2 -6
  32. data/lib/contrast/agent/patching/policy/patcher.rb +1 -1
  33. data/lib/contrast/agent/protect/input_analyzer/input_analyzer.rb +94 -0
  34. data/lib/contrast/agent/protect/rule/base.rb +28 -1
  35. data/lib/contrast/agent/protect/rule/base_service.rb +10 -1
  36. data/lib/contrast/agent/protect/rule/cmd_injection.rb +2 -0
  37. data/lib/contrast/agent/protect/rule/deserialization.rb +6 -0
  38. data/lib/contrast/agent/protect/rule/http_method_tampering.rb +5 -1
  39. data/lib/contrast/agent/protect/rule/no_sqli.rb +1 -0
  40. data/lib/contrast/agent/protect/rule/path_traversal.rb +1 -0
  41. data/lib/contrast/agent/protect/rule/sqli/sqli_input_classification.rb +124 -0
  42. data/lib/contrast/agent/protect/rule/sqli/sqli_worth_watching.rb +121 -0
  43. data/lib/contrast/agent/protect/rule/sqli.rb +33 -0
  44. data/lib/contrast/agent/protect/rule/xxe.rb +4 -0
  45. data/lib/contrast/agent/reporting/input_analysis/input_analysis.rb +44 -0
  46. data/lib/contrast/agent/reporting/input_analysis/input_analysis_result.rb +115 -0
  47. data/lib/contrast/agent/reporting/input_analysis/input_type.rb +44 -0
  48. data/lib/contrast/agent/reporting/input_analysis/score_level.rb +21 -0
  49. data/lib/contrast/agent/reporting/report.rb +1 -0
  50. data/lib/contrast/agent/reporting/reporter.rb +8 -1
  51. data/lib/contrast/agent/reporting/reporting_events/finding.rb +69 -36
  52. data/lib/contrast/agent/reporting/reporting_events/finding_event.rb +88 -59
  53. data/lib/contrast/agent/reporting/reporting_events/{finding_object.rb → finding_event_object.rb} +24 -20
  54. data/lib/contrast/agent/reporting/reporting_events/finding_event_parent_object.rb +39 -0
  55. data/lib/contrast/agent/reporting/reporting_events/finding_event_property.rb +40 -0
  56. data/lib/contrast/agent/reporting/reporting_events/{finding_signature.rb → finding_event_signature.rb} +29 -24
  57. data/lib/contrast/agent/reporting/reporting_events/finding_event_source.rb +12 -8
  58. data/lib/contrast/agent/reporting/reporting_events/{finding_stack.rb → finding_event_stack.rb} +23 -19
  59. data/lib/contrast/agent/reporting/reporting_events/{finding_taint_range.rb → finding_event_taint_range.rb} +17 -15
  60. data/lib/contrast/agent/reporting/reporting_events/finding_request.rb +26 -53
  61. data/lib/contrast/agent/reporting/reporting_events/poll.rb +29 -0
  62. data/lib/contrast/agent/reporting/reporting_events/reporting_event.rb +5 -4
  63. data/lib/contrast/agent/reporting/reporting_events/route_discovery.rb +1 -0
  64. data/lib/contrast/agent/reporting/reporting_events/server_activity.rb +1 -1
  65. data/lib/contrast/agent/reporting/reporting_utilities/audit.rb +10 -3
  66. data/lib/contrast/agent/reporting/reporting_utilities/endpoints.rb +0 -1
  67. data/lib/contrast/agent/reporting/reporting_utilities/reporter_client.rb +1 -0
  68. data/lib/contrast/agent/reporting/reporting_utilities/reporter_client_utils.rb +28 -20
  69. data/lib/contrast/agent/reporting/reporting_utilities/response_handler.rb +1 -1
  70. data/lib/contrast/agent/reporting/reporting_utilities/response_handler_utils.rb +13 -1
  71. data/lib/contrast/agent/request_context.rb +6 -1
  72. data/lib/contrast/agent/request_context_extend.rb +85 -21
  73. data/lib/contrast/agent/scope.rb +102 -107
  74. data/lib/contrast/agent/service_heartbeat.rb +45 -2
  75. data/lib/contrast/agent/version.rb +1 -1
  76. data/lib/contrast/api/decorators/bot_blocker.rb +37 -0
  77. data/lib/contrast/api/decorators/ip_denylist.rb +37 -0
  78. data/lib/contrast/api/decorators/rasp_rule_sample.rb +29 -0
  79. data/lib/contrast/api/decorators/user_input.rb +11 -1
  80. data/lib/contrast/api/decorators/virtual_patch.rb +34 -0
  81. data/lib/contrast/components/logger.rb +5 -0
  82. data/lib/contrast/components/protect.rb +4 -2
  83. data/lib/contrast/components/scope.rb +98 -91
  84. data/lib/contrast/config/agent_configuration.rb +58 -12
  85. data/lib/contrast/config/api_configuration.rb +100 -12
  86. data/lib/contrast/config/api_proxy_configuration.rb +55 -3
  87. data/lib/contrast/config/application_configuration.rb +114 -15
  88. data/lib/contrast/config/assess_configuration.rb +106 -12
  89. data/lib/contrast/config/assess_rules_configuration.rb +44 -3
  90. data/lib/contrast/config/base_configuration.rb +1 -0
  91. data/lib/contrast/config/certification_configuration.rb +74 -3
  92. data/lib/contrast/config/exception_configuration.rb +61 -3
  93. data/lib/contrast/config/heap_dump_configuration.rb +101 -17
  94. data/lib/contrast/config/inventory_configuration.rb +64 -3
  95. data/lib/contrast/config/logger_configuration.rb +46 -3
  96. data/lib/contrast/config/protect_rule_configuration.rb +36 -9
  97. data/lib/contrast/config/protect_rules_configuration.rb +120 -17
  98. data/lib/contrast/config/request_audit_configuration.rb +68 -3
  99. data/lib/contrast/config/ruby_configuration.rb +96 -22
  100. data/lib/contrast/config/sampling_configuration.rb +76 -10
  101. data/lib/contrast/config/server_configuration.rb +56 -11
  102. data/lib/contrast/configuration.rb +6 -3
  103. data/lib/contrast/logger/cef_log.rb +151 -0
  104. data/lib/contrast/utils/hash_digest.rb +14 -6
  105. data/lib/contrast/utils/log_utils.rb +114 -0
  106. data/lib/contrast/utils/middleware_utils.rb +6 -7
  107. data/lib/contrast/utils/net_http_base.rb +12 -9
  108. data/lib/contrast/utils/patching/policy/patch_utils.rb +0 -4
  109. data/lib/contrast.rb +4 -3
  110. data/ruby-agent.gemspec +1 -1
  111. data/service_executables/VERSION +1 -1
  112. data/service_executables/linux/contrast-service +0 -0
  113. data/service_executables/mac/contrast-service +0 -0
  114. metadata +41 -21
  115. data/lib/contrast/agent/assess/rule/response/cachecontrol_rule.rb +0 -184
  116. data/lib/contrast/agent/assess/rule/response/clickjacking_rule.rb +0 -66
  117. data/lib/contrast/agent/assess/rule/response/x_content_type_rule.rb +0 -52
  118. data/lib/contrast/agent/assess/rule/response/x_xss_protection_rule.rb +0 -53
  119. data/lib/contrast/extension/kernel.rb +0 -54
@@ -10,6 +10,7 @@ require 'contrast/components/scope'
10
10
  require 'contrast/utils/request_utils'
11
11
  require 'contrast/agent/request_context_extend'
12
12
  require 'contrast/agent/reporting/reporting_events/observed_route'
13
+ require 'contrast/agent/reporting/input_analysis/input_analysis'
13
14
 
14
15
  module Contrast
15
16
  module Agent
@@ -36,9 +37,10 @@ module Contrast
36
37
  include Contrast::Agent::RequestContextExtend
37
38
 
38
39
  EMPTY_INPUT_ANALYSIS_PB = Contrast::Api::Settings::InputAnalysis.new
40
+ INPUT_ANALYSIS = Contrast::Agent::Reporting::InputAnalysis.new
39
41
 
40
42
  attr_reader :activity, :logging_hash, :observed_route, :new_observed_route, :request, :response, :route,
41
- :speedracer_input_analysis, :server_activity, :timer
43
+ :speedracer_input_analysis, :agent_input_analysis, :server_activity, :timer
42
44
 
43
45
  def initialize rack_request, app_loaded = true
44
46
  with_contrast_scope do
@@ -59,6 +61,9 @@ module Contrast
59
61
  @speedracer_input_analysis = EMPTY_INPUT_ANALYSIS_PB
60
62
  speedracer_input_analysis.request = request
61
63
 
64
+ @agent_input_analysis = INPUT_ANALYSIS
65
+ agent_input_analysis.request = request
66
+
62
67
  # flag to indicate whether the app is fully loaded
63
68
  @app_loaded = !!app_loaded
64
69
 
@@ -1,19 +1,27 @@
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/rule/response/autocomplete_rule'
4
+ require 'contrast/agent/assess/rule/response/auto_complete_rule'
5
+ require 'contrast/agent/assess/rule/response/cache_control_header_rule'
6
+ require 'contrast/agent/assess/rule/response/click_jacking_header_rule'
7
+ require 'contrast/agent/assess/rule/response/csp_header_insecure_rule'
8
+ require 'contrast/agent/assess/rule/response/csp_header_missing_rule'
5
9
  require 'contrast/agent/assess/rule/response/hsts_header_rule'
6
- require 'contrast/agent/assess/rule/response/cachecontrol_rule'
7
- require 'contrast/agent/assess/rule/response/clickjacking_rule'
8
- require 'contrast/agent/assess/rule/response/x_content_type_rule'
9
10
  require 'contrast/agent/assess/rule/response/parameters_pollution_rule'
11
+ require 'contrast/agent/assess/rule/response/x_content_type_header_rule'
12
+ require 'contrast/agent/assess/rule/response/x_xss_protection_header_rule'
13
+ require 'contrast/agent/protect/input_analyzer/input_analyzer'
14
+ require 'contrast/components/logger'
15
+ require 'contrast/utils/log_utils'
10
16
 
11
17
  module Contrast
12
18
  module Agent
13
19
  # This class extends RequestContexts: this class acts to encapsulate information about the currently
14
20
  # executed request, making it available to the Agent for the duration of the request in a standardized
15
21
  # and normalized format which the Agent understands.
16
- module RequestContextExtend
22
+ module RequestContextExtend # rubocop:disable Metrics/ModuleLength
23
+ include Contrast::Utils::CEFLogUtils
24
+ include Contrast::Components::Logger::InstanceMethods
17
25
  BUILD_ATTACK_LOGGER_MESSAGE = 'Building attack result from Contrast Service input analysis result'
18
26
  # Convert the discovered route for this request to appropriate forms and disseminate it to those locations
19
27
  # where it is necessary for our route coverage and finding vulnerability discovery features to function.
@@ -74,10 +82,10 @@ module Contrast
74
82
  handle_protect_state(service_response)
75
83
  ia = service_response.input_analysis
76
84
  if ia
77
- if logger.trace?
78
- logger.trace('Analysis from Contrast Service', evaluations: ia.results.length)
79
- logger.trace('Results', input_analysis: ia.inspect)
80
- end
85
+ service_extract_logging ia
86
+ # using Agent analysis
87
+ initialize_agent_input_analysis request
88
+
81
89
  @speedracer_input_analysis = ia
82
90
  speedracer_input_analysis.request = request
83
91
  else
@@ -113,23 +121,41 @@ module Contrast
113
121
  # append anything we've learned to the request seen message this is the sum-total of all inventory information
114
122
  # that has been accumulated since the last request
115
123
  def extract_after rack_response
124
+ # We must ALWAYS save the response, even if we don't need it here for response sampling. It is used for other
125
+ # vulnerability detection, most notably XSS, and not capturing it may suppress valid findings.
116
126
  @response = Contrast::Agent::Response.new(rack_response)
117
- activity.http_response = @response.dtm if @sample_res
118
- return unless Contrast::Agent::Reporter.enabled?
119
-
120
- Contrast::Agent::Assess::Rule::Response::Autocomplete.new.analyze(@response)
121
- Contrast::Agent::Assess::Rule::Response::HSTSHeader.new.analyze(@response)
122
- Contrast::Agent::Assess::Rule::Response::Cachecontrol.new.analyze(@response)
123
- Contrast::Agent::Assess::Rule::Response::XXssProtection.new.analyze(@response)
124
- Contrast::Agent::Assess::Rule::Response::CspHeaderMissing.new.analyze(@response)
125
- Contrast::Agent::Assess::Rule::Response::CspHeaderInsecure.new.analyze(@response)
126
- Contrast::Agent::Assess::Rule::Response::Clickjacking.new.analyze(@response)
127
- Contrast::Agent::Assess::Rule::Response::XContentType.new.analyze(@response)
128
- Contrast::Agent::Assess::Rule::Response::ParametersPollution.new.analyze(@response)
127
+ return unless @sample_res
128
+
129
+ # TODO: RUBY-1376 once all rules translated, move this to if/else w/ the enabled
130
+ if Contrast::Agent::Reporter.enabled?
131
+ Contrast::Agent::Assess::Rule::Response::AutoComplete.new.analyze(@response)
132
+ Contrast::Agent::Assess::Rule::Response::CacheControl.new.analyze(@response)
133
+ Contrast::Agent::Assess::Rule::Response::ClickJacking.new.analyze(@response)
134
+ Contrast::Agent::Assess::Rule::Response::CspHeaderMissing.new.analyze(@response)
135
+ Contrast::Agent::Assess::Rule::Response::CspHeaderInsecure.new.analyze(@response)
136
+ Contrast::Agent::Assess::Rule::Response::HSTSHeader.new.analyze(@response)
137
+ Contrast::Agent::Assess::Rule::Response::ParametersPollution.new.analyze(@response)
138
+ Contrast::Agent::Assess::Rule::Response::XContentType.new.analyze(@response)
139
+ Contrast::Agent::Assess::Rule::Response::XXssProtection.new.analyze(@response)
140
+ else
141
+ activity.http_response = @response.dtm
142
+ end
129
143
  rescue StandardError => e
130
144
  logger.error('Unable to extract information after request', e)
131
145
  end
132
146
 
147
+ # This here is for things we don't have implemented
148
+ def log_to_cef
149
+ activity.results.each { |attack_result| logging_logic attack_result, attack_result.rule_id.downcase }
150
+ end
151
+
152
+ # @param input_analysis [Contrast::Api::Settings::InputAnalysis]
153
+ def service_extract_logging input_analysis
154
+ log_to_cef
155
+ logger.trace('Analysis from Contrast Service', evaluations: input_analysis.results.length) if logger.trace?
156
+ logger.trace('Results', input_analysis: input_analysis.inspect) if logger.trace?
157
+ end
158
+
133
159
  private
134
160
 
135
161
  # Generate attack results directly from any evaluations on the agent settings object.
@@ -171,6 +197,44 @@ module Contrast
171
197
  rule.build_attack_without_match(self, ia_result, results_by_rule[rule_id])
172
198
  end
173
199
  end
200
+
201
+ # Sets request to be used with agent and service input analysis.
202
+ #
203
+ # @param request [Contrast::Agent::Request] our wrapper around the Rack::Request
204
+ # for this context
205
+ def initialize_agent_input_analysis request
206
+ # using Agent analysis
207
+ ia = Contrast::Agent::Protect::InputAnalyzer.analyse request
208
+ if ia
209
+ @agent_input_analysis = ia
210
+ else
211
+ logger.trace('Analysis from Agent was empty.')
212
+
213
+ end
214
+ end
215
+
216
+ def logging_logic result, rule_id
217
+ rules = %w[bot_blocker virtual_patch ip_denylist]
218
+ return unless rules.include?(rule_id)
219
+
220
+ rule_details = Contrast::Api::Dtm::RaspRuleSample.to_controlled_hash(result.samples[0]).fetch(rule_id.to_sym)
221
+ outcome = Contrast::Api::Dtm::AttackResult::ResponseType.get_name_by_tag(result.response)
222
+ case rule_id
223
+ when /bot_blocker/i
224
+ blocker_to_json = Contrast::Api::Dtm::BotBlockerDetails.to_controlled_hash rule_details
225
+ cef_logger.bot_blocking_message(blocker_to_json, outcome)
226
+ when /virtual_patch/i
227
+ virtual_patch_to_json = Contrast::Api::Dtm::VirtualPatchDetails.to_controlled_hash rule_details
228
+ cef_logger.virtual_patch_message(virtual_patch_to_json, outcome)
229
+ when /ip_denylist/i
230
+ sender_ip = extract_sender_ip
231
+ ip_denylist_to_json = Contrast::Api::Dtm::IpDenylistDetails.to_controlled_hash rule_details
232
+ return unless sender_ip
233
+ return unless sender_ip.include?(ip_denylist_to_json[:ip])
234
+
235
+ cef_logger.ip_denylisted_message(sender_ip, ip_denylist_to_json, outcome)
236
+ end
237
+ end
174
238
  end
175
239
  end
176
240
  end
@@ -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 'cs__scope/cs__scope'
5
+
4
6
  module Contrast
5
7
  module Agent
6
8
  # Scope lets us disable Contrast for certain code calls. We need to do this so
@@ -14,75 +16,100 @@ module Contrast
14
16
  #
15
17
  # Instead, we should say "If I'm already doing Contrast things, don't track
16
18
  # this"
19
+ #
20
+ # Scope Exits...
21
+ # by design, can go below zero.
22
+ # every exit/enter pair (regardless of series)
23
+ # should cancel each other out.
24
+ #
25
+ # so we prefer this sequence:
26
+ # scope = 0
27
+ # exit = -1
28
+ # enter = 0
29
+ # enter = 1
30
+ # exit = 0
31
+ # scope = 0
32
+ #
33
+ # over this sequence:
34
+ # scope = 0
35
+ # exit = 0
36
+ # enter = 1
37
+ # enter = 2
38
+ # exit = 1
39
+ # scope = 1
40
+ #
41
+ # This class have been moved to C and it's called from there. Here remains
42
+ # the validation and wrapper methods.
43
+ #
44
+ # Methods defined in C:
45
+ #
46
+ # sets scope instance variables.
47
+ # def initialize end;
48
+ #
49
+ # Check if we are in contrast scope.
50
+ # def in_contrast_scope? end;
51
+ # @return [Boolean] check if we are in contrast scope
52
+ # if the scope is above 0 return true.
53
+ #
54
+ # check if we are in deserialization scope.
55
+ # def in_deserialization_scope? end;
56
+ # @return [Boolean] check if we are in contrast scope
57
+ # if the scope is above 0 return true.
58
+ #
59
+ # check if we are in split scope.
60
+ # def in_split_scope? end;
61
+ # @return [Boolean] check if we are in contrast scope
62
+ # if the scope is above 0 return true.
63
+ #
64
+ # enter contrast scope.
65
+ # def enter_contrast_scope! end;
66
+ # @return @contrast_scope [Integer] contrast scope increased.
67
+ #
68
+ # enter deserialization scope.
69
+ # def enter_deserialization_scope! end;
70
+ # @return @deserialization_scope [Integer] deserialization scope increased.
71
+ #
72
+ # enter split scope.
73
+ # def enter_split_scope! end;
74
+ # @return @split_scope [Integer] split scope increased.
75
+ #
76
+ # check split scope depth.
77
+ # def split_scope_depth end;
78
+ # @return @split_scope [Integer] split scope depth.
79
+ #
80
+ # exit contrast scope.
81
+ # def exit_contrast_scope! end;
82
+ # @return @contrast_scope [Integer] contrast scope decreased.
83
+ #
84
+ # exit deserialization scope.
85
+ # def exit_deserialization_scope! end;
86
+ # @return @deserialization_scope [Integer] deserialization scope decreased.
87
+ #
88
+ # exit split scope.
89
+ # def exit_split_scope! end;
90
+ # @return @split_scope [Integer] split scope decreased.
91
+ #
92
+ # Static methods to be used, the cases are defined by the usage from the above methods
93
+ #
94
+ # check if we are in specific scope.
95
+ # def in_scope? name end;
96
+ # @param name [Symbol] scope symbol representing scope to check.
97
+ # @return [Boolean] check if we are in passed scope.
98
+ #
99
+ # enter specific scope.
100
+ # def enter_scope! name end;
101
+ # @param name [Symbol] scope symbol representing scope to enter.
102
+ # @return scope [Integer] entered scope value increased.
103
+ #
104
+ # exit specific scope.
105
+ # def exit_cope! name end;
106
+ # @param name [Symbol] scope symbol representing scope to exit.
107
+ # @return scope [Integer] entered scope value decreased.
17
108
  class Scope
18
109
  SCOPE_LIST = %i[contrast deserialization split].cs__freeze
19
110
 
20
- def initialize
21
- @contrast_scope = 0
22
- @deserialization_scope = 0
23
- @split_scope = 0
24
- end
25
-
26
- def in_contrast_scope?
27
- @contrast_scope.positive?
28
- end
29
-
30
- def in_deserialization_scope?
31
- @deserialization_scope.positive?
32
- end
33
-
34
- def in_split_scope?
35
- @split_scope.positive?
36
- end
37
-
38
- def enter_contrast_scope!
39
- @contrast_scope += 1
40
- end
41
-
42
- def enter_deserialization_scope!
43
- @deserialization_scope += 1
44
- end
45
-
46
- def enter_split_scope!
47
- @split_scope += 1
48
- end
49
-
50
- def split_scope_depth
51
- @split_scope
52
- end
53
-
54
- # Scope Exits...
55
- # by design, can go below zero.
56
- # every exit/enter pair (regardless of series)
57
- # should cancel each other out.
58
- #
59
- # so we prefer this sequence:
60
- # scope = 0
61
- # exit = -1
62
- # enter = 0
63
- # enter = 1
64
- # exit = 0
65
- # scope = 0
66
- #
67
- # over this sequence:
68
- # scope = 0
69
- # exit = 0
70
- # enter = 1
71
- # enter = 2
72
- # exit = 1
73
- # scope = 1
74
- def exit_contrast_scope!
75
- @contrast_scope -= 1
76
- end
77
-
78
- def exit_deserialization_scope!
79
- @deserialization_scope -= 1
80
- end
81
-
82
- def exit_split_scope!
83
- @split_scope -= 1
84
- end
85
-
111
+ # Wraps block to be executed in contrast scope.
112
+ # On completion exits scope.
86
113
  def with_contrast_scope
87
114
  enter_contrast_scope!
88
115
  yield
@@ -90,6 +117,8 @@ module Contrast
90
117
  exit_contrast_scope!
91
118
  end
92
119
 
120
+ # Wraps block to be executed in deserialization scope.
121
+ # On completion exits scope.
93
122
  def with_deserialization_scope
94
123
  enter_deserialization_scope!
95
124
  yield
@@ -97,6 +126,8 @@ module Contrast
97
126
  exit_deserialization_scope!
98
127
  end
99
128
 
129
+ # Wraps block to be executed in split scope.
130
+ # On completion exits scope.
100
131
  def with_split_scope
101
132
  enter_split_scope!
102
133
  yield
@@ -104,48 +135,12 @@ module Contrast
104
135
  exit_split_scope!
105
136
  end
106
137
 
107
- # Static methods to be used, the cases are defined by the usage from the above methods
108
- # if more methods are added - please extend the case statements as they are no longed dynamic
109
- def in_scope? name
110
- case name
111
- when :contrast
112
- in_contrast_scope?
113
- when :deserialization
114
- in_deserialization_scope?
115
- when :split
116
- in_split_scope?
117
- else
118
- raise NoMethodError, "Scope '#{ name.inspect }' is not registered as a scope."
119
- end
120
- end
121
-
122
- def enter_scope! name
123
- case name
124
- when :contrast
125
- enter_contrast_scope!
126
- when :deserialization
127
- enter_deserialization_scope!
128
- when :split
129
- enter_split_scope!
130
- else
131
- raise NoMethodError, "Scope '#{ name.inspect }' is not registered as a scope."
132
- end
133
- end
134
-
135
- def exit_scope! name
136
- case name
137
- when :contrast
138
- exit_contrast_scope!
139
- when :deserialization
140
- exit_deserialization_scope!
141
- when :split
142
- exit_split_scope!
143
- else
144
- raise NoMethodError, "Scope '#{ name.inspect }' is not registered as a scope."
145
- end
146
- end
147
-
148
138
  class << self
139
+ # Validates scope. To be valid the scope must be one of:
140
+ # :contrast, :split, :deserialization
141
+ #
142
+ # @param scope_sym [Symbol] scope to check.
143
+ # @return [Boolean] true | false
149
144
  def valid_scope? scope_sym
150
145
  Contrast::Agent::Scope::SCOPE_LIST.include? scope_sym
151
146
  end
@@ -3,6 +3,7 @@
3
3
 
4
4
  require 'contrast/components/logger'
5
5
  require 'contrast/agent/worker_thread'
6
+ require 'contrast/agent/reporting/report'
6
7
 
7
8
  module Contrast
8
9
  module Agent
@@ -14,13 +15,35 @@ module Contrast
14
15
  # Spec recommends 30 seconds, we're going with 15.
15
16
  REFRESH_INTERVAL_SEC = 15
16
17
 
18
+ # check if we can report to TS
19
+ #
20
+ # @return[Boolean] true if bypass is enabled, or false if bypass disabled
21
+ def enabled?
22
+ @_enabled = Contrast::CONTRAST_SERVICE.use_agent_communication? if @_enabled.nil?
23
+ @_enabled
24
+ end
25
+
26
+ def client
27
+ @_client ||= Contrast::Agent::Reporting::ReporterClient.new
28
+ end
29
+
30
+ def connection
31
+ @_connection ||= client.initialize_connection
32
+ end
33
+
17
34
  def start_thread!
18
35
  return if running?
19
36
 
37
+ report_from_reporter = check_report_provider
38
+
20
39
  @_thread = Contrast::Agent::Thread.new do
21
40
  logger.info('Starting heartbeat thread.')
22
41
  loop do
23
- Contrast::Agent.messaging_queue.send_event_eventually(poll_message)
42
+ if report_from_reporter
43
+ client.send_event(poll_message, connection)
44
+ else
45
+ Contrast::Agent.messaging_queue.send_event_eventually(poll_message)
46
+ end
24
47
 
25
48
  sleep REFRESH_INTERVAL_SEC
26
49
  end
@@ -28,7 +51,27 @@ module Contrast
28
51
  end
29
52
 
30
53
  def poll_message
31
- @_poll_message ||= Contrast::Api::Dtm::Poll.new
54
+ @_poll_message ||= if enabled? && client
55
+ Contrast::Agent::Reporting::Poll.new
56
+ else
57
+ Contrast::Api::Dtm::Poll.new
58
+ end
59
+ end
60
+
61
+ def check_report_provider
62
+ return false unless enabled?
63
+ return false unless client && connection
64
+
65
+ client.startup!(connection)
66
+ true
67
+ end
68
+
69
+ def send_event provider
70
+ if provider
71
+ client.send_event(poll_message, connection)
72
+ return
73
+ end
74
+ Contrast::Agent.messaging_queue.send_event_eventually(poll_message)
32
75
  end
33
76
  end
34
77
  end
@@ -3,6 +3,6 @@
3
3
 
4
4
  module Contrast
5
5
  module Agent
6
- VERSION = '5.1.0'
6
+ VERSION = '5.2.0'
7
7
  end
8
8
  end
@@ -0,0 +1,37 @@
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/api/dtm.pb'
5
+ require 'contrast/utils/string_utils'
6
+ require 'contrast/components/base'
7
+
8
+ module Contrast
9
+ module Api
10
+ module Decorators
11
+ # Used to decorate the {Contrast::Api::Dtm::BotBlockerDetails} protobuf
12
+ # model so it can own the request which its data is for.
13
+ module BotBlockerDetails
14
+ def self.included klass
15
+ klass.extend(ClassMethods)
16
+ end
17
+
18
+ # Used to add class methods to the AgentStartup class on inclusion of the decorator
19
+ module ClassMethods
20
+ def build
21
+ new
22
+ end
23
+
24
+ # @param result [Contrast::Api::Dtm::BotBlockerDetails]
25
+ def to_controlled_hash result
26
+ {
27
+ bot: result.bot,
28
+ user_agent: result.user_agent
29
+ }
30
+ end
31
+ end
32
+ end
33
+ end
34
+ end
35
+ end
36
+
37
+ Contrast::Api::Dtm::BotBlockerDetails.include(Contrast::Api::Decorators::BotBlockerDetails)
@@ -0,0 +1,37 @@
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/api/dtm.pb'
5
+ require 'contrast/utils/string_utils'
6
+ require 'contrast/components/base'
7
+
8
+ module Contrast
9
+ module Api
10
+ module Decorators
11
+ # Used to decorate the {Contrast::Api::Dtm::IpDenylistDetails} protobuf
12
+ # model so it can own the request which its data is for.
13
+ module IpDenylistDetails
14
+ def self.included klass
15
+ klass.extend(ClassMethods)
16
+ end
17
+
18
+ # Used to add class methods to the AgentStartup class on inclusion of the decorator
19
+ module ClassMethods
20
+ def build
21
+ new
22
+ end
23
+
24
+ # @param result [Contrast::Api::Dtm::IpDenylistDetails]
25
+ def to_controlled_hash result
26
+ {
27
+ ip: result.ip,
28
+ uuid: result.uuid
29
+ }
30
+ end
31
+ end
32
+ end
33
+ end
34
+ end
35
+ end
36
+
37
+ Contrast::Api::Dtm::IpDenylistDetails.include(Contrast::Api::Decorators::IpDenylistDetails)
@@ -20,6 +20,35 @@ module Contrast
20
20
  sample.user_input.document_type = Contrast::Utils::StringUtils.force_utf8(context.request.document_type)
21
21
  sample
22
22
  end
23
+
24
+ # @param result [Contrast::Api::Dtm::RaspRuleSample]
25
+ def to_controlled_hash result
26
+ {
27
+ timestamp: Time.at(result.timestamp_ms).iso8601,
28
+ user_input: result.user_input,
29
+ brute_force: result.brute_force,
30
+ bot_blocker: result.bot_blocker,
31
+ cmdi: result.cmdi,
32
+ csrf: result.csrf,
33
+ cve: result.cve,
34
+ untrusted_deserialization: result.untrusted_deserialization,
35
+ el_injection: result.el_injection,
36
+ mark_of_the_beast: result.mark_of_the_beast,
37
+ padding_oracle: result.padding_oracle,
38
+ path_traversal: result.path_traversal,
39
+ re_dos: result.re_dos,
40
+ sqli: result.sqli,
41
+ ssrf: result.ssrf,
42
+ virtual_patch: result.virtual_patch,
43
+ xss: result.xss,
44
+ xxe: result.xxe,
45
+ no_sqli: result.no_sqli,
46
+ method_tampering: result.method_tampering,
47
+ path_traversal_semantic: result.path_traversal_semantic,
48
+ ssjs: result.ssjs,
49
+ ip_denylist: result.ip_denylist
50
+ }
51
+ end
23
52
  end
24
53
  end
25
54
  end
@@ -25,11 +25,21 @@ module Contrast
25
25
  return UNKNOWN_USER_INPUT.dup unless ia_result
26
26
 
27
27
  user_input = new
28
- user_input.input_type = ia_result.input_type.to_i
29
28
  user_input.matcher_ids = ia_result.ids
30
29
  user_input.path = ia_result.path.to_s
31
30
  user_input.key = ia_result.key.to_s
32
31
  user_input.value = ia_result.value.to_s
32
+ if ia_result.input_type
33
+ #
34
+ # InputAnalysis have local Agent implementation, so we need ot take care of difference
35
+ # if we pass data from wrong place - we need to handle the TypeError in throws
36
+ begin
37
+ user_input.input_type = ia_result.input_type.to_i
38
+ rescue TypeError, NoMethodError => _e
39
+ user_input.input_type = ia_result.input_type
40
+ end
41
+ end
42
+
33
43
  user_input
34
44
  end
35
45
  end
@@ -0,0 +1,34 @@
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/api/dtm.pb'
5
+ require 'contrast/utils/string_utils'
6
+ require 'contrast/components/base'
7
+
8
+ module Contrast
9
+ module Api
10
+ module Decorators
11
+ # Used to decorate the {Contrast::Api::Dtm::VirtualPatchDetails} protobuf
12
+ # model so it can own the request which its data is for.
13
+ module VirtualPatchDetails
14
+ def self.included klass
15
+ klass.extend(ClassMethods)
16
+ end
17
+
18
+ # Used to add class methods to the AgentStartup class on inclusion of the decorator
19
+ module ClassMethods
20
+ def build
21
+ new
22
+ end
23
+
24
+ # @param result [Contrast::Api::Dtm::VirtualPatchDetails]
25
+ def to_controlled_hash result
26
+ { uuid: result.uuid }
27
+ end
28
+ end
29
+ end
30
+ end
31
+ end
32
+ end
33
+
34
+ Contrast::Api::Dtm::VirtualPatchDetails.include(Contrast::Api::Decorators::VirtualPatchDetails)