contrast-agent 6.8.0 → 6.10.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/.gitignore +1 -1
- data/lib/contrast/agent/assess/policy/trigger_method.rb +1 -1
- data/lib/contrast/agent/assess/property/evented.rb +11 -11
- data/lib/contrast/agent/assess/rule/response/body_rule.rb +1 -1
- data/lib/contrast/agent/assess.rb +0 -1
- data/lib/contrast/agent/excluder.rb +1 -1
- data/lib/contrast/agent/middleware.rb +12 -4
- data/lib/contrast/agent/protect/input_analyzer/input_analyzer.rb +76 -83
- data/lib/contrast/agent/protect/input_analyzer/worth_watching_analyzer.rb +121 -0
- data/lib/contrast/agent/protect/policy/applies_command_injection_rule.rb +2 -0
- data/lib/contrast/agent/protect/policy/applies_no_sqli_rule.rb +6 -3
- data/lib/contrast/agent/protect/policy/applies_path_traversal_rule.rb +3 -0
- data/lib/contrast/agent/protect/policy/applies_sqli_rule.rb +3 -0
- data/lib/contrast/agent/protect/policy/rule_applicator.rb +12 -0
- data/lib/contrast/agent/protect/rule/base.rb +21 -7
- data/lib/contrast/agent/protect/rule/base_service.rb +6 -0
- data/lib/contrast/agent/protect/rule/bot_blocker/bot_blocker_input_classification.rb +1 -1
- data/lib/contrast/agent/protect/rule/bot_blocker.rb +9 -1
- data/lib/contrast/agent/protect/rule/cmd_injection.rb +8 -7
- data/lib/contrast/agent/protect/rule/cmdi/cmdi_base_rule.rb +8 -0
- data/lib/contrast/agent/protect/rule/cmdi/cmdi_chained_command.rb +0 -5
- data/lib/contrast/agent/protect/rule/cmdi/cmdi_dangerous_path.rb +0 -5
- data/lib/contrast/agent/protect/rule/deserialization.rb +2 -2
- data/lib/contrast/agent/protect/rule/no_sqli.rb +24 -2
- data/lib/contrast/agent/protect/rule/path_traversal/path_traversal_input_classification.rb +1 -1
- data/lib/contrast/agent/protect/rule/path_traversal.rb +12 -3
- data/lib/contrast/agent/protect/rule/sqli/sqli_input_classification.rb +0 -1
- data/lib/contrast/agent/protect/rule/sqli.rb +10 -13
- data/lib/contrast/agent/protect/rule/unsafe_file_upload/unsafe_file_upload_input_classification.rb +6 -2
- data/lib/contrast/agent/protect/rule/unsafe_file_upload.rb +20 -0
- data/lib/contrast/agent/protect/rule/xss/reflected_xss_input_classification.rb +1 -1
- data/lib/contrast/agent/protect/rule/xss.rb +9 -0
- data/lib/contrast/agent/protect/rule/xxe.rb +2 -2
- data/lib/contrast/agent/protect/rule.rb +0 -3
- data/lib/contrast/agent/reporting/attack_result/rasp_rule_sample.rb +1 -1
- data/lib/contrast/agent/reporting/attack_result/user_input.rb +0 -1
- data/lib/contrast/agent/reporting/details/details.rb +0 -1
- data/lib/contrast/agent/reporting/input_analysis/input_analysis.rb +12 -0
- data/lib/contrast/agent/reporting/report.rb +2 -0
- data/lib/contrast/agent/reporting/reporter.rb +42 -7
- data/lib/contrast/agent/reporting/reporting_events/application_activity.rb +5 -6
- data/lib/contrast/agent/reporting/reporting_events/application_defend_activity.rb +24 -7
- data/lib/contrast/agent/reporting/reporting_events/application_defend_attack_activity.rb +20 -5
- data/lib/contrast/agent/reporting/reporting_events/application_defend_attack_sample.rb +0 -1
- data/lib/contrast/agent/reporting/reporting_events/application_defend_attack_sample_activity.rb +5 -0
- data/lib/contrast/agent/reporting/reporting_events/application_defend_attacker_activity.rb +10 -1
- data/lib/contrast/agent/reporting/reporting_events/application_inventory.rb +2 -1
- data/lib/contrast/agent/reporting/reporting_events/application_inventory_activity.rb +6 -1
- data/lib/contrast/agent/reporting/reporting_events/application_reporting_event.rb +10 -0
- data/lib/contrast/agent/reporting/reporting_events/application_settings.rb +40 -0
- data/lib/contrast/agent/reporting/reporting_events/finding.rb +2 -2
- data/lib/contrast/agent/reporting/reporting_events/finding_event.rb +239 -93
- data/lib/contrast/agent/reporting/reporting_events/finding_event_signature.rb +10 -23
- data/lib/contrast/agent/reporting/reporting_events/finding_event_source.rb +10 -9
- data/lib/contrast/agent/reporting/reporting_events/observed_route.rb +12 -0
- data/lib/contrast/agent/reporting/reporting_events/server_reporting_event.rb +8 -0
- data/lib/contrast/agent/reporting/reporting_events/server_settings.rb +40 -0
- data/lib/contrast/agent/reporting/reporting_utilities/endpoints.rb +6 -0
- data/lib/contrast/agent/reporting/reporting_utilities/ng_response_extractor.rb +137 -0
- data/lib/contrast/agent/reporting/reporting_utilities/reporter_client.rb +52 -2
- data/lib/contrast/agent/reporting/reporting_utilities/reporter_client_utils.rb +8 -4
- data/lib/contrast/agent/reporting/reporting_utilities/response_extractor.rb +105 -58
- data/lib/contrast/agent/reporting/reporting_utilities/response_handler.rb +9 -7
- data/lib/contrast/agent/reporting/reporting_utilities/response_handler_utils.rb +143 -49
- data/lib/contrast/agent/reporting/reporting_workers/application_server_worker.rb +46 -0
- data/lib/contrast/agent/reporting/reporting_workers/reporter_heartbeat.rb +51 -0
- data/lib/contrast/agent/reporting/reporting_workers/reporting_workers.rb +14 -0
- data/lib/contrast/agent/reporting/reporting_workers/server_settings_worker.rb +46 -0
- data/lib/contrast/agent/reporting/settings/assess.rb +14 -1
- data/lib/contrast/agent/reporting/settings/assess_rule.rb +18 -0
- data/lib/contrast/agent/reporting/settings/assess_server_feature.rb +14 -2
- data/lib/contrast/agent/reporting/settings/helpers.rb +9 -0
- data/lib/contrast/agent/reporting/settings/protect.rb +17 -12
- data/lib/contrast/agent/reporting/settings/protect_rule.rb +18 -0
- data/lib/contrast/agent/reporting/settings/protect_server_feature.rb +40 -3
- data/lib/contrast/agent/reporting/settings/rule_definition.rb +3 -0
- data/lib/contrast/agent/reporting/settings/security_logger.rb +77 -0
- data/lib/contrast/agent/reporting/settings/sensitive_data_masking.rb +1 -1
- data/lib/contrast/agent/reporting/settings/server_features.rb +9 -0
- data/lib/contrast/agent/reporting/settings/syslog.rb +34 -5
- data/lib/contrast/agent/reporting/settings/virtual_patch.rb +56 -0
- data/lib/contrast/agent/reporting/settings/virtual_patch_condition.rb +47 -0
- data/lib/contrast/agent/request.rb +1 -0
- data/lib/contrast/agent/request_context_extend.rb +20 -0
- data/lib/contrast/agent/request_handler.rb +5 -10
- data/lib/contrast/agent/telemetry/base.rb +11 -10
- data/lib/contrast/agent/telemetry/events/exceptions/obfuscate.rb +108 -103
- data/lib/contrast/agent/telemetry/events/exceptions/telemetry_exception_event.rb +1 -1
- data/lib/contrast/agent/telemetry/events/startup_metrics_event.rb +1 -1
- data/lib/contrast/agent/thread_watcher.rb +46 -6
- data/lib/contrast/agent/version.rb +1 -1
- data/lib/contrast/agent.rb +18 -0
- data/lib/contrast/agent_lib/api/init.rb +1 -7
- data/lib/contrast/agent_lib/api/input_tracing.rb +2 -4
- data/lib/contrast/agent_lib/interface.rb +1 -16
- data/lib/contrast/agent_lib/interface_base.rb +52 -39
- data/lib/contrast/agent_lib/return_types/eval_result.rb +2 -2
- data/lib/contrast/api/communication/connection_status.rb +15 -0
- data/lib/contrast/components/agent.rb +34 -0
- data/lib/contrast/components/api.rb +23 -0
- data/lib/contrast/components/app_context.rb +23 -3
- data/lib/contrast/components/assess.rb +60 -8
- data/lib/contrast/components/assess_rules.rb +18 -0
- data/lib/contrast/components/base.rb +40 -0
- data/lib/contrast/components/config/sources.rb +95 -0
- data/lib/contrast/components/config.rb +18 -1
- data/lib/contrast/components/heap_dump.rb +10 -0
- data/lib/contrast/components/inventory.rb +15 -2
- data/lib/contrast/components/logger.rb +18 -0
- data/lib/contrast/components/polling.rb +39 -0
- data/lib/contrast/components/protect.rb +48 -1
- data/lib/contrast/components/ruby_component.rb +15 -0
- data/lib/contrast/components/sampling.rb +70 -13
- data/lib/contrast/components/security_logger.rb +13 -0
- data/lib/contrast/components/settings.rb +120 -10
- data/lib/contrast/config/certification_configuration.rb +14 -0
- data/lib/contrast/config/config.rb +46 -0
- data/lib/contrast/config/diagnostics.rb +114 -0
- data/lib/contrast/config/diagnostics_tools.rb +98 -0
- data/lib/contrast/config/effective_config.rb +65 -0
- data/lib/contrast/config/effective_config_value.rb +32 -0
- data/lib/contrast/config/exception_configuration.rb +12 -0
- data/lib/contrast/config/protect_rule_configuration.rb +2 -2
- data/lib/contrast/config/protect_rules_configuration.rb +7 -6
- data/lib/contrast/config/request_audit_configuration.rb +13 -0
- data/lib/contrast/config/server_configuration.rb +41 -2
- data/lib/contrast/configuration.rb +28 -2
- data/lib/contrast/extension/assess/array.rb +3 -3
- data/lib/contrast/extension/assess/erb.rb +1 -1
- data/lib/contrast/extension/assess/regexp.rb +2 -2
- data/lib/contrast/logger/aliased_logging.rb +48 -15
- data/lib/contrast/utils/assess/event_limit_utils.rb +31 -9
- data/lib/contrast/utils/assess/trigger_method_utils.rb +5 -4
- data/lib/contrast/utils/hash_digest.rb +2 -2
- data/lib/contrast/utils/input_classification_base.rb +21 -5
- data/lib/contrast/utils/reporting/application_activity_batch_utils.rb +81 -0
- data/lib/contrast/utils/routes_sent.rb +60 -0
- data/lib/contrast/utils/telemetry.rb +1 -1
- data/lib/contrast/utils/telemetry_client.rb +2 -3
- data/lib/contrast/utils/timer.rb +16 -0
- data/lib/contrast.rb +3 -1
- data/resources/protect/policy.json +8 -0
- data/ruby-agent.gemspec +6 -2
- metadata +43 -24
- data/lib/contrast/agent/assess/contrast_event.rb +0 -157
- data/lib/contrast/agent/assess/events/event_factory.rb +0 -34
- data/lib/contrast/agent/assess/events/source_event.rb +0 -46
- data/lib/contrast/agent/protect/rule/http_method_tampering/http_method_tampering_input_classification.rb +0 -96
- data/lib/contrast/agent/protect/rule/http_method_tampering.rb +0 -83
- data/lib/contrast/agent/reporting/details/http_method_tempering_details.rb +0 -27
- data/lib/contrast/agent/reporting/reporter_heartbeat.rb +0 -53
- data/lib/contrast/agent/reporting/reporting_events/server_activity.rb +0 -36
- data/lib/contrast/agent_lib/api/method_tempering.rb +0 -29
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 624b29e40ff797608bb6fef0ab00377cc1bc3e6756af01063d4768fad53bfbfa
|
|
4
|
+
data.tar.gz: 7380498d855e3f6b8a7387ee98351d118b6b70b7bc332536bf1124a870c1a48c
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 44d0b0cc41d92f58cf048212295fdef8367a4aa4475e3d035c9ae09c80bbcad9de08ba2a63f321381de4af41fd90b581dca2710ef3641d8eb486e8664a3d18cf
|
|
7
|
+
data.tar.gz: cb2f9e2799f8ef7fff7da2ba6714e94022ad5495ddc57c448244c34f649cb70eb756d4f806c1f1274676cbdd37749bf0177cd8539b175ba7b8086857bd1f86a3
|
data/.gitignore
CHANGED
|
@@ -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/
|
|
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::
|
|
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::
|
|
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
|
-
|
|
46
|
-
|
|
47
|
-
|
|
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
|
-
|
|
50
|
-
|
|
48
|
+
next
|
|
49
|
+
end
|
|
51
50
|
|
|
52
|
-
|
|
51
|
+
current_request.observed_route.sources << event_source
|
|
52
|
+
end
|
|
53
53
|
end
|
|
54
54
|
end
|
|
55
55
|
end
|
|
@@ -50,7 +50,7 @@ module Contrast
|
|
|
50
50
|
|
|
51
51
|
potential_elements(section, element_start_str).flatten.each do |potential_element|
|
|
52
52
|
next unless potential_element
|
|
53
|
-
next unless element_openings.any? { |opening| potential_element.
|
|
53
|
+
next unless element_openings.any? { |opening| potential_element.start_with?(opening) }
|
|
54
54
|
|
|
55
55
|
section_start = section.index(element_start_str, section_start)
|
|
56
56
|
next unless section_start
|
|
@@ -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.
|
|
85
|
+
input_match?(exclusion, event_source.source_type, event_source.source_name)
|
|
86
86
|
end
|
|
87
87
|
end
|
|
88
88
|
|
|
@@ -14,8 +14,9 @@ require 'contrast/utils/telemetry'
|
|
|
14
14
|
require 'contrast/agent/request_handler'
|
|
15
15
|
require 'contrast/agent/static_analysis'
|
|
16
16
|
require 'contrast/agent/telemetry/events/startup_metrics_event'
|
|
17
|
+
require 'contrast/agent/protect/input_analyzer/input_analyzer'
|
|
17
18
|
require 'contrast/utils/middleware_utils'
|
|
18
|
-
|
|
19
|
+
require 'contrast/utils/reporting/application_activity_batch_utils'
|
|
19
20
|
require 'contrast/utils/timer'
|
|
20
21
|
|
|
21
22
|
module Contrast
|
|
@@ -23,10 +24,11 @@ module Contrast
|
|
|
23
24
|
# This class allows the Agent to plug into the Rack middleware stack. When the application is first started, we
|
|
24
25
|
# initialize ourselves as a rack middleware inside of #initialize. Afterwards, we process each http request and
|
|
25
26
|
# response as it goes through the middleware stack inside of #call.
|
|
26
|
-
class Middleware
|
|
27
|
+
class Middleware # rubocop:disable Metrics/ClassLength
|
|
27
28
|
include Contrast::Components::Logger::InstanceMethods
|
|
28
29
|
include Contrast::Components::Scope::InstanceMethods
|
|
29
30
|
include Contrast::Utils::MiddlewareUtils
|
|
31
|
+
include Contrast::Utils::Reporting::ApplicationActivityBatchUtils
|
|
30
32
|
|
|
31
33
|
attr_reader :app
|
|
32
34
|
|
|
@@ -62,6 +64,7 @@ module Contrast
|
|
|
62
64
|
# the Rack framework.
|
|
63
65
|
def call env
|
|
64
66
|
logger.trace_with_time('Elapsed time for Contrast::Agent::Middleware#call') do
|
|
67
|
+
::Contrast::Agent::ThreadWatcher.check_before_start
|
|
65
68
|
return app.call(env) unless ::Contrast::AGENT.enabled?
|
|
66
69
|
|
|
67
70
|
Contrast::Agent.heapdump_util.start_thread!
|
|
@@ -169,17 +172,22 @@ module Contrast
|
|
|
169
172
|
with_contrast_scope do
|
|
170
173
|
context.extract_after(response) # update context with final response information
|
|
171
174
|
|
|
172
|
-
# Build and report all collected findings prior response
|
|
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)
|
|
180
|
+
# Now we can build the ia_results only for postfilter rules.
|
|
181
|
+
context.protect_postfilter_ia
|
|
176
182
|
|
|
177
183
|
if Contrast::Agent.framework_manager.streaming?(env)
|
|
178
184
|
context.reset_activity
|
|
179
185
|
request_handler.stream_safe_postfilter
|
|
180
186
|
else
|
|
181
187
|
request_handler.ruleset.postfilter
|
|
182
|
-
request_handler.
|
|
188
|
+
request_handler.report_observed_route
|
|
189
|
+
add_activity_to_batch(context.activity)
|
|
190
|
+
report_batch
|
|
183
191
|
end
|
|
184
192
|
end
|
|
185
193
|
# unsuccessful attack
|
|
@@ -16,7 +16,6 @@ require 'contrast/agent/protect/rule/path_traversal'
|
|
|
16
16
|
require 'contrast/agent/protect/rule/path_traversal/path_traversal_input_classification'
|
|
17
17
|
require 'contrast/agent/protect/rule/xss/reflected_xss_input_classification'
|
|
18
18
|
require 'contrast/agent/protect/rule/xss'
|
|
19
|
-
require 'contrast/agent/protect/rule/http_method_tampering/http_method_tampering_input_classification'
|
|
20
19
|
require 'contrast/components/logger'
|
|
21
20
|
require 'contrast/utils/object_share'
|
|
22
21
|
require 'json'
|
|
@@ -29,7 +28,13 @@ module Contrast
|
|
|
29
28
|
module InputAnalyzer
|
|
30
29
|
DISPOSITION_NAME = 'name'
|
|
31
30
|
DISPOSITION_FILENAME = 'filename'
|
|
32
|
-
|
|
31
|
+
PREFILTER_RULES = %w[bot-blocker unsafe-file-upload reflected-xss].cs__freeze
|
|
32
|
+
INFILTER_RULES = %w[
|
|
33
|
+
sql-injection cmd-injection reflected-xss bot-blocker unsafe-file-upload path-traversal
|
|
34
|
+
nosql-injection
|
|
35
|
+
].cs__freeze
|
|
36
|
+
POSTFILTER_RULES = %w[sql-injection cmd-injection reflected-xss path-traversal nosql-injection].cs__freeze
|
|
37
|
+
AGENTLIB_TIMEOUT = 5.cs__freeze
|
|
33
38
|
|
|
34
39
|
class << self
|
|
35
40
|
include Contrast::Agent::Reporting::InputType
|
|
@@ -37,44 +42,8 @@ module Contrast
|
|
|
37
42
|
include Contrast::Utils::ObjectShare
|
|
38
43
|
include Contrast::Components::Logger::InstanceMethods
|
|
39
44
|
|
|
40
|
-
PROTECT_RULES = {
|
|
41
|
-
sqli: {
|
|
42
|
-
rule_name: 'sql-injection',
|
|
43
|
-
classification: Contrast::Agent::Protect::Rule::SqliInputClassification
|
|
44
|
-
},
|
|
45
|
-
cmdi: {
|
|
46
|
-
rule_name: 'cmd-injection',
|
|
47
|
-
classification: Contrast::Agent::Protect::Rule::CmdiInputClassification
|
|
48
|
-
},
|
|
49
|
-
# method_tampering: {
|
|
50
|
-
# rule_name: 'method-tampering',
|
|
51
|
-
# classification: Contrast::Agent::Protect::Rule::HttpMethodTamperingInputClassification
|
|
52
|
-
# },
|
|
53
|
-
reflected_xss: {
|
|
54
|
-
rule_name: Contrast::Agent::Protect::Rule::Xss::NAME,
|
|
55
|
-
classification: Contrast::Agent::Protect::Rule::ReflectedXssInputClassification
|
|
56
|
-
},
|
|
57
|
-
bot_blocker: {
|
|
58
|
-
rule_name: Contrast::Agent::Protect::Rule::BotBlocker::NAME,
|
|
59
|
-
classification: Contrast::Agent::Protect::Rule::BotBlockerInputClassification
|
|
60
|
-
},
|
|
61
|
-
unsafe_file_upload: {
|
|
62
|
-
rule_name: Contrast::Agent::Protect::Rule::UnsafeFileUpload::NAME,
|
|
63
|
-
classification: Contrast::Agent::Protect::Rule::UnsafeFileUploadInputClassification
|
|
64
|
-
},
|
|
65
|
-
path_traversal: {
|
|
66
|
-
rule_name: Contrast::Agent::Protect::Rule::PathTraversal::NAME,
|
|
67
|
-
classification: Contrast::Agent::Protect::Rule::PathTraversalInputClassification
|
|
68
|
-
},
|
|
69
|
-
nosqli: {
|
|
70
|
-
rule_name: Contrast::Agent::Protect::Rule::NoSqli::NAME,
|
|
71
|
-
classification: Contrast::Agent::Protect::Rule::NoSqliInputClassification
|
|
72
|
-
}
|
|
73
|
-
}.cs__freeze
|
|
74
|
-
|
|
75
45
|
# This method with analyze the user input from the context of the
|
|
76
|
-
# current request and
|
|
77
|
-
# found input types
|
|
46
|
+
# current request and return new ia with extracted input types.
|
|
78
47
|
#
|
|
79
48
|
# @param request [Contrast::Agent::Request] current request context.
|
|
80
49
|
# @return input_analysis [Contrast::Agent::Reporting::InputAnalysis, nil]
|
|
@@ -87,39 +56,8 @@ module Contrast
|
|
|
87
56
|
|
|
88
57
|
input_analysis = Contrast::Agent::Reporting::InputAnalysis.new
|
|
89
58
|
input_analysis.request = request
|
|
90
|
-
#
|
|
91
|
-
|
|
92
|
-
input_analysis
|
|
93
|
-
end
|
|
94
|
-
|
|
95
|
-
private
|
|
96
|
-
|
|
97
|
-
# classify input by rule implementation of worth_watching_v2 for the rules supporting it.
|
|
98
|
-
#
|
|
99
|
-
# @param inputs [String, Array<String>] user input to be analysed.
|
|
100
|
-
# @param input_analysis [Contrast::Agent::Reporting::InputAnalysis] Here we will keep all the results
|
|
101
|
-
# for each protect rule.
|
|
102
|
-
# @return input_analysis [Contrast::Agent::Reporting::InputAnalysis, nil]
|
|
103
|
-
def input_classification inputs, input_analysis
|
|
104
|
-
# key = input type, value = user_input
|
|
105
|
-
inputs.each do |input_type, value|
|
|
106
|
-
next if value.nil? || value.empty?
|
|
107
|
-
|
|
108
|
-
PROTECT_RULES.each do |_key, rule|
|
|
109
|
-
protect_rule = Contrast::PROTECT.rule(rule[:rule_name])
|
|
110
|
-
logger.debug("Rule #{ rule[:rule_name] } not recognised in Protect rules") if protect_rule.nil?
|
|
111
|
-
|
|
112
|
-
# check if rule is enabled
|
|
113
|
-
next unless protect_rule&.enabled?
|
|
114
|
-
|
|
115
|
-
# method tampering doesn't take value
|
|
116
|
-
if rule[:rule_name] == Contrast::Agent::Protect::Rule::HttpMethodTampering::NAME
|
|
117
|
-
rule[:classification].send(:classify, rule[:rule_name], input_type, input_analysis)
|
|
118
|
-
else
|
|
119
|
-
rule[:classification].send(:classify, rule[:rule_name], input_type, value, input_analysis)
|
|
120
|
-
end
|
|
121
|
-
end
|
|
122
|
-
end
|
|
59
|
+
# Save those for trigger time
|
|
60
|
+
input_analysis.inputs = extract_input(request)
|
|
123
61
|
input_analysis
|
|
124
62
|
end
|
|
125
63
|
|
|
@@ -146,22 +84,77 @@ module Contrast
|
|
|
146
84
|
inputs
|
|
147
85
|
end
|
|
148
86
|
|
|
87
|
+
# classify input by rule
|
|
88
|
+
#
|
|
89
|
+
# @param rule_id [String] name of the rule
|
|
90
|
+
# @param input_analysis [Contrast::Agent::Reporting::InputAnalysis] from
|
|
91
|
+
# analyze method.
|
|
92
|
+
def input_classification_for rule_id, input_analysis
|
|
93
|
+
return unless input_analysis&.inputs
|
|
94
|
+
return unless (protect_rule = Contrast::PROTECT.rule(rule_id)) && protect_rule.enabled?
|
|
95
|
+
|
|
96
|
+
input_analysis.inputs.each do |input_type, value|
|
|
97
|
+
next if value.nil? || value.empty?
|
|
98
|
+
|
|
99
|
+
# append to results.
|
|
100
|
+
protect_rule.classification.classify(rule_id, input_type, value, input_analysis)
|
|
101
|
+
end
|
|
102
|
+
input_analysis
|
|
103
|
+
end
|
|
104
|
+
|
|
105
|
+
# classify input by array of rules. There is a timeout for the AgentLib analysis if not set it
|
|
106
|
+
# will use the default 5s.
|
|
107
|
+
#
|
|
108
|
+
# @param input_analysis [Contrast::Agent::Reporting::InputAnalysis] Here we will keep all the results
|
|
109
|
+
# for each protect rule.
|
|
110
|
+
# @param prefilter [Boolean] flag to set input analysis for prefilter rules only
|
|
111
|
+
# @param postfilter [Boolean] flag to set input analysis for postfilter rules.
|
|
112
|
+
# @param infilter [Boolean]
|
|
113
|
+
# @param interval [Integer] The timeout determined for the AgentLib analysis to be performed
|
|
114
|
+
# @return input_analysis [Contrast::Agent::Reporting::InputAnalysis, nil]
|
|
115
|
+
# @raise [Timeout::Error] If timeout is met.
|
|
116
|
+
def input_classification(input_analysis,
|
|
117
|
+
prefilter: false,
|
|
118
|
+
postfilter: false,
|
|
119
|
+
infilter: false,
|
|
120
|
+
interval: AGENTLIB_TIMEOUT)
|
|
121
|
+
return unless input_analysis
|
|
122
|
+
|
|
123
|
+
rules = if prefilter
|
|
124
|
+
PREFILTER_RULES
|
|
125
|
+
elsif postfilter
|
|
126
|
+
POSTFILTER_RULES
|
|
127
|
+
else
|
|
128
|
+
INFILTER_RULES
|
|
129
|
+
end
|
|
130
|
+
|
|
131
|
+
rules.each do |rule_id|
|
|
132
|
+
# Check to see if rules is already triggered only for infilter:
|
|
133
|
+
next if input_analysis.triggered_rules.include?(rule_id) && infilter
|
|
134
|
+
|
|
135
|
+
Timeout.timeout(interval) do
|
|
136
|
+
input_classification_for(rule_id, input_analysis)
|
|
137
|
+
end
|
|
138
|
+
end
|
|
139
|
+
input_analysis
|
|
140
|
+
rescue Timeout::Error => e
|
|
141
|
+
logger.warn('AgentLib timed out when processing InputAnalysisResult', e, ia_result)
|
|
142
|
+
nil
|
|
143
|
+
end
|
|
144
|
+
|
|
145
|
+
private
|
|
146
|
+
|
|
149
147
|
# Extract the filename and name of the Content Disposition Header.
|
|
150
148
|
#
|
|
151
149
|
# @param inputs [Hash<Contrast::Agent::Protect::InputType => user_inputs>]
|
|
152
150
|
# @param request [Contrast::Agent::Request] current request context.
|
|
153
151
|
def extract_multipart inputs, request
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
new_pair = elem.strip.split(EQUALS, 2)
|
|
161
|
-
pairs[new_pair[0].downcase] = new_pair[1] if new_pair
|
|
162
|
-
end
|
|
163
|
-
inputs[MULTIPART_NAME] = pairs[DISPOSITION_NAME]
|
|
164
|
-
inputs[MULTIPART_FIELD_NAME] = pairs[DISPOSITION_FILENAME]
|
|
152
|
+
return unless (parsed_data = Rack::Multipart.parse_multipart(request.rack_request.env))
|
|
153
|
+
|
|
154
|
+
filename = parsed_data[DISPOSITION_FILENAME]
|
|
155
|
+
inputs[MULTIPART_FIELD_NAME] = filename[DISPOSITION_FILENAME.to_sym] if filename
|
|
156
|
+
name = filename[DISPOSITION_NAME.to_sym]
|
|
157
|
+
inputs[MULTIPART_NAME] = name if name
|
|
165
158
|
end
|
|
166
159
|
end
|
|
167
160
|
end
|
|
@@ -0,0 +1,121 @@
|
|
|
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/input_analysis_result'
|
|
6
|
+
require 'contrast/agent/reporting/input_analysis/score_level'
|
|
7
|
+
require 'contrast/agent/reporting/reporting_events/application_activity'
|
|
8
|
+
require 'contrast/utils/input_classification_base'
|
|
9
|
+
|
|
10
|
+
module Contrast
|
|
11
|
+
module Agent
|
|
12
|
+
module Protect
|
|
13
|
+
# WorthWatchingInputAnalyzer Perform analysis of input tracing v2 worthwatching results in a
|
|
14
|
+
# separate thread, should only be run at the end of the request.
|
|
15
|
+
# Currently only includes: cmd_injection & sqli_injection rules
|
|
16
|
+
class WorthWatchingInputAnalyzer < WorkerThread
|
|
17
|
+
include Timeout
|
|
18
|
+
include Contrast::Agent::Protect::Rule::InputClassificationBase
|
|
19
|
+
|
|
20
|
+
QUEUE_SIZE = 1000.cs__freeze
|
|
21
|
+
AGENTLIB_TIMEOUT = 5.cs__freeze
|
|
22
|
+
# max size of inputs to evaluate
|
|
23
|
+
INPUT_BYTESIZE_THRESHOLD = 100_000.cs__freeze
|
|
24
|
+
REPORT_INTERVAL_SECOND = 30.cs__freeze
|
|
25
|
+
|
|
26
|
+
# Thread that will process all the InputAnalysisResults that have a score level of WORTHWATCHING and
|
|
27
|
+
# sends results to TeamServer
|
|
28
|
+
def start_thread!
|
|
29
|
+
return if running?
|
|
30
|
+
|
|
31
|
+
@_thread = Contrast::Agent::Thread.new do
|
|
32
|
+
logger.info('[WorthWatchingAnalyzer] Starting thread.')
|
|
33
|
+
loop do
|
|
34
|
+
sleep(REPORT_INTERVAL_SECOND)
|
|
35
|
+
next if queue.empty?
|
|
36
|
+
|
|
37
|
+
report = false
|
|
38
|
+
# build attack_results for all infilter active protect rules.
|
|
39
|
+
results = build_results(queue.pop)
|
|
40
|
+
activity = Contrast::Agent::Reporting::ApplicationActivity.new
|
|
41
|
+
results.each do |result|
|
|
42
|
+
next unless (attack_result = eval_input(result))
|
|
43
|
+
|
|
44
|
+
activity.attach_defend(attack_result)
|
|
45
|
+
report = true
|
|
46
|
+
end
|
|
47
|
+
Contrast::Agent.reporter.send_event_immediately(activity) if report
|
|
48
|
+
rescue StandardError => e
|
|
49
|
+
logger.debug('[WorthWatchingAnalyzer] thread could not process result because of:', e)
|
|
50
|
+
end
|
|
51
|
+
end
|
|
52
|
+
end
|
|
53
|
+
|
|
54
|
+
# @param input_analysis [Contrast::Agent::Reporting::InputAnalysis]
|
|
55
|
+
def add_to_queue input_analysis
|
|
56
|
+
return unless input_analysis
|
|
57
|
+
|
|
58
|
+
if queue.size >= QUEUE_SIZE
|
|
59
|
+
logger.debug('[WorthWatchingAnalyzer] queue at max size, skip input_result')
|
|
60
|
+
return
|
|
61
|
+
end
|
|
62
|
+
# There will be no results here because of the delay of the protect rule analysis,
|
|
63
|
+
# we need to save the ia which contains the request and saved extracted user inputs to
|
|
64
|
+
# be evaluated on the thread rather than building results here. This way we allow the
|
|
65
|
+
# request to continue and will build the attack results later.
|
|
66
|
+
queue << input_analysis
|
|
67
|
+
end
|
|
68
|
+
|
|
69
|
+
private
|
|
70
|
+
|
|
71
|
+
# This method will build the attack results from the saved ia.
|
|
72
|
+
#
|
|
73
|
+
# @param input_analysis [Contrast::Agent::Reporting::InputAnalysis]
|
|
74
|
+
# @return attack_results [array<Contrast::Agent::Reporting::InputAnalysisResult>] all the results
|
|
75
|
+
# from the input analysis.
|
|
76
|
+
def build_results input_analysis
|
|
77
|
+
# Construct the input analysis for the all the infilter rules that were not triggered.
|
|
78
|
+
# There is a set timeout for each rule to be analyzed in. The infilter flag will make
|
|
79
|
+
# sure that if a rule is already triggered during the infilter phase it will not be analyzed
|
|
80
|
+
# now, making sure we don't report same rule twice.
|
|
81
|
+
Contrast::Agent::Protect::InputAnalyzer.input_classification(input_analysis, infilter: true)
|
|
82
|
+
results = []
|
|
83
|
+
input_analysis.results.reject { |val| val.score_level == SCORE_LEVEL::IGNORE }.
|
|
84
|
+
each do |ia_result|
|
|
85
|
+
results << ia_result
|
|
86
|
+
end
|
|
87
|
+
|
|
88
|
+
results
|
|
89
|
+
end
|
|
90
|
+
|
|
91
|
+
# @param ia_result Contrast::Agent::Reporting::InputAnalysisResult the WorthWatching InputAnalysisResult
|
|
92
|
+
# @return [Contrast::Agent::Reporting::AttackResult, nil] InputAnalysisResult updated Result or nil
|
|
93
|
+
def eval_input ia_result
|
|
94
|
+
return build_attack_result(ia_result) unless ia_result.value.to_s.bytesize >= INPUT_BYTESIZE_THRESHOLD
|
|
95
|
+
|
|
96
|
+
logger.debug("[WorthWatchingAnalyzer] Skipping analysis: Input size is larger than
|
|
97
|
+
#{ INPUT_BYTESIZE_THRESHOLD / 1024 }KB")
|
|
98
|
+
nil
|
|
99
|
+
end
|
|
100
|
+
|
|
101
|
+
# @param ia_result Contrast::Agent::Reporting::InputAnalysisResult the updated InputAnalysisResult
|
|
102
|
+
# with a score of :DEFINITEATTACK
|
|
103
|
+
# @return [Contrast::Agent::Reporting::AttackResult] the attack result from
|
|
104
|
+
# this input
|
|
105
|
+
def build_attack_result ia_result
|
|
106
|
+
Contrast::PROTECT.rule(ia_result.rule_id).build_attack_without_match(nil, ia_result, nil)
|
|
107
|
+
end
|
|
108
|
+
|
|
109
|
+
def queue
|
|
110
|
+
@_queue ||= Queue.new
|
|
111
|
+
end
|
|
112
|
+
|
|
113
|
+
def delete_queue!
|
|
114
|
+
@_queue&.clear
|
|
115
|
+
@_queue&.close
|
|
116
|
+
@_queue = nil
|
|
117
|
+
end
|
|
118
|
+
end
|
|
119
|
+
end
|
|
120
|
+
end
|
|
121
|
+
end
|
|
@@ -32,6 +32,8 @@ module Contrast
|
|
|
32
32
|
|
|
33
33
|
clazz = object.is_a?(Module) ? object : object.cs__class
|
|
34
34
|
class_name = clazz.cs__name
|
|
35
|
+
# Get the ia for current rule:
|
|
36
|
+
apply_classification(rule_name, Contrast::Agent::REQUEST_TRACKER.current)
|
|
35
37
|
rule.infilter(Contrast::Agent::REQUEST_TRACKER.current, class_name, method, command)
|
|
36
38
|
# invoke cmdi sub-rules.
|
|
37
39
|
rule.sub_rules.each do |sub_rule|
|
|
@@ -20,6 +20,8 @@ module Contrast
|
|
|
20
20
|
return unless valid_input?(args)
|
|
21
21
|
return if skip_analysis?
|
|
22
22
|
|
|
23
|
+
# Get the ia for current rule:
|
|
24
|
+
apply_classification(rule_name, Contrast::Agent::REQUEST_TRACKER.current)
|
|
23
25
|
database = properties['database']
|
|
24
26
|
operations = args[0]
|
|
25
27
|
context = Contrast::Agent::REQUEST_TRACKER.current
|
|
@@ -48,10 +50,11 @@ module Contrast
|
|
|
48
50
|
end
|
|
49
51
|
|
|
50
52
|
def handle_operation context, database, _action, operation
|
|
51
|
-
|
|
52
|
-
|
|
53
|
+
# TODO: RUBY-1991 Expand NoSQLI triggers
|
|
54
|
+
# data = extract_mongo_data(operation)
|
|
55
|
+
# return unless data && !data.empty?
|
|
53
56
|
|
|
54
|
-
rule.infilter(context, database,
|
|
57
|
+
rule.infilter(context, database, operation)
|
|
55
58
|
end
|
|
56
59
|
|
|
57
60
|
def extract_mongo_data operation
|
|
@@ -29,6 +29,9 @@ module Contrast
|
|
|
29
29
|
write_marker = write?(action, *args)
|
|
30
30
|
possible_write = write_marker && possible_write?(write_marker)
|
|
31
31
|
|
|
32
|
+
# Get the ia for current rule:
|
|
33
|
+
apply_classification(rule_name, Contrast::Agent::REQUEST_TRACKER.current)
|
|
34
|
+
|
|
32
35
|
# Invoke semantic rules from here, not in a separate protect policy
|
|
33
36
|
invoke_semantic_rules(path, possible_write, object, method)
|
|
34
37
|
path_traversal_rule(path, possible_write, object, method)
|
|
@@ -29,6 +29,9 @@ module Contrast
|
|
|
29
29
|
return if skip_analysis?
|
|
30
30
|
|
|
31
31
|
sql = args[index]
|
|
32
|
+
|
|
33
|
+
# Get the ia for current rule:
|
|
34
|
+
apply_classification(rule_name, Contrast::Agent::REQUEST_TRACKER.current)
|
|
32
35
|
rule.infilter(Contrast::Agent::REQUEST_TRACKER.current, database, sql)
|
|
33
36
|
rule.sub_rules.each { |sub_rule| sub_rule.infilter(Contrast::Agent::REQUEST_TRACKER.current, sql) }
|
|
34
37
|
end
|
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
# frozen_string_literal: true
|
|
3
3
|
|
|
4
4
|
require 'contrast/components/logger'
|
|
5
|
+
require 'contrast/agent/protect/input_analyzer/input_analyzer'
|
|
5
6
|
|
|
6
7
|
module Contrast
|
|
7
8
|
module Agent
|
|
@@ -44,6 +45,17 @@ module Contrast
|
|
|
44
45
|
rule: rule_name)
|
|
45
46
|
end
|
|
46
47
|
|
|
48
|
+
# applies input_analysis for the invoked rule
|
|
49
|
+
#
|
|
50
|
+
# @param rule_id [String] name of the rule
|
|
51
|
+
# @param context [Contrast::Agent::RequestContext] current request contest
|
|
52
|
+
def apply_classification rule_id, context
|
|
53
|
+
return unless context
|
|
54
|
+
return unless (ia = context.agent_input_analysis)
|
|
55
|
+
|
|
56
|
+
Contrast::Agent::Protect::InputAnalyzer.input_classification_for(rule_id, ia)
|
|
57
|
+
end
|
|
58
|
+
|
|
47
59
|
protected
|
|
48
60
|
|
|
49
61
|
# Calls the actual rule for this applicator, if required. Most rules
|
|
@@ -5,6 +5,7 @@ require 'contrast/components/logger'
|
|
|
5
5
|
require 'contrast/components/scope'
|
|
6
6
|
require 'contrast/utils/object_share'
|
|
7
7
|
require 'contrast/agent/reporting/attack_result/response_type'
|
|
8
|
+
require 'contrast/agent/reporting/attack_result/attack_result'
|
|
8
9
|
|
|
9
10
|
module Contrast
|
|
10
11
|
module Agent
|
|
@@ -51,6 +52,23 @@ module Contrast
|
|
|
51
52
|
Contrast::Utils::ObjectShare::EMPTY_ARRAY
|
|
52
53
|
end
|
|
53
54
|
|
|
55
|
+
# The classification module used for each specific rule to
|
|
56
|
+
# classify input data and score it. Extend for each rule.
|
|
57
|
+
def classification; end
|
|
58
|
+
|
|
59
|
+
# Input Classification stage is done to determine if an user input is
|
|
60
|
+
# DEFINITEATTACK or to be ignored.
|
|
61
|
+
#
|
|
62
|
+
# @param input_type [Contrast::Agent::Reporting::InputType] The type of the user input.
|
|
63
|
+
# @param value [Hash<String>] the value of the input.
|
|
64
|
+
# @param input_analysis [Contrast::Agent::Reporting::InputAnalysis] Holds all the results from the
|
|
65
|
+
# agent analysis from the current
|
|
66
|
+
# Request.
|
|
67
|
+
# @return ia [Contrast::Agent::Reporting::InputAnalysis, nil] with updated results.
|
|
68
|
+
def classify input_type, value, input_analysis
|
|
69
|
+
classification.classify(rule_name, input_type, value, input_analysis)
|
|
70
|
+
end
|
|
71
|
+
|
|
54
72
|
def enabled?
|
|
55
73
|
# 1. it is not enabled because protect is not enabled
|
|
56
74
|
return false unless ::Contrast::AGENT.enabled?
|
|
@@ -148,14 +166,14 @@ module Contrast
|
|
|
148
166
|
# protect rule but did not exploit the application. As such, we need
|
|
149
167
|
# to build a result to report this violation to TeamServer.
|
|
150
168
|
#
|
|
151
|
-
# @param context [Contrast::Agent::RequestContext] the context of the
|
|
169
|
+
# @param context [Contrast::Agent::RequestContext, nil] the context of the
|
|
152
170
|
# request in which this input is evaluated.
|
|
153
171
|
# @param ia_result [Contrast::Agent::Reporting::InputAnalysis] the
|
|
154
172
|
# analysis of the input that was determined to be an attack
|
|
155
173
|
# @param result [Contrast::Agent::Reporting::AttackResult, nil] previous
|
|
156
174
|
# attack result for this rule, if one exists, in the case of
|
|
157
175
|
# multiple inputs being found to violate the protection criteria
|
|
158
|
-
# @param kwargs [Hash] key - value pairs of context individual rules
|
|
176
|
+
# @param kwargs [Hash, nil] key - value pairs of context individual rules
|
|
159
177
|
# need to build out details to send to TeamServer to tell the
|
|
160
178
|
# story of the attack
|
|
161
179
|
# @return [Contrast::Agent::Reporting::AttackResult] the attack result from
|
|
@@ -288,11 +306,7 @@ module Contrast
|
|
|
288
306
|
# @param _context [Contrast::Agent::RequestContext] the context of
|
|
289
307
|
# the current request
|
|
290
308
|
# @return [Contrast::Agent::Reporting::AttackResult]
|
|
291
|
-
def build_attack_result _context
|
|
292
|
-
result = Contrast::Agent::Reporting::AttackResult.new
|
|
293
|
-
result.rule_id = rule_name
|
|
294
|
-
result
|
|
295
|
-
end
|
|
309
|
+
def build_attack_result _context; end
|
|
296
310
|
|
|
297
311
|
# @param sample [Contrast::Agent::Reporting::RaspRuleSample]
|
|
298
312
|
# @param result [Contrast::Agent::Reporting::AttackResult, nil] previous attack result for this rule, if one
|
|
@@ -96,6 +96,12 @@ module Contrast
|
|
|
96
96
|
end
|
|
97
97
|
end
|
|
98
98
|
|
|
99
|
+
def build_attack_result _context
|
|
100
|
+
result = Contrast::Agent::Reporting::AttackResult.new
|
|
101
|
+
result.rule_id = rule_name
|
|
102
|
+
result
|
|
103
|
+
end
|
|
104
|
+
|
|
99
105
|
# @param context [Contrast::Agent::RequestContext]
|
|
100
106
|
# @param potential_attack_string [String, nil]
|
|
101
107
|
# @param **kwargs
|