contrast-agent 6.6.2 → 6.6.5
Sign up to get free protection for your applications and to get access to all the features.
- 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
|