contrast-agent 6.6.2 → 6.6.5
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 +21 -6
- data/lib/contrast/agent/assess/rule/provider/hardcoded_value_rule.rb +2 -0
- data/lib/contrast/agent/at_exit_hook.rb +1 -7
- data/lib/contrast/agent/inventory/database_config.rb +16 -12
- data/lib/contrast/agent/inventory/policy/datastores.rb +1 -2
- data/lib/contrast/agent/middleware.rb +0 -1
- data/lib/contrast/agent/protect/rule/base.rb +16 -20
- data/lib/contrast/agent/protect/rule/cmd_injection.rb +5 -4
- data/lib/contrast/agent/protect/rule/deserialization.rb +5 -4
- data/lib/contrast/agent/protect/rule/path_traversal.rb +9 -7
- data/lib/contrast/agent/protect/rule/sql_sample_builder.rb +16 -14
- data/lib/contrast/agent/protect/rule/sqli.rb +1 -1
- data/lib/contrast/agent/protect/rule/xxe.rb +9 -6
- data/lib/contrast/agent/reporting/attack_result/attack_result.rb +8 -0
- data/lib/contrast/agent/reporting/attack_result/rasp_rule_sample.rb +85 -36
- data/lib/contrast/agent/reporting/attack_result/user_input.rb +11 -0
- data/lib/contrast/agent/reporting/details/bot_blocker_details.rb +29 -0
- data/lib/contrast/agent/reporting/details/cmd_injection_details.rb +30 -0
- data/lib/contrast/agent/reporting/details/details.rb +18 -0
- data/lib/contrast/agent/reporting/details/http_method_tempering_details.rb +27 -0
- data/lib/contrast/agent/reporting/details/ip_denylist_details.rb +27 -0
- data/lib/contrast/agent/reporting/details/no_sqli_details.rb +36 -0
- data/lib/contrast/agent/reporting/details/path_traversal_details.rb +24 -0
- data/lib/contrast/agent/reporting/details/path_traversal_semantic_analysis_details.rb +32 -0
- data/lib/contrast/agent/reporting/details/protect_rule_details.rb +17 -0
- data/lib/contrast/agent/reporting/details/sqli_details.rb +36 -0
- data/lib/contrast/agent/reporting/details/untrusted_deserialization_details.rb +27 -0
- data/lib/contrast/agent/reporting/details/virtual_patch_details.rb +24 -0
- data/lib/contrast/agent/reporting/details/xss_details.rb +33 -0
- data/lib/contrast/agent/reporting/details/xss_match.rb +30 -0
- data/lib/contrast/agent/reporting/details/xxe_details.rb +36 -0
- data/lib/contrast/agent/reporting/details/xxe_match.rb +25 -0
- data/lib/contrast/agent/reporting/details/xxe_wrapper.rb +25 -0
- data/lib/contrast/agent/reporting/input_analysis/input_analysis_result.rb +1 -1
- data/lib/contrast/agent/reporting/masker/masker.rb +78 -65
- data/lib/contrast/agent/reporting/masker/masker_utils.rb +1 -30
- data/lib/contrast/agent/reporting/reporting_events/application_activity.rb +84 -15
- data/lib/contrast/agent/reporting/reporting_events/application_defend_activity.rb +13 -25
- data/lib/contrast/agent/reporting/reporting_events/application_defend_attack_activity.rb +17 -22
- data/lib/contrast/agent/reporting/reporting_events/application_defend_attack_sample.rb +46 -125
- data/lib/contrast/agent/reporting/reporting_events/application_defend_attack_sample_activity.rb +5 -16
- data/lib/contrast/agent/reporting/reporting_events/application_defend_attacker_activity.rb +10 -18
- data/lib/contrast/agent/reporting/reporting_events/application_inventory_activity.rb +6 -14
- data/lib/contrast/agent/reporting/reporting_events/architecture_component.rb +29 -20
- data/lib/contrast/agent/reporting/reporting_events/finding_request.rb +45 -10
- data/lib/contrast/agent/reporting/reporting_events/preflight_message.rb +2 -2
- data/lib/contrast/agent/reporting/reporting_utilities/dtm_message.rb +0 -7
- data/lib/contrast/agent/reporting/reporting_utilities/endpoints.rb +1 -1
- data/lib/contrast/agent/reporting/reporting_utilities/headers.rb +2 -2
- data/lib/contrast/agent/reporting/reporting_utilities/reporter_client.rb +2 -1
- data/lib/contrast/agent/reporting/reporting_utilities/response_handler_utils.rb +1 -1
- data/lib/contrast/agent/request.rb +2 -0
- data/lib/contrast/agent/request_context.rb +13 -4
- data/lib/contrast/agent/request_context_extend.rb +59 -40
- data/lib/contrast/agent/request_handler.rb +7 -9
- data/lib/contrast/agent/service_heartbeat.rb +1 -1
- data/lib/contrast/agent/version.rb +1 -1
- data/lib/contrast/api/decorators/message.rb +1 -1
- data/lib/contrast/components/app_context.rb +62 -8
- data/lib/contrast/components/app_context_extend.rb +8 -8
- data/lib/contrast/config/assess_configuration.rb +1 -1
- data/lib/contrast/config/root_configuration.rb +6 -4
- data/lib/contrast/config.rb +0 -1
- data/lib/contrast/framework/rails/patch/action_controller_live_buffer.rb +1 -6
- data/lib/contrast/utils/assess/event_limit_utils.rb +26 -7
- data/lib/contrast/utils/log_utils.rb +16 -10
- data/lib/contrast/utils/net_http_base.rb +5 -6
- data/lib/contrast/utils/string_utils.rb +2 -6
- data/lib/contrast.rb +1 -1
- metadata +30 -14
- data/lib/contrast/config/application_configuration.rb +0 -57
@@ -12,6 +12,7 @@ require 'contrast/utils/request_utils'
|
|
12
12
|
require 'contrast/agent/request_context_extend'
|
13
13
|
require 'contrast/agent/reporting/reporting_events/observed_route'
|
14
14
|
require 'contrast/agent/reporting/input_analysis/input_analysis'
|
15
|
+
require 'contrast/agent/reporting/reporting_events/application_activity'
|
15
16
|
|
16
17
|
module Contrast
|
17
18
|
module Agent
|
@@ -25,8 +26,12 @@ module Contrast
|
|
25
26
|
|
26
27
|
EMPTY_INPUT_ANALYSIS_PB = Contrast::Api::Settings::InputAnalysis.new
|
27
28
|
|
28
|
-
# @return [Contrast::
|
29
|
+
# @return [Contrast::Agent::Reporting:ApplicationActivity] the application activity found in this request
|
29
30
|
attr_reader :activity
|
31
|
+
# REMOVE once findings and routes are done.
|
32
|
+
#
|
33
|
+
# @return [Contrast::Api::Dtm::Activity] the application activity found in this request
|
34
|
+
attr_reader :dtm_activity
|
30
35
|
# @return [Hash] context used to log the request
|
31
36
|
attr_reader :logging_hash
|
32
37
|
# @return [Contrast::Agent::Reporting::ObservedRoute] the route, used for coverage, of this request
|
@@ -53,9 +58,13 @@ module Contrast
|
|
53
58
|
|
54
59
|
# instantiate helper for request and response
|
55
60
|
@request = Contrast::Agent::Request.new(rack_request)
|
61
|
+
@activity = Contrast::Agent::Reporting::ApplicationActivity.new
|
56
62
|
|
57
|
-
|
58
|
-
|
63
|
+
# REMOVE once findings and routes are done.
|
64
|
+
#
|
65
|
+
# We still need the activity to report routes and findings
|
66
|
+
@dtm_activity = Contrast::Api::Dtm::Activity.new
|
67
|
+
@dtm_activity.http_request = request.dtm
|
59
68
|
|
60
69
|
# build analyzer
|
61
70
|
@do_not_track = false
|
@@ -125,7 +134,7 @@ module Contrast
|
|
125
134
|
end
|
126
135
|
|
127
136
|
def reset_activity
|
128
|
-
@activity = Contrast::
|
137
|
+
@activity = Contrast::Agent::Reporting::ApplicationActivity.new
|
129
138
|
@observed_route = Contrast::Agent::Reporting::ObservedRoute.new
|
130
139
|
end
|
131
140
|
|
@@ -23,6 +23,7 @@ module Contrast
|
|
23
23
|
include Contrast::Utils::CEFLogUtils
|
24
24
|
include Contrast::Components::Logger::InstanceMethods
|
25
25
|
BUILD_ATTACK_LOGGER_MESSAGE = 'Building attack result from Contrast Service input analysis result'
|
26
|
+
CEF_LOGGING_RULES = %w[bot-blocker virtual-patch ip-denylist].cs__freeze
|
26
27
|
# Convert the discovered route for this request to appropriate forms and disseminate it to those locations
|
27
28
|
# where it is necessary for our route coverage and finding vulnerability discovery features to function.
|
28
29
|
#
|
@@ -34,8 +35,10 @@ module Contrast
|
|
34
35
|
# For our findings
|
35
36
|
@route = route
|
36
37
|
|
38
|
+
# REMOVE_DTM_ACTIVITY
|
39
|
+
#
|
37
40
|
# For SR findings
|
38
|
-
@
|
41
|
+
@dtm_activity.routes << route
|
39
42
|
|
40
43
|
# For TS routes
|
41
44
|
@request.route = route
|
@@ -54,27 +57,14 @@ module Contrast
|
|
54
57
|
@request.observed_route = @observed_route
|
55
58
|
end
|
56
59
|
|
57
|
-
# Collect the results for the given rule with the given action
|
58
|
-
#
|
59
|
-
# @param rule [String] the id of the rule to which the results apply
|
60
|
-
# @param response_type [Symbol] the result of the response, matching a value of
|
61
|
-
# Contrast::Api::Dtm::AttackResult::ResponseType
|
62
|
-
# @return [Array<Contrast::Api::Dtm::AttackResult>]
|
63
|
-
def results_for rule, response_type = nil
|
64
|
-
if response_type.nil?
|
65
|
-
activity.results.select { |r| r.rule_id == rule }
|
66
|
-
else
|
67
|
-
activity.results.select { |r| r.rule_id == rule && r.response == response_type }
|
68
|
-
end
|
69
|
-
end
|
70
|
-
|
71
60
|
# @raise [Contrast::SecurityException]
|
72
61
|
def service_extract_request
|
73
62
|
return false unless ::Contrast::AGENT.enabled?
|
74
63
|
return false unless ::Contrast::PROTECT.enabled?
|
75
64
|
return false if @do_not_track
|
76
65
|
|
77
|
-
|
66
|
+
# REMOVE_DTM_ACTIVITY
|
67
|
+
service_response = Contrast::Agent&.messaging_queue&.send_event_immediately(@activity.request.dtm)
|
78
68
|
return false unless service_response
|
79
69
|
|
80
70
|
handle_protect_state(service_response)
|
@@ -112,6 +102,7 @@ module Contrast
|
|
112
102
|
@do_not_track = true unless state.track_request
|
113
103
|
return unless state.security_exception
|
114
104
|
|
105
|
+
# make sure the activity get send before the error
|
115
106
|
# If Contrast Service has NOT handled the input analysis, handle them here
|
116
107
|
build_attack_results(agent_settings)
|
117
108
|
logger.debug('Contrast Service said to block this request')
|
@@ -126,7 +117,6 @@ module Contrast
|
|
126
117
|
@response = Contrast::Agent::Response.new(rack_response)
|
127
118
|
return unless @sample_res
|
128
119
|
|
129
|
-
#
|
130
120
|
# TODO: RUBY-1376 once all rules translated, move this to if/else w/ the enabled
|
131
121
|
if Contrast::Agent::Reporter.enabled?
|
132
122
|
Contrast::Agent::Assess::Rule::Response::AutoComplete.new.analyze(@response)
|
@@ -139,7 +129,8 @@ module Contrast
|
|
139
129
|
Contrast::Agent::Assess::Rule::Response::XContentType.new.analyze(@response)
|
140
130
|
Contrast::Agent::Assess::Rule::Response::XXssProtection.new.analyze(@response)
|
141
131
|
else
|
142
|
-
|
132
|
+
# REMOVE_DTM_ACTIVITY
|
133
|
+
dtm_activity.http_response = @response.dtm
|
143
134
|
end
|
144
135
|
rescue StandardError => e
|
145
136
|
logger.error('Unable to extract information after request', e)
|
@@ -147,7 +138,7 @@ module Contrast
|
|
147
138
|
|
148
139
|
# This here is for things we don't have implemented
|
149
140
|
def log_to_cef
|
150
|
-
activity.
|
141
|
+
activity.defend.attackers.each { |attacker| logging_logic(attacker.protection_rules) }
|
151
142
|
end
|
152
143
|
|
153
144
|
# @param input_analysis [Contrast::Api::Settings::InputAnalysis]
|
@@ -177,7 +168,7 @@ module Contrast
|
|
177
168
|
|
178
169
|
results_by_rule.each_pair do |_, attack_result|
|
179
170
|
logger.info('Blocking attack result', rule: attack_result.rule_id)
|
180
|
-
activity.
|
171
|
+
activity.attach_defend(attack_result)
|
181
172
|
end
|
182
173
|
end
|
183
174
|
|
@@ -199,28 +190,56 @@ module Contrast
|
|
199
190
|
end
|
200
191
|
end
|
201
192
|
|
202
|
-
|
203
|
-
|
204
|
-
|
205
|
-
|
206
|
-
|
207
|
-
|
208
|
-
|
209
|
-
|
210
|
-
|
211
|
-
|
212
|
-
|
213
|
-
|
214
|
-
|
215
|
-
|
216
|
-
|
217
|
-
|
218
|
-
|
219
|
-
|
220
|
-
|
221
|
-
|
193
|
+
# @param protection_rules [Array<rule_id => Contrast::Agent::Reporting::ApplicationDefendAttackActivity>] Array
|
194
|
+
# of all protection rules per active attackers of this request life cycle.
|
195
|
+
def logging_logic protection_rules
|
196
|
+
protection_rules.any? do |rule_id, activity|
|
197
|
+
next unless CEF_LOGGING_RULES.include?(rule_id)
|
198
|
+
|
199
|
+
outcome = activity.response_type
|
200
|
+
rule_details = details_builder(outcome, activity)
|
201
|
+
case rule_id
|
202
|
+
when /bot-blocker/i
|
203
|
+
cef_logger.bot_blocking_message(rule_details.to_controlled_hash, outcome) if rule_details
|
204
|
+
when /virtual-patch/i
|
205
|
+
cef_logger.virtual_patch_message(rule_details.to_controlled_hash, outcome) if rule_details
|
206
|
+
when /ip-denylist/i
|
207
|
+
sender_ip = extract_sender_ip
|
208
|
+
next unless sender_ip
|
209
|
+
next unless rule_details && rule_details.ip == sender_ip
|
210
|
+
|
211
|
+
cef_logger.ip_denylisted_message(sender_ip, rule_details.to_controlled_hash, outcome)
|
212
|
+
end
|
213
|
+
end
|
214
|
+
end
|
215
|
+
|
216
|
+
# @param outcome [Symbol<Contrast::Agent::Reporting::ResponseType>]
|
217
|
+
# @param activity [Contrast::Agent::Reporting::ApplicationDefendAttackActivity]
|
218
|
+
def details_builder outcome, activity
|
219
|
+
case outcome
|
220
|
+
when ::Contrast::Agent::Reporting::ResponseType::BLOCKED
|
221
|
+
blocked = activity.blocked
|
222
|
+
get_details(blocked)
|
223
|
+
when ::Contrast::Agent::Reporting::ResponseType::MONITORED
|
224
|
+
exploited = activity.exploited
|
225
|
+
get_details(exploited)
|
226
|
+
when ::Contrast::Agent::Reporting::ResponseType::PROBED
|
227
|
+
ineffective = activity.ineffective
|
228
|
+
get_details(ineffective)
|
229
|
+
when ::Contrast::Agent::Reporting::ResponseType::SUSPICIOUS
|
230
|
+
activity.suspicious.samples[0].details
|
231
|
+
suspicious = activity.suspicious
|
232
|
+
get_details(suspicious)
|
222
233
|
end
|
223
234
|
end
|
235
|
+
|
236
|
+
# @param type [Contrast::Agent::Reporting::ApplicationDefendAttackSampleActivity]
|
237
|
+
# @return details [Contrast::Agent::Reporting::ProtectRuleDetails] depends on rule
|
238
|
+
def get_details type
|
239
|
+
sample = nil
|
240
|
+
sample = type.samples[0] unless type.samples.empty?
|
241
|
+
sample&.details
|
242
|
+
end
|
224
243
|
end
|
225
244
|
end
|
226
245
|
end
|
@@ -22,13 +22,6 @@ module Contrast
|
|
22
22
|
@ruleset = ::Contrast::AGENT.ruleset
|
23
23
|
end
|
24
24
|
|
25
|
-
# Send Activities messages to TS [Contrast::Api::Dtm::Activity]
|
26
|
-
# TODO: RUBY-1704
|
27
|
-
# TODO: RUBY-1438
|
28
|
-
def send_activity_messages
|
29
|
-
Contrast::Agent.messaging_queue&.send_event_eventually(context.activity)
|
30
|
-
end
|
31
|
-
|
32
25
|
# reports events[Contrast::Agent::Reporting::ReporterEvent] to TS
|
33
26
|
# This method is used to send our JSON messages directly to TeamServer at the end of each request. As we move
|
34
27
|
# more endpoints over, this method will take the messages originally sent by #send_actiivty_messages. At the end,
|
@@ -37,11 +30,16 @@ module Contrast
|
|
37
30
|
return unless (reporter = Contrast::Agent.reporter)
|
38
31
|
|
39
32
|
reporter.send_event(context.observed_route)
|
40
|
-
return unless Contrast::Agent::Reporter.enabled?
|
33
|
+
# return unless Contrast::Agent::Reporter.enabled?
|
34
|
+
|
35
|
+
# REMOVE_DTM_ACTIVITY after reporter routes, responses, findings are implemented
|
36
|
+
#
|
37
|
+
# This reports routes, findings and traces with response dependent rules.
|
38
|
+
Contrast::Agent.messaging_queue&.send_event_eventually(context.dtm_activity)
|
41
39
|
|
42
40
|
# Mask Sensitive Data
|
43
41
|
Contrast::Agent::Reporting::Masker.mask(context.activity)
|
44
|
-
event =
|
42
|
+
event = context.activity
|
45
43
|
reporter.send_event(event)
|
46
44
|
end
|
47
45
|
|
@@ -19,7 +19,7 @@ module Contrast
|
|
19
19
|
@_thread = Contrast::Agent::Thread.new do
|
20
20
|
logger.info('Starting heartbeat thread.')
|
21
21
|
loop do
|
22
|
-
logger.info("Queue Size: #{ Contrast::Agent.messaging_queue
|
22
|
+
logger.info("Queue Size: #{ Contrast::Agent.messaging_queue&.queue&.length }")
|
23
23
|
Contrast::Agent.messaging_queue&.send_event_eventually(poll_message)
|
24
24
|
clean_properties
|
25
25
|
sleep(REFRESH_INTERVAL_SEC)
|
@@ -52,7 +52,7 @@ module Contrast
|
|
52
52
|
|
53
53
|
def build event
|
54
54
|
msg = new
|
55
|
-
msg.app_name = ::Contrast::APP_CONTEXT.
|
55
|
+
msg.app_name = ::Contrast::APP_CONTEXT.name # rubocop:disable Security/Module/Name
|
56
56
|
msg.app_path = ::Contrast::APP_CONTEXT.path
|
57
57
|
msg.app_language = Contrast::Utils::ObjectShare::RUBY
|
58
58
|
msg.client_id = ::Contrast::APP_CONTEXT.client_id
|
@@ -6,6 +6,7 @@ require 'contrast/api/decorators/agent_startup'
|
|
6
6
|
require 'contrast/api/decorators/application_startup'
|
7
7
|
require 'contrast/utils/object_share'
|
8
8
|
require 'contrast/components/app_context_extend'
|
9
|
+
require 'contrast/config/base_configuration'
|
9
10
|
|
10
11
|
module Contrast
|
11
12
|
module Components
|
@@ -18,15 +19,66 @@ module Contrast
|
|
18
19
|
class Interface
|
19
20
|
include Contrast::Components::AppContextExtend
|
20
21
|
include Contrast::Components::ComponentBase
|
21
|
-
include Contrast::
|
22
|
+
include Contrast::Config::BaseConfiguration
|
22
23
|
|
23
24
|
DEFAULT_APP_NAME = 'rails'
|
24
25
|
DEFAULT_APP_PATH = '/'
|
25
26
|
DEFAULT_SERVER_NAME = 'localhost'
|
26
27
|
DEFAULT_SERVER_PATH = '/'
|
27
28
|
|
28
|
-
|
29
|
+
# @return [String]
|
30
|
+
attr_accessor :version
|
31
|
+
# @return [String]
|
32
|
+
attr_accessor :language
|
33
|
+
# @return [String]
|
34
|
+
attr_accessor :group
|
35
|
+
# @return [String]
|
36
|
+
attr_accessor :tags
|
37
|
+
# @return [String]
|
38
|
+
attr_accessor :code
|
39
|
+
# @return [String]
|
40
|
+
attr_accessor :metadata
|
41
|
+
|
42
|
+
def initialize hsh = {}
|
29
43
|
original_pid
|
44
|
+
return unless hsh
|
45
|
+
|
46
|
+
@_name = hsh[:name]
|
47
|
+
@version = hsh[:version]
|
48
|
+
@language = hsh[:language]
|
49
|
+
@_path = hsh[:path]
|
50
|
+
@group = hsh[:group]
|
51
|
+
@tags = hsh[:tags]
|
52
|
+
@code = hsh[:code]
|
53
|
+
@metadata = hsh[:metadata]
|
54
|
+
@_session_id = hsh[:session_id]
|
55
|
+
@_session_metadata = hsh[:session_metadata]
|
56
|
+
end
|
57
|
+
|
58
|
+
# @return [String, Contrast::Utils::ObjectShare::EMPTY_STRING]
|
59
|
+
def session_id
|
60
|
+
@_session_id ||= Contrast::Utils::ObjectShare::EMPTY_STRING
|
61
|
+
end
|
62
|
+
|
63
|
+
# Set session_id
|
64
|
+
#
|
65
|
+
# @param id [String]
|
66
|
+
# @return [String]
|
67
|
+
def session_id= id
|
68
|
+
@_session_id = id
|
69
|
+
end
|
70
|
+
|
71
|
+
# @return [String, Contrast::Utils::ObjectShare::EMPTY_STRING]
|
72
|
+
def session_metadata
|
73
|
+
@_session_metadata ||= Contrast::Utils::ObjectShare::EMPTY_STRING
|
74
|
+
end
|
75
|
+
|
76
|
+
# Set session_metadata
|
77
|
+
#
|
78
|
+
# @param meta [String]
|
79
|
+
# @return [String]
|
80
|
+
def session_metadata= meta
|
81
|
+
@_session_metadata = meta
|
30
82
|
end
|
31
83
|
|
32
84
|
def server_type
|
@@ -37,9 +89,8 @@ module Contrast
|
|
37
89
|
end
|
38
90
|
end
|
39
91
|
|
40
|
-
def
|
41
|
-
@
|
42
|
-
tmp = ::Contrast::CONFIG.root.application.name # rubocop:disable Security/Module/Name
|
92
|
+
def name
|
93
|
+
@_name ||= begin
|
43
94
|
tmp = Contrast::Agent.framework_manager.app_name unless Contrast::Utils::StringUtils.present?(tmp)
|
44
95
|
tmp = File.basename(Dir.pwd) unless Contrast::Utils::StringUtils.present?(tmp)
|
45
96
|
Contrast::Utils::StringUtils.truncate(tmp, DEFAULT_APP_NAME)
|
@@ -48,13 +99,16 @@ module Contrast
|
|
48
99
|
end
|
49
100
|
end
|
50
101
|
|
51
|
-
|
52
|
-
|
102
|
+
# Set application name
|
103
|
+
#
|
104
|
+
# @param app_name [String] application name
|
105
|
+
# @return [String]
|
106
|
+
def name= app_name
|
107
|
+
@_name = app_name
|
53
108
|
end
|
54
109
|
|
55
110
|
def path
|
56
111
|
@_path ||= begin
|
57
|
-
tmp = ::Contrast::CONFIG.root.application.path
|
58
112
|
tmp = Contrast::Agent.framework_manager.application_root unless Contrast::Utils::StringUtils.present?(tmp)
|
59
113
|
Contrast::Utils::StringUtils.truncate(tmp, DEFAULT_APP_PATH)
|
60
114
|
rescue StandardError
|
@@ -18,13 +18,13 @@ module Contrast
|
|
18
18
|
|
19
19
|
def build_agent_startup_message
|
20
20
|
msg = Contrast::Api::Dtm::AgentStartup.build(server_name, server_path, server_type)
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
21
|
+
Contrast::CONFIG.proto_logger.info('Application context',
|
22
|
+
server_name: msg.server_name,
|
23
|
+
server_path: msg.server_path,
|
24
|
+
server_type: msg.server_type,
|
25
|
+
application_name: name, # rubocop:disable Security/Module/Name
|
26
|
+
application_path: path,
|
27
|
+
application_language: Contrast::Utils::ObjectShare::RUBY)
|
28
28
|
|
29
29
|
msg
|
30
30
|
end
|
@@ -42,7 +42,7 @@ module Contrast
|
|
42
42
|
end
|
43
43
|
|
44
44
|
def client_id
|
45
|
-
@_client_id ||= [
|
45
|
+
@_client_id ||= [name, pgid].join('-') # rubocop:disable Security/Module/Name
|
46
46
|
end
|
47
47
|
|
48
48
|
def app_and_server_information
|
@@ -19,7 +19,7 @@ module Contrast
|
|
19
19
|
DEFAULT_STACKTRACES = 'ALL'
|
20
20
|
DEFAULT_MAX_SOURCE_EVENTS = 50_000
|
21
21
|
DEFAULT_MAX_PROPAGATION_EVENTS = 50_000
|
22
|
-
DEFAULT_MAX_RULE_REPORTED =
|
22
|
+
DEFAULT_MAX_RULE_REPORTED = 100
|
23
23
|
DEFAULT_MAX_RULE_TIME_THRESHOLD = 300_000
|
24
24
|
|
25
25
|
def initialize hsh = {}
|
@@ -4,6 +4,8 @@
|
|
4
4
|
require 'contrast/components/agent'
|
5
5
|
require 'contrast/components/inventory'
|
6
6
|
require 'contrast/components/protect'
|
7
|
+
require 'contrast/components/app_context'
|
8
|
+
|
7
9
|
module Contrast
|
8
10
|
module Config
|
9
11
|
# The base of the Common Configuration settings.
|
@@ -14,7 +16,7 @@ module Contrast
|
|
14
16
|
attr_writer :api
|
15
17
|
# @return [Contrast::Components::Agent::Interface]
|
16
18
|
attr_writer :agent
|
17
|
-
# @return [Contrast::
|
19
|
+
# @return [Contrast::Components::AppContext::Interface]
|
18
20
|
attr_writer :application
|
19
21
|
# @return [Contrast::Config::ServerConfiguration]
|
20
22
|
attr_writer :server
|
@@ -36,7 +38,7 @@ module Contrast
|
|
36
38
|
@api = Contrast::Components::Api::Interface.new(hsh[:api])
|
37
39
|
@enable = hsh[:enable]
|
38
40
|
@agent = Contrast::Components::Agent::Interface.new(hsh[:agent])
|
39
|
-
@application = Contrast::
|
41
|
+
@application = Contrast::Components::AppContext::Interface.new(hsh[:application])
|
40
42
|
@server = Contrast::Config::ServerConfiguration.new(hsh[:server])
|
41
43
|
@assess = Contrast::Config::AssessConfiguration.new(hsh[:assess])
|
42
44
|
@inventory = Contrast::Components::Inventory::Interface.new(hsh[:inventory])
|
@@ -54,9 +56,9 @@ module Contrast
|
|
54
56
|
@agent ||= Contrast::Components::Agent::Interface.new
|
55
57
|
end
|
56
58
|
|
57
|
-
# @return [Contrast::
|
59
|
+
# @return [Contrast::Components::AppContext::Interface]
|
58
60
|
def application
|
59
|
-
@application ||= Contrast::
|
61
|
+
@application ||= Contrast::Components::AppContext::Interface.new
|
60
62
|
end
|
61
63
|
|
62
64
|
# @return [Contrast::Config::ServerConfiguration]
|
data/lib/contrast/config.rb
CHANGED
@@ -18,7 +18,6 @@ require 'contrast/config/protect_rule_configuration'
|
|
18
18
|
require 'contrast/config/protect_rules_configuration'
|
19
19
|
|
20
20
|
require 'contrast/config/ruby_configuration'
|
21
|
-
require 'contrast/config/application_configuration'
|
22
21
|
require 'contrast/config/server_configuration'
|
23
22
|
require 'contrast/config/assess_configuration'
|
24
23
|
require 'contrast/config/root_configuration'
|
@@ -20,12 +20,7 @@ module Contrast
|
|
20
20
|
Contrast::Agent.reporter&.send_event_immediately(event)
|
21
21
|
end
|
22
22
|
|
23
|
-
|
24
|
-
event = Contrast::Agent::Reporting::DtmMessage.dtm_to_event(context.activity)
|
25
|
-
Contrast::Agent.reporter&.send_event_immediately(event)
|
26
|
-
else
|
27
|
-
Contrast::Agent.messaging_queue&.send_event_immediately(context.activity)
|
28
|
-
end
|
23
|
+
Contrast::Agent.reporter&.send_event_immediately(context.activity)
|
29
24
|
end
|
30
25
|
|
31
26
|
def instrument
|
@@ -30,16 +30,29 @@ module Contrast
|
|
30
30
|
false # policy does not have limit
|
31
31
|
end
|
32
32
|
|
33
|
-
def event_limit_for_rule? rule_id
|
34
|
-
|
35
|
-
|
36
|
-
|
33
|
+
def event_limit_for_rule? rule_id # rubocop:disable Metrics/AbcSize
|
34
|
+
return false unless (context = Contrast::Agent::REQUEST_TRACKER.current)
|
35
|
+
|
36
|
+
saved_request_ids = rule_counts.keys.map { |k| k.to_s.split('_')[1] }
|
37
|
+
|
38
|
+
# if we passed the threshold and we actually have records for that request - wipe them
|
39
|
+
if saved_request_ids.uniq.include?(context.request.__id__)
|
40
|
+
restore_defaults
|
37
41
|
threshold_time_limit
|
38
42
|
end
|
39
|
-
|
43
|
+
|
44
|
+
# if we have recorded rule counts, but none of them are for the current request_id
|
45
|
+
# eventually we can try and play with the time_limit_threshold -> DEFAULT_MAX_RULE_TIME_THRESHOLD
|
46
|
+
unless !rule_counts.empty? && saved_request_ids.include?(context.request.__id__)
|
47
|
+
restore_defaults
|
48
|
+
threshold_time_limit
|
49
|
+
end
|
50
|
+
|
51
|
+
rule_key = "#{ rule_id }_#{ context.request.__id__ }"
|
52
|
+
rule_counts[rule_key] += 1
|
40
53
|
# TODO: RUBY-1680 remove default
|
41
|
-
|
42
|
-
|
54
|
+
# we don't need the default here because we either return from the config, or we return the default
|
55
|
+
rule_counts[rule_key] >= ::Contrast::ASSESS.max_rule_reported
|
43
56
|
end
|
44
57
|
|
45
58
|
# Increments the event count for the type of event that is being tracked
|
@@ -90,6 +103,12 @@ module Contrast
|
|
90
103
|
def threshold_time_limit
|
91
104
|
@_threshold_time_limit ||= Contrast::Utils::Timer.now_ms + (::Contrast::ASSESS.time_limit_threshold || 0)
|
92
105
|
end
|
106
|
+
|
107
|
+
# @return nil
|
108
|
+
def restore_defaults
|
109
|
+
@_rule_counts = nil
|
110
|
+
@_threshold_time_limit = nil
|
111
|
+
end
|
93
112
|
end
|
94
113
|
end
|
95
114
|
end
|
@@ -3,6 +3,7 @@
|
|
3
3
|
|
4
4
|
require 'socket'
|
5
5
|
require 'contrast/agent/version'
|
6
|
+
require 'contrast/utils/object_share'
|
6
7
|
require 'contrast/logger/aliased_logging'
|
7
8
|
|
8
9
|
module Contrast
|
@@ -175,14 +176,11 @@ module Contrast
|
|
175
176
|
# initially here we will use case to add it
|
176
177
|
def extract_metadata rule_id = nil, outcome = nil
|
177
178
|
message = []
|
178
|
-
|
179
|
+
request = context&.activity&.request
|
180
|
+
sender_info = { ip: request&.ip || Contrast::Utils::ObjectShare::EMPTY_STRING, port: request&.port || 0 }
|
179
181
|
rule_id ? message << "pri=#{ rule_id } " : 'asd'
|
180
|
-
request_method =
|
181
|
-
|
182
|
-
else
|
183
|
-
DEFAULT_METADATA
|
184
|
-
end
|
185
|
-
app_name = ::Contrast::APP_CONTEXT.app_name
|
182
|
+
request_method = assign_request_method(context)
|
183
|
+
app_name = ::Contrast::APP_CONTEXT.name # rubocop:disable Security/Module/Name
|
186
184
|
attach_request_and_sender_info(message, sender_info)
|
187
185
|
message << "request=#{ context.request.url } "
|
188
186
|
message << "requestMethod=#{ request_method } "
|
@@ -198,10 +196,10 @@ module Contrast
|
|
198
196
|
src = if needed_header
|
199
197
|
needed_header
|
200
198
|
else
|
201
|
-
sender_info
|
199
|
+
sender_info[:ip].length > 1 ? sender_info[:ip] : DEFAULT_METADATA
|
202
200
|
end
|
203
201
|
message << "src=#{ src }"
|
204
|
-
message << "port=#{ sender_info
|
202
|
+
message << "port=#{ sender_info[:port] }"
|
205
203
|
end
|
206
204
|
|
207
205
|
def extract_ip_address
|
@@ -216,9 +214,17 @@ module Contrast
|
|
216
214
|
end
|
217
215
|
|
218
216
|
def extract_sender_ip
|
219
|
-
request_headers = context.activity.
|
217
|
+
request_headers = context.activity.request.headers&.transform_keys(&:to_s)
|
220
218
|
request_headers['X-Forwarded-For']
|
221
219
|
end
|
220
|
+
|
221
|
+
def assign_request_method context
|
222
|
+
if context.request.rack_request.env['REQUEST_METHOD'].length.positive?
|
223
|
+
context.request.rack_request.env['REQUEST_METHOD']
|
224
|
+
else
|
225
|
+
DEFAULT_METADATA
|
226
|
+
end
|
227
|
+
end
|
222
228
|
end
|
223
229
|
end
|
224
230
|
end
|
@@ -38,7 +38,7 @@ module Contrast
|
|
38
38
|
return unless net_http_client.started?
|
39
39
|
|
40
40
|
logger.debug("Starting #{ service_name } connection test")
|
41
|
-
return unless connection_verified?(net_http_client)
|
41
|
+
return unless connection_verified?(net_http_client, url)
|
42
42
|
|
43
43
|
logger.debug('Client verified', service: service_name, url: url)
|
44
44
|
net_http_client
|
@@ -49,18 +49,17 @@ module Contrast
|
|
49
49
|
|
50
50
|
# Validates connection with assigned domain.
|
51
51
|
# If connection is running, SSL certificate of the endpoint is valid, Ip address is resolvable
|
52
|
-
# and response is received without peer's reset or refuse of connection,
|
53
|
-
# then validation returns true. Error handling is in place so that the work of the agent will continue as
|
54
|
-
# normal without Telemetry.
|
52
|
+
# and response is received without peer's reset or refuse of connection, then validation returns true.
|
55
53
|
#
|
56
54
|
# @param client [Net::HTTP]
|
55
|
+
# @param url [String]
|
57
56
|
# @return [Boolean] true | false
|
58
|
-
def connection_verified? client
|
57
|
+
def connection_verified? client, url
|
59
58
|
return @_connection_verified unless @_connection_verified.nil?
|
60
59
|
return false if client.nil?
|
61
60
|
|
62
61
|
ipaddr = get_ipaddr(client)
|
63
|
-
response = client.request(Net::HTTP::Get.new(
|
62
|
+
response = client.request(Net::HTTP::Get.new(url))
|
64
63
|
verify_cert = client.address.to_s.include?('localhost') ||
|
65
64
|
OpenSSL::SSL.verify_certificate_identity(client.peer_cert, client.address)
|
66
65
|
resolved = resolved?(client.address, ipaddr)
|
@@ -1,15 +1,11 @@
|
|
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/components/logger'
|
5
|
-
|
6
4
|
module Contrast
|
7
5
|
module Utils
|
8
6
|
# Utilities for encoding and normalizing strings
|
9
|
-
|
7
|
+
module StringUtils
|
10
8
|
class << self
|
11
|
-
include Contrast::Components::Logger::InstanceMethods
|
12
|
-
|
13
9
|
UTF8 = 'utf-8'
|
14
10
|
HTTP_PREFIX = 'HTTP_'
|
15
11
|
|
@@ -61,7 +57,7 @@ module Contrast
|
|
61
57
|
# We were unable to switch the String to a UTF-8 format.
|
62
58
|
# Return non-nil so as not to throw an exception later when trying
|
63
59
|
# to do regexp or other compares on the String
|
64
|
-
|
60
|
+
Contrast::CONFIG.proto_logger.trace('Unable to cast String to UTF-8 format', e, value: str)
|
65
61
|
|
66
62
|
Contrast::Utils::ObjectShare::EMPTY_STRING
|
67
63
|
end
|