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