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.
- checksums.yaml +4 -4
- data/ext/cs__assess_kernel/cs__assess_kernel.c +7 -4
- data/ext/cs__assess_module/cs__assess_module.c +7 -7
- data/ext/cs__common/cs__common.c +4 -0
- data/ext/cs__common/cs__common.h +1 -0
- data/ext/cs__contrast_patch/cs__contrast_patch.c +52 -27
- data/ext/cs__contrast_patch/cs__contrast_patch.h +2 -0
- data/ext/cs__scope/cs__scope.c +747 -0
- data/ext/cs__scope/cs__scope.h +88 -0
- data/ext/cs__scope/extconf.rb +5 -0
- data/lib/contrast/agent/assess/contrast_event.rb +20 -13
- data/lib/contrast/agent/assess/contrast_object.rb +4 -1
- data/lib/contrast/agent/assess/policy/propagation_node.rb +2 -5
- data/lib/contrast/agent/assess/policy/propagator/match_data.rb +2 -0
- data/lib/contrast/agent/assess/policy/trigger_method.rb +4 -1
- data/lib/contrast/agent/assess/rule/response/{autocomplete_rule.rb → auto_complete_rule.rb} +4 -3
- data/lib/contrast/agent/assess/rule/response/base_rule.rb +12 -79
- data/lib/contrast/agent/assess/rule/response/body_rule.rb +109 -0
- data/lib/contrast/agent/assess/rule/response/cache_control_header_rule.rb +157 -0
- data/lib/contrast/agent/assess/rule/response/click_jacking_header_rule.rb +26 -0
- data/lib/contrast/agent/assess/rule/response/csp_header_insecure_rule.rb +14 -15
- data/lib/contrast/agent/assess/rule/response/csp_header_missing_rule.rb +5 -25
- data/lib/contrast/agent/assess/rule/response/framework/rails_support.rb +29 -0
- data/lib/contrast/agent/assess/rule/response/header_rule.rb +70 -0
- data/lib/contrast/agent/assess/rule/response/hsts_header_rule.rb +12 -36
- data/lib/contrast/agent/assess/rule/response/parameters_pollution_rule.rb +2 -1
- data/lib/contrast/agent/assess/rule/response/x_content_type_header_rule.rb +26 -0
- data/lib/contrast/agent/assess/rule/response/x_xss_protection_header_rule.rb +36 -0
- data/lib/contrast/agent/middleware.rb +1 -0
- data/lib/contrast/agent/patching/policy/after_load_patcher.rb +1 -3
- data/lib/contrast/agent/patching/policy/patch.rb +2 -6
- data/lib/contrast/agent/patching/policy/patcher.rb +1 -1
- data/lib/contrast/agent/protect/input_analyzer/input_analyzer.rb +94 -0
- data/lib/contrast/agent/protect/rule/base.rb +28 -1
- data/lib/contrast/agent/protect/rule/base_service.rb +10 -1
- data/lib/contrast/agent/protect/rule/cmd_injection.rb +2 -0
- data/lib/contrast/agent/protect/rule/deserialization.rb +6 -0
- data/lib/contrast/agent/protect/rule/http_method_tampering.rb +5 -1
- data/lib/contrast/agent/protect/rule/no_sqli.rb +1 -0
- data/lib/contrast/agent/protect/rule/path_traversal.rb +1 -0
- data/lib/contrast/agent/protect/rule/sqli/sqli_input_classification.rb +124 -0
- data/lib/contrast/agent/protect/rule/sqli/sqli_worth_watching.rb +121 -0
- data/lib/contrast/agent/protect/rule/sqli.rb +33 -0
- data/lib/contrast/agent/protect/rule/xxe.rb +4 -0
- data/lib/contrast/agent/reporting/input_analysis/input_analysis.rb +44 -0
- data/lib/contrast/agent/reporting/input_analysis/input_analysis_result.rb +115 -0
- data/lib/contrast/agent/reporting/input_analysis/input_type.rb +44 -0
- data/lib/contrast/agent/reporting/input_analysis/score_level.rb +21 -0
- data/lib/contrast/agent/reporting/report.rb +1 -0
- data/lib/contrast/agent/reporting/reporter.rb +8 -1
- data/lib/contrast/agent/reporting/reporting_events/finding.rb +69 -36
- data/lib/contrast/agent/reporting/reporting_events/finding_event.rb +88 -59
- data/lib/contrast/agent/reporting/reporting_events/{finding_object.rb → finding_event_object.rb} +24 -20
- data/lib/contrast/agent/reporting/reporting_events/finding_event_parent_object.rb +39 -0
- data/lib/contrast/agent/reporting/reporting_events/finding_event_property.rb +40 -0
- data/lib/contrast/agent/reporting/reporting_events/{finding_signature.rb → finding_event_signature.rb} +29 -24
- data/lib/contrast/agent/reporting/reporting_events/finding_event_source.rb +12 -8
- data/lib/contrast/agent/reporting/reporting_events/{finding_stack.rb → finding_event_stack.rb} +23 -19
- data/lib/contrast/agent/reporting/reporting_events/{finding_taint_range.rb → finding_event_taint_range.rb} +17 -15
- data/lib/contrast/agent/reporting/reporting_events/finding_request.rb +26 -53
- data/lib/contrast/agent/reporting/reporting_events/poll.rb +29 -0
- data/lib/contrast/agent/reporting/reporting_events/reporting_event.rb +5 -4
- data/lib/contrast/agent/reporting/reporting_events/route_discovery.rb +1 -0
- data/lib/contrast/agent/reporting/reporting_events/server_activity.rb +1 -1
- data/lib/contrast/agent/reporting/reporting_utilities/audit.rb +10 -3
- data/lib/contrast/agent/reporting/reporting_utilities/endpoints.rb +0 -1
- data/lib/contrast/agent/reporting/reporting_utilities/reporter_client.rb +1 -0
- data/lib/contrast/agent/reporting/reporting_utilities/reporter_client_utils.rb +28 -20
- data/lib/contrast/agent/reporting/reporting_utilities/response_handler.rb +1 -1
- data/lib/contrast/agent/reporting/reporting_utilities/response_handler_utils.rb +13 -1
- data/lib/contrast/agent/request_context.rb +6 -1
- data/lib/contrast/agent/request_context_extend.rb +85 -21
- data/lib/contrast/agent/scope.rb +102 -107
- data/lib/contrast/agent/service_heartbeat.rb +45 -2
- data/lib/contrast/agent/version.rb +1 -1
- data/lib/contrast/api/decorators/bot_blocker.rb +37 -0
- data/lib/contrast/api/decorators/ip_denylist.rb +37 -0
- data/lib/contrast/api/decorators/rasp_rule_sample.rb +29 -0
- data/lib/contrast/api/decorators/user_input.rb +11 -1
- data/lib/contrast/api/decorators/virtual_patch.rb +34 -0
- data/lib/contrast/components/logger.rb +5 -0
- data/lib/contrast/components/protect.rb +4 -2
- data/lib/contrast/components/scope.rb +98 -91
- data/lib/contrast/config/agent_configuration.rb +58 -12
- data/lib/contrast/config/api_configuration.rb +100 -12
- data/lib/contrast/config/api_proxy_configuration.rb +55 -3
- data/lib/contrast/config/application_configuration.rb +114 -15
- data/lib/contrast/config/assess_configuration.rb +106 -12
- data/lib/contrast/config/assess_rules_configuration.rb +44 -3
- data/lib/contrast/config/base_configuration.rb +1 -0
- data/lib/contrast/config/certification_configuration.rb +74 -3
- data/lib/contrast/config/exception_configuration.rb +61 -3
- data/lib/contrast/config/heap_dump_configuration.rb +101 -17
- data/lib/contrast/config/inventory_configuration.rb +64 -3
- data/lib/contrast/config/logger_configuration.rb +46 -3
- data/lib/contrast/config/protect_rule_configuration.rb +36 -9
- data/lib/contrast/config/protect_rules_configuration.rb +120 -17
- data/lib/contrast/config/request_audit_configuration.rb +68 -3
- data/lib/contrast/config/ruby_configuration.rb +96 -22
- data/lib/contrast/config/sampling_configuration.rb +76 -10
- data/lib/contrast/config/server_configuration.rb +56 -11
- data/lib/contrast/configuration.rb +6 -3
- data/lib/contrast/logger/cef_log.rb +151 -0
- data/lib/contrast/utils/hash_digest.rb +14 -6
- data/lib/contrast/utils/log_utils.rb +114 -0
- data/lib/contrast/utils/middleware_utils.rb +6 -7
- data/lib/contrast/utils/net_http_base.rb +12 -9
- data/lib/contrast/utils/patching/policy/patch_utils.rb +0 -4
- data/lib/contrast.rb +4 -3
- data/ruby-agent.gemspec +1 -1
- data/service_executables/VERSION +1 -1
- data/service_executables/linux/contrast-service +0 -0
- data/service_executables/mac/contrast-service +0 -0
- metadata +41 -21
- data/lib/contrast/agent/assess/rule/response/cachecontrol_rule.rb +0 -184
- data/lib/contrast/agent/assess/rule/response/clickjacking_rule.rb +0 -66
- data/lib/contrast/agent/assess/rule/response/x_content_type_rule.rb +0 -52
- data/lib/contrast/agent/assess/rule/response/x_xss_protection_rule.rb +0 -53
- 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/
|
|
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
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
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
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
Contrast::Agent::
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
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
|
data/lib/contrast/agent/scope.rb
CHANGED
|
@@ -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
|
-
|
|
21
|
-
|
|
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
|
-
|
|
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 ||=
|
|
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
|
|
@@ -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)
|