contrast-agent 6.6.5 → 6.7.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 +3 -0
- data/.gitmodules +0 -3
- data/ext/cs__scope/cs__scope.c +1 -1
- data/lib/contrast/agent/assess/contrast_event.rb +2 -24
- data/lib/contrast/agent/assess/events/source_event.rb +7 -61
- data/lib/contrast/agent/assess/finalizers/hash.rb +11 -0
- data/lib/contrast/agent/assess/policy/dynamic_source_factory.rb +0 -55
- data/lib/contrast/agent/assess/policy/policy_node.rb +3 -3
- data/lib/contrast/agent/assess/policy/policy_node_utils.rb +0 -1
- data/lib/contrast/agent/assess/policy/propagation_node.rb +4 -4
- data/lib/contrast/agent/assess/policy/source_method.rb +24 -1
- data/lib/contrast/agent/assess/policy/trigger/reflected_xss.rb +7 -5
- data/lib/contrast/agent/assess/policy/trigger/xpath.rb +6 -1
- data/lib/contrast/agent/assess/policy/trigger_method.rb +36 -132
- data/lib/contrast/agent/assess/policy/trigger_node.rb +3 -3
- data/lib/contrast/agent/assess/property/evented.rb +2 -12
- data/lib/contrast/agent/assess/rule/provider/hardcoded_value_rule.rb +42 -84
- data/lib/contrast/agent/assess/rule/response/base_rule.rb +11 -27
- data/lib/contrast/agent/assess/rule/response/body_rule.rb +1 -3
- data/lib/contrast/agent/assess/rule/response/cache_control_header_rule.rb +77 -62
- data/lib/contrast/agent/assess/rule/response/csp_header_insecure_rule.rb +1 -1
- data/lib/contrast/agent/assess/rule/response/framework/rails_support.rb +6 -1
- data/lib/contrast/agent/assess/rule/response/header_rule.rb +5 -5
- data/lib/contrast/agent/assess/rule/response/hsts_header_rule.rb +1 -1
- data/lib/contrast/agent/assess/rule/response/x_xss_protection_header_rule.rb +1 -1
- data/lib/contrast/agent/assess/tracker.rb +1 -7
- data/lib/contrast/agent/excluder.rb +206 -0
- data/lib/contrast/agent/exclusion_matcher.rb +6 -0
- data/lib/contrast/agent/inventory/database_config.rb +6 -10
- data/lib/contrast/agent/protect/policy/applies_command_injection_rule.rb +4 -0
- data/lib/contrast/agent/protect/policy/applies_sqli_rule.rb +1 -0
- data/lib/contrast/agent/protect/rule/base.rb +49 -5
- data/lib/contrast/agent/protect/rule/base_service.rb +1 -0
- data/lib/contrast/agent/protect/rule/cmd_injection.rb +18 -105
- data/lib/contrast/agent/protect/rule/cmdi/cmdi_backdoors.rb +129 -0
- data/lib/contrast/agent/protect/rule/cmdi/cmdi_base_rule.rb +169 -0
- data/lib/contrast/agent/protect/rule/deserialization.rb +2 -1
- data/lib/contrast/agent/protect/rule/sqli/sqli_base_rule.rb +51 -0
- data/lib/contrast/agent/protect/rule/sqli/sqli_semantic/sqli_dangerous_functions.rb +67 -0
- data/lib/contrast/agent/protect/rule/sqli.rb +6 -31
- data/lib/contrast/agent/protect/rule/xxe.rb +2 -0
- data/lib/contrast/agent/protect/rule.rb +3 -1
- data/lib/contrast/agent/reporting/attack_result/rasp_rule_sample.rb +6 -0
- data/lib/contrast/agent/reporting/details/sqli_dangerous_functions.rb +22 -0
- data/lib/contrast/agent/reporting/reporter.rb +1 -2
- data/lib/contrast/agent/reporting/reporting_events/agent_startup.rb +2 -2
- data/lib/contrast/agent/reporting/reporting_events/application_activity.rb +1 -4
- data/lib/contrast/agent/reporting/reporting_events/application_startup.rb +1 -1
- data/lib/contrast/agent/reporting/reporting_events/architecture_component.rb +0 -23
- data/lib/contrast/agent/reporting/reporting_events/finding.rb +19 -49
- data/lib/contrast/agent/reporting/reporting_events/finding_event.rb +12 -9
- data/lib/contrast/agent/reporting/reporting_events/finding_event_signature.rb +1 -1
- data/lib/contrast/agent/reporting/reporting_events/finding_event_source.rb +23 -21
- data/lib/contrast/agent/reporting/reporting_events/finding_event_stack.rb +5 -18
- data/lib/contrast/agent/reporting/reporting_events/finding_event_taint_range.rb +1 -0
- data/lib/contrast/{api/decorators/trace_taint_range_tags.rb → agent/reporting/reporting_events/finding_event_taint_range_tags.rb} +7 -6
- data/lib/contrast/agent/reporting/reporting_events/finding_request.rb +1 -1
- data/lib/contrast/agent/reporting/reporting_events/library_usage_observation.rb +1 -1
- data/lib/contrast/agent/reporting/reporting_events/observed_route.rb +2 -2
- data/lib/contrast/agent/reporting/reporting_events/preflight_message.rb +10 -14
- data/lib/contrast/agent/reporting/reporting_events/reporting_event.rb +11 -0
- data/lib/contrast/agent/reporting/reporting_events/route_coverage.rb +3 -1
- data/lib/contrast/agent/reporting/reporting_events/route_discovery.rb +11 -23
- data/lib/contrast/agent/reporting/reporting_events/route_discovery_observation.rb +8 -26
- data/lib/contrast/agent/reporting/reporting_utilities/audit.rb +1 -1
- data/lib/contrast/agent/reporting/reporting_utilities/build_preflight.rb +4 -7
- data/lib/contrast/agent/reporting/reporting_utilities/headers.rb +1 -1
- data/lib/contrast/agent/reporting/reporting_utilities/reporter_client_utils.rb +3 -3
- data/lib/contrast/agent/request.rb +2 -2
- data/lib/contrast/agent/request_context.rb +8 -20
- data/lib/contrast/agent/request_context_extend.rb +15 -36
- data/lib/contrast/agent/request_handler.rb +0 -8
- data/lib/contrast/agent/response.rb +0 -18
- data/lib/contrast/agent/telemetry/events/event.rb +1 -1
- data/lib/contrast/agent/telemetry/events/metric_event.rb +1 -1
- data/lib/contrast/agent/telemetry/events/startup_metrics_event.rb +3 -3
- data/lib/contrast/agent/version.rb +1 -1
- data/lib/contrast/api/communication/messaging_queue.rb +2 -3
- data/lib/contrast/api/communication/socket_client.rb +4 -4
- data/lib/contrast/api/communication/speedracer.rb +4 -8
- data/lib/contrast/api/decorators/agent_startup.rb +5 -6
- data/lib/contrast/api/decorators/application_settings.rb +2 -1
- data/lib/contrast/api/decorators/application_startup.rb +6 -6
- data/lib/contrast/api/decorators/message.rb +0 -4
- data/lib/contrast/api/decorators/rasp_rule_sample.rb +0 -6
- data/lib/contrast/api/decorators.rb +0 -6
- data/lib/contrast/api/dtm.pb.rb +0 -489
- data/lib/contrast/components/agent.rb +16 -12
- data/lib/contrast/components/api.rb +10 -10
- data/lib/contrast/components/app_context.rb +3 -3
- data/lib/contrast/components/app_context_extend.rb +1 -1
- data/lib/contrast/components/assess.rb +92 -38
- data/lib/contrast/components/assess_rules.rb +36 -0
- data/lib/contrast/components/config.rb +54 -12
- data/lib/contrast/components/contrast_service.rb +8 -8
- data/lib/contrast/components/heap_dump.rb +1 -1
- data/lib/contrast/components/protect.rb +5 -5
- data/lib/contrast/components/ruby_component.rb +81 -0
- data/lib/contrast/components/sampling.rb +1 -1
- data/lib/contrast/components/security_logger.rb +23 -0
- data/lib/contrast/components/service.rb +55 -0
- data/lib/contrast/components/settings.rb +12 -4
- data/lib/contrast/config/base_configuration.rb +1 -1
- data/lib/contrast/config/protect_rules_configuration.rb +17 -3
- data/lib/contrast/config/server_configuration.rb +1 -1
- data/lib/contrast/config.rb +0 -6
- data/lib/contrast/configuration.rb +81 -17
- data/lib/contrast/extension/assess/exec_trigger.rb +3 -1
- data/lib/contrast/extension/assess/marshal.rb +3 -2
- data/lib/contrast/extension/assess/string.rb +0 -1
- data/lib/contrast/extension/extension.rb +1 -1
- data/lib/contrast/framework/base_support.rb +0 -5
- data/lib/contrast/framework/grape/support.rb +1 -23
- data/lib/contrast/framework/manager.rb +0 -10
- data/lib/contrast/framework/rails/support.rb +5 -58
- data/lib/contrast/framework/sinatra/support.rb +2 -21
- data/lib/contrast/logger/cef_log.rb +21 -3
- data/lib/contrast/logger/log.rb +1 -11
- data/lib/contrast/tasks/config.rb +4 -2
- data/lib/contrast/utils/assess/event_limit_utils.rb +5 -8
- data/lib/contrast/utils/assess/trigger_method_utils.rb +10 -18
- data/lib/contrast/utils/findings.rb +6 -5
- data/lib/contrast/utils/hash_digest.rb +9 -24
- data/lib/contrast/utils/hash_digest_extend.rb +6 -6
- data/lib/contrast/utils/invalid_configuration_util.rb +21 -58
- data/lib/contrast/utils/log_utils.rb +32 -8
- data/lib/contrast/utils/net_http_base.rb +2 -2
- data/lib/contrast/utils/patching/policy/patch_utils.rb +3 -2
- data/lib/contrast/utils/stack_trace_utils.rb +0 -25
- data/lib/contrast/utils/string_utils.rb +9 -0
- data/lib/contrast/utils/telemetry_client.rb +13 -7
- data/lib/contrast.rb +5 -10
- metadata +22 -28
- data/lib/contrast/agent/reporting/reporting_events/trace_event_source.rb +0 -30
- data/lib/contrast/agent/reporting/reporting_utilities/dtm_message.rb +0 -36
- data/lib/contrast/api/decorators/activity.rb +0 -33
- data/lib/contrast/api/decorators/architecture_component.rb +0 -36
- data/lib/contrast/api/decorators/finding.rb +0 -29
- data/lib/contrast/api/decorators/route_coverage.rb +0 -91
- data/lib/contrast/api/decorators/trace_event.rb +0 -120
- data/lib/contrast/api/decorators/trace_event_object.rb +0 -63
- data/lib/contrast/api/decorators/trace_event_signature.rb +0 -69
- data/lib/contrast/api/decorators/trace_taint_range.rb +0 -52
- data/lib/contrast/config/assess_configuration.rb +0 -93
- data/lib/contrast/config/assess_rules_configuration.rb +0 -32
- data/lib/contrast/config/root_configuration.rb +0 -90
- data/lib/contrast/config/ruby_configuration.rb +0 -81
- data/lib/contrast/config/service_configuration.rb +0 -49
- data/lib/contrast/utils/preflight_util.rb +0 -13
|
@@ -49,36 +49,6 @@ module Contrast
|
|
|
49
49
|
find_all_routes(::Rails.application, [])
|
|
50
50
|
end
|
|
51
51
|
|
|
52
|
-
# Find the current route, based on the provided Request wrapper
|
|
53
|
-
#
|
|
54
|
-
# @param request[Contrast::Agent::Request]
|
|
55
|
-
# @return [Contrast::Api::Dtm::RouteCoverage, nil]
|
|
56
|
-
def current_route request
|
|
57
|
-
return unless ::Rails.cs__respond_to?(:application)
|
|
58
|
-
|
|
59
|
-
# ActionDispatch::Journey::Path::Pattern::MatchData, Hash, ActionDispatch::Journey::Route, Array<String>
|
|
60
|
-
match, _params, route, path = get_full_route(request.rack_request)
|
|
61
|
-
unless route
|
|
62
|
-
logger.warn("Unable to determine the current route of this request: #{ request.rack_request }")
|
|
63
|
-
return
|
|
64
|
-
end
|
|
65
|
-
|
|
66
|
-
original_url = request.rack_request.path_info
|
|
67
|
-
mounted_app = route&.app&.app
|
|
68
|
-
# Route is either the final rails route, or a router that points to a Sinatra controller.
|
|
69
|
-
if mounted_app && Contrast::Framework::Sinatra::Support.sinatra_controller?(mounted_app)
|
|
70
|
-
return mounted_sinatra_route(request, match, path, route, original_url)
|
|
71
|
-
end
|
|
72
|
-
if mounted_app && Contrast::Framework::Grape::Support.grape_controller?(mounted_app)
|
|
73
|
-
return mounted_grape_route(request, match, path, route, original_url)
|
|
74
|
-
end
|
|
75
|
-
|
|
76
|
-
Contrast::Api::Dtm::RouteCoverage.from_action_dispatch_journey(route, original_url)
|
|
77
|
-
rescue StandardError => e
|
|
78
|
-
logger.warn('Unable to determine the current route of this request', e)
|
|
79
|
-
nil
|
|
80
|
-
end
|
|
81
|
-
|
|
82
52
|
# Find the current route, based on the provided Request wrapper
|
|
83
53
|
#
|
|
84
54
|
# @param request[Contrast::Agent::Request]
|
|
@@ -105,9 +75,10 @@ module Contrast
|
|
|
105
75
|
end
|
|
106
76
|
|
|
107
77
|
new_route_coverage = Contrast::Agent::Reporting::RouteCoverage.new
|
|
108
|
-
new_route_coverage
|
|
78
|
+
new_route_coverage&.attach_rails_data(route, original_url)
|
|
79
|
+
new_route_coverage
|
|
109
80
|
rescue StandardError => e
|
|
110
|
-
logger.warn('Unable to determine the current route of this request', e)
|
|
81
|
+
logger.warn('Unable to determine the current route of this request due to exception: ', e)
|
|
111
82
|
nil
|
|
112
83
|
end
|
|
113
84
|
|
|
@@ -132,7 +103,7 @@ module Contrast
|
|
|
132
103
|
# Determine if route is a Rails engine route.
|
|
133
104
|
#
|
|
134
105
|
# @param route [Object] app or route that points to a ::Rails::Engine
|
|
135
|
-
# @return [
|
|
106
|
+
# @return [Boolean, nil] whether the router is an engine or not.
|
|
136
107
|
def engine_route? route
|
|
137
108
|
return false unless route&.app&.app
|
|
138
109
|
return false unless route.app.is_a?(::ActionDispatch::Routing::Mapper::Constraints) ||
|
|
@@ -155,7 +126,7 @@ module Contrast
|
|
|
155
126
|
|
|
156
127
|
match, params, route = route_matches.first
|
|
157
128
|
|
|
158
|
-
# If the current routing node points to a sub-app (::
|
|
129
|
+
# If the current routing node points to a sub-app (::Rails::Engine), dive deeper.
|
|
159
130
|
# Have sub-app route the remainder of the url.
|
|
160
131
|
if engine_route?(route)
|
|
161
132
|
new_req = retrieve_request(request.env)
|
|
@@ -186,36 +157,12 @@ module Contrast
|
|
|
186
157
|
route_list
|
|
187
158
|
end
|
|
188
159
|
|
|
189
|
-
# @param request[Contrast::Agent::Request]
|
|
190
|
-
# @param match [ActionDispatch::Journey::Path::Pattern::MatchData]
|
|
191
|
-
# @param path [Array<String>] the path of this request, built out from each nested
|
|
192
|
-
# ActionDispatch::Journey::Path::Pattern::MatchData
|
|
193
|
-
# @param route [::ActionDispatch::Journey::Route]
|
|
194
|
-
# @param original_url [String] the full url of this request, including the mount
|
|
195
|
-
# @return [Contrast::Api::Dtm::RouteCoverage, nil]
|
|
196
|
-
def mounted_sinatra_route request, match, path, route, original_url
|
|
197
|
-
new_req = unmounted_route(request, match, path)
|
|
198
|
-
Contrast::Framework::Sinatra::Support.current_route(new_req, route.app.app, original_url)
|
|
199
|
-
end
|
|
200
|
-
|
|
201
160
|
# @return [Contrast::Agent::Reporting::RouteCoverage, nil]
|
|
202
161
|
def mounted_new_sinatra_route request, match, path, route, original_url
|
|
203
162
|
new_req = unmounted_route(request, match, path)
|
|
204
163
|
Contrast::Framework::Sinatra::Support.current_route_coverage(new_req, route.app.app, original_url)
|
|
205
164
|
end
|
|
206
165
|
|
|
207
|
-
# @param request[Contrast::Agent::Request]
|
|
208
|
-
# @param match [ActionDispatch::Journey::Path::Pattern::MatchData]
|
|
209
|
-
# @param path [Array<String>] the path of this request, built out from each nested
|
|
210
|
-
# ActionDispatch::Journey::Path::Pattern::MatchData
|
|
211
|
-
# @param route [::ActionDispatch::Journey::Route]
|
|
212
|
-
# @param original_url [String] the full url of this request, including the mount
|
|
213
|
-
# @return [Contrast::Api::Dtm::RouteCoverage, nil]
|
|
214
|
-
def mounted_grape_route request, match, path, route, original_url
|
|
215
|
-
new_req = unmounted_route(request, match, path)
|
|
216
|
-
Contrast::Framework::Grape::Support.current_route(new_req, route.app.app, original_url)
|
|
217
|
-
end
|
|
218
|
-
|
|
219
166
|
# @return [Contrast::Agent::Reporting::RouteCoverage, nil]
|
|
220
167
|
def mounted_new_grape_route request, match, path, route, original_url
|
|
221
168
|
new_req = unmounted_route(request, match, path)
|
|
@@ -64,26 +64,6 @@ module Contrast
|
|
|
64
64
|
routes
|
|
65
65
|
end
|
|
66
66
|
|
|
67
|
-
# Given the current request return a RouteCoverage dtm.
|
|
68
|
-
#
|
|
69
|
-
# @param request [Contrast::Agent::Request] a contrast tracked request.
|
|
70
|
-
# @param controller [::Sinatra::Base] optionally use this controller instead of global ::Sinatra::Base.
|
|
71
|
-
# @return [Contrast::Api::Dtm::RouteCoverage, nil] a Dtm describing the route
|
|
72
|
-
# matched to the request if a match was found.
|
|
73
|
-
def current_route request, controller = ::Sinatra::Base, full_route = nil
|
|
74
|
-
return unless sinatra_controller?(controller)
|
|
75
|
-
|
|
76
|
-
method = request.env[::Rack::REQUEST_METHOD] # GET, PUT, POST, etc...
|
|
77
|
-
|
|
78
|
-
# Find route match--checking superclasses if necessary.
|
|
79
|
-
final_controller, route_pattern = _route_recurse(controller, method, _cleaned_route(request))
|
|
80
|
-
return unless !final_controller.nil? && !route_pattern.nil?
|
|
81
|
-
|
|
82
|
-
full_route ||= request.path_info
|
|
83
|
-
|
|
84
|
-
Contrast::Api::Dtm::RouteCoverage.from_sinatra_route(final_controller, method, route_pattern, full_route)
|
|
85
|
-
end
|
|
86
|
-
|
|
87
67
|
# Given the current request - return a RouteCoverage object
|
|
88
68
|
|
|
89
69
|
# @param request [Contrast::Agent::Request] a contrast tracked request.
|
|
@@ -97,12 +77,13 @@ module Contrast
|
|
|
97
77
|
|
|
98
78
|
# Find route match--checking superclasses if necessary.
|
|
99
79
|
final_controller, route_pattern = _route_recurse(controller, method, _cleaned_route(request))
|
|
100
|
-
return unless final_controller
|
|
80
|
+
return unless final_controller && route_pattern
|
|
101
81
|
|
|
102
82
|
full_route ||= request.env[::Rack::PATH_INFO]
|
|
103
83
|
|
|
104
84
|
new_route_coverage = Contrast::Agent::Reporting::RouteCoverage.new
|
|
105
85
|
new_route_coverage.attach_rack_based_data(final_controller, method, route_pattern, full_route)
|
|
86
|
+
new_route_coverage
|
|
106
87
|
end
|
|
107
88
|
|
|
108
89
|
# Search object space for sinatra controllers--any class that subclasses ::Sinatra::Base.
|
|
@@ -51,16 +51,20 @@ module Contrast
|
|
|
51
51
|
# set by local configuration.
|
|
52
52
|
#
|
|
53
53
|
# @param log_level [String] the level at which to log, as provided by TeamServer settings
|
|
54
|
-
|
|
54
|
+
# @param log_file [String] the file to which to log, as provided by TeamServer settings
|
|
55
|
+
def build_logger log_level = nil, log_file = nil
|
|
56
|
+
current_path_const = find_valid_path(log_file)
|
|
55
57
|
current_level_const = find_valid_level(log_level)
|
|
56
58
|
level_change = current_level_const != previous_level
|
|
59
|
+
path_change = current_path_const != previous_path
|
|
57
60
|
|
|
58
61
|
# don't needlessly recreate logger
|
|
59
|
-
return if @cef_logger && !level_change
|
|
62
|
+
return if @cef_logger && !(level_change || path_change)
|
|
60
63
|
|
|
61
64
|
@previous_level = current_level_const
|
|
65
|
+
@previous_path = current_path_const
|
|
62
66
|
|
|
63
|
-
@_cef_logger = build(path:
|
|
67
|
+
@_cef_logger = build(path: current_path_const, level_const: current_level_const)
|
|
64
68
|
# If we're logging to a new path, then let's start it w/ our helpful
|
|
65
69
|
# data gathering messages
|
|
66
70
|
# log_update if path_change
|
|
@@ -82,6 +86,20 @@ module Contrast
|
|
|
82
86
|
@_cef_logger
|
|
83
87
|
end
|
|
84
88
|
|
|
89
|
+
def find_valid_path log_file
|
|
90
|
+
config = ::Contrast::CONFIG.agent.security_logger
|
|
91
|
+
config_path = config&.path&.length.to_i.positive? ? config.path : nil
|
|
92
|
+
valid_path(config_path || log_file, default_name: DEFAULT_CEF_NAME)
|
|
93
|
+
end
|
|
94
|
+
|
|
95
|
+
# @return [::Ougai::Logging::Severity] the level at which to log
|
|
96
|
+
def find_valid_level log_level
|
|
97
|
+
config = ::Contrast::CONFIG.agent.security_logger
|
|
98
|
+
config_level = config&.level&.length&.positive? ? config.level : nil
|
|
99
|
+
|
|
100
|
+
valid_level(config_level || log_level)
|
|
101
|
+
end
|
|
102
|
+
|
|
85
103
|
def log msg, level = @_cef_logger.level
|
|
86
104
|
case level
|
|
87
105
|
when ::Logger::Severity::INFO
|
data/lib/contrast/logger/log.rb
CHANGED
|
@@ -65,7 +65,7 @@ module Contrast
|
|
|
65
65
|
@previous_path = current_path
|
|
66
66
|
@previous_level = current_level_const
|
|
67
67
|
|
|
68
|
-
progname = Contrast::CONFIG.
|
|
68
|
+
progname = Contrast::CONFIG.agent.logger.progname
|
|
69
69
|
@_logger = build(path: current_path, level_const: current_level_const, progname: progname)
|
|
70
70
|
# If we're logging to a new path, then let's start it w/ our helpful
|
|
71
71
|
# data gathering messages
|
|
@@ -85,16 +85,6 @@ module Contrast
|
|
|
85
85
|
def logger
|
|
86
86
|
@_logger
|
|
87
87
|
end
|
|
88
|
-
|
|
89
|
-
# StringIO is a valid path because it logs directly to a string buffer
|
|
90
|
-
def write_permission? path
|
|
91
|
-
return false if path.nil?
|
|
92
|
-
return true if path.is_a?(StringIO)
|
|
93
|
-
return File.writable?(path) if File.exist?(path)
|
|
94
|
-
|
|
95
|
-
dir_name = File.dirname(File.absolute_path(path))
|
|
96
|
-
File.writable?(dir_name)
|
|
97
|
-
end
|
|
98
88
|
end
|
|
99
89
|
end
|
|
100
90
|
end
|
|
@@ -73,7 +73,9 @@ module Contrast
|
|
|
73
73
|
config = Contrast::Configuration.new
|
|
74
74
|
abort('Unable to Build Config') unless config
|
|
75
75
|
missing = []
|
|
76
|
-
|
|
76
|
+
|
|
77
|
+
api_hash = config.api.to_contrast_hash
|
|
78
|
+
|
|
77
79
|
api_hash.each_key do |key|
|
|
78
80
|
value = mask_keys(api_hash, key)
|
|
79
81
|
if value.is_a?(Contrast::Config::ApiProxyConfiguration)
|
|
@@ -123,7 +125,7 @@ module Contrast
|
|
|
123
125
|
def self.validate_headers
|
|
124
126
|
missing = []
|
|
125
127
|
reporter = Contrast::Agent::Reporter.new
|
|
126
|
-
reporter_headers = reporter.client.headers.
|
|
128
|
+
reporter_headers = reporter.client.headers.to_contrast_hash
|
|
127
129
|
reporter_headers.each_key do |key|
|
|
128
130
|
value = mask_keys(reporter_headers, key)
|
|
129
131
|
missing << key if value.nil?
|
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
# frozen_string_literal: true
|
|
3
3
|
|
|
4
4
|
require 'contrast/components/logger'
|
|
5
|
+
require 'contrast/components/assess'
|
|
5
6
|
|
|
6
7
|
module Contrast
|
|
7
8
|
module Utils
|
|
@@ -16,14 +17,12 @@ module Contrast
|
|
|
16
17
|
return false unless (context = Contrast::Agent::REQUEST_TRACKER.current)
|
|
17
18
|
|
|
18
19
|
if method_policy.source_node
|
|
19
|
-
max =
|
|
20
|
-
Contrast::Config::AssessConfiguration::DEFAULT_MAX_SOURCE_EVENTS)
|
|
20
|
+
max = ::Contrast::ASSESS.max_context_source_events
|
|
21
21
|
return at_limit?(method_policy, context.source_event_count, max)
|
|
22
22
|
|
|
23
23
|
end
|
|
24
24
|
if method_policy.propagation_node
|
|
25
|
-
max =
|
|
26
|
-
Contrast::Config::AssessConfiguration::DEFAULT_MAX_PROPAGATION_EVENTS)
|
|
25
|
+
max = ::Contrast::ASSESS.max_propagation_events
|
|
27
26
|
return at_limit?(method_policy, context.propagation_event_count, max)
|
|
28
27
|
end
|
|
29
28
|
|
|
@@ -33,7 +32,7 @@ module Contrast
|
|
|
33
32
|
def event_limit_for_rule? rule_id # rubocop:disable Metrics/AbcSize
|
|
34
33
|
return false unless (context = Contrast::Agent::REQUEST_TRACKER.current)
|
|
35
34
|
|
|
36
|
-
saved_request_ids = rule_counts.keys.map { |k| k.to_s.split('_')[1] }
|
|
35
|
+
saved_request_ids = rule_counts.keys.map { |k| k.to_s.split('_')[1].to_i }
|
|
37
36
|
|
|
38
37
|
# if we passed the threshold and we actually have records for that request - wipe them
|
|
39
38
|
if saved_request_ids.uniq.include?(context.request.__id__)
|
|
@@ -43,15 +42,13 @@ module Contrast
|
|
|
43
42
|
|
|
44
43
|
# if we have recorded rule counts, but none of them are for the current request_id
|
|
45
44
|
# eventually we can try and play with the time_limit_threshold -> DEFAULT_MAX_RULE_TIME_THRESHOLD
|
|
46
|
-
|
|
45
|
+
if !rule_counts.empty? && !saved_request_ids.include?(context.request.__id__)
|
|
47
46
|
restore_defaults
|
|
48
47
|
threshold_time_limit
|
|
49
48
|
end
|
|
50
49
|
|
|
51
50
|
rule_key = "#{ rule_id }_#{ context.request.__id__ }"
|
|
52
51
|
rule_counts[rule_key] += 1
|
|
53
|
-
# TODO: RUBY-1680 remove default
|
|
54
|
-
# we don't need the default here because we either return from the config, or we return the default
|
|
55
52
|
rule_counts[rule_key] >= ::Contrast::ASSESS.max_rule_reported
|
|
56
53
|
end
|
|
57
54
|
|
|
@@ -72,7 +72,8 @@ module Contrast
|
|
|
72
72
|
elsif trigger_node.dataflow?
|
|
73
73
|
apply_dataflow_rule(trigger_node, source, object, ret, *args)
|
|
74
74
|
else # trigger rule - just calling the method is dangerous
|
|
75
|
-
build_finding(trigger_node, source, object, ret, *args)
|
|
75
|
+
finding = build_finding(trigger_node, source, object, ret, *args)
|
|
76
|
+
Contrast::Agent::Assess::Policy::TriggerMethod.report_finding(finding) if finding
|
|
76
77
|
end
|
|
77
78
|
rescue StandardError => e
|
|
78
79
|
logger.warn('Unable to apply trigger', e, node_id: trigger_node.id)
|
|
@@ -81,8 +82,8 @@ module Contrast
|
|
|
81
82
|
# This is our method that actually checks the taint on the object our trigger_node targets for our Regexp
|
|
82
83
|
# based rules.
|
|
83
84
|
#
|
|
84
|
-
# @param trigger_node [Contrast::Agent::Assess::Policy::TriggerNode] the node to direct applying this
|
|
85
|
-
#
|
|
85
|
+
# @param trigger_node [Contrast::Agent::Assess::Policy::TriggerNode] the node to direct applying this trigger
|
|
86
|
+
# event
|
|
86
87
|
# @param source [Object] the source of the Trigger Event
|
|
87
88
|
# @param object [Object] the Object on which the method was invoked
|
|
88
89
|
# @param ret [Object] the Return of the invoked method
|
|
@@ -92,7 +93,8 @@ module Contrast
|
|
|
92
93
|
return if trigger_node.good_value && source.match?(trigger_node.good_value)
|
|
93
94
|
return if trigger_node.bad_value && source !~ trigger_node.bad_value
|
|
94
95
|
|
|
95
|
-
build_finding(trigger_node, source, object, ret, *args)
|
|
96
|
+
finding = build_finding(trigger_node, source, object, ret, *args)
|
|
97
|
+
Contrast::Agent::Assess::Policy::TriggerMethod.report_finding(finding) if finding
|
|
96
98
|
end
|
|
97
99
|
|
|
98
100
|
# This is our method that actually checks the taint on the object our trigger_node targets for our Dataflow
|
|
@@ -104,33 +106,23 @@ module Contrast
|
|
|
104
106
|
# @param object [Object] the Object on which the method was invoked
|
|
105
107
|
# @param ret [Object] the Return of the invoked method
|
|
106
108
|
# @param args [Array<Object>] the Arguments with which the method was invoked
|
|
107
|
-
def apply_dataflow_rule trigger_node, source, object, ret, *args
|
|
108
|
-
return unless source
|
|
109
|
+
def apply_dataflow_rule trigger_node, source, object, ret, *args
|
|
110
|
+
return unless source && Contrast::Agent::Assess::Tracker.tracked?(source)
|
|
109
111
|
|
|
110
112
|
if Contrast::Agent::Assess::Tracker.trackable?(source)
|
|
111
|
-
return unless Contrast::Agent::Assess::Tracker.tracked?(source)
|
|
112
113
|
return unless trigger_node.violated?(source)
|
|
113
114
|
|
|
114
|
-
build_finding(trigger_node, source, object, ret, *args)
|
|
115
|
+
finding = build_finding(trigger_node, source, object, ret, *args)
|
|
116
|
+
report_finding(finding) if finding
|
|
115
117
|
elsif Contrast::Utils::DuckUtils.iterable_hash?(source)
|
|
116
|
-
return unless Contrast::Agent::Assess::Tracker.tracked?(source)
|
|
117
|
-
|
|
118
118
|
source.each_pair do |key, value|
|
|
119
119
|
apply_dataflow_rule(trigger_node, key, object, ret, *args)
|
|
120
120
|
apply_dataflow_rule(trigger_node, value, object, ret, *args)
|
|
121
121
|
end
|
|
122
122
|
elsif Contrast::Utils::DuckUtils.iterable_enumerable?(source)
|
|
123
|
-
return unless Contrast::Agent::Assess::Tracker.tracked?(source)
|
|
124
|
-
|
|
125
123
|
source.each do |value|
|
|
126
124
|
apply_dataflow_rule(trigger_node, value, object, ret, *args)
|
|
127
125
|
end
|
|
128
|
-
else
|
|
129
|
-
logger.debug('Trigger source is untrackable. Unable to inspect.',
|
|
130
|
-
node_id: trigger_node.id,
|
|
131
|
-
source_id: source.__id__,
|
|
132
|
-
source_type: source.cs__class.cs__name,
|
|
133
|
-
frozen: source.cs__frozen?)
|
|
134
126
|
end
|
|
135
127
|
end
|
|
136
128
|
end
|
|
@@ -52,11 +52,12 @@ module Contrast
|
|
|
52
52
|
|
|
53
53
|
while @_collection.any?
|
|
54
54
|
finding = @_collection.pop
|
|
55
|
-
Contrast::Agent::Assess::Policy::TriggerMethod.build_finding(finding[:trigger_node],
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
55
|
+
collected = Contrast::Agent::Assess::Policy::TriggerMethod.build_finding(finding[:trigger_node],
|
|
56
|
+
finding[:source],
|
|
57
|
+
finding[:object],
|
|
58
|
+
finding[:ret],
|
|
59
|
+
finding[:args])
|
|
60
|
+
Contrast::Agent::Assess::Policy::TriggerMethod.report_finding(collected) if collected
|
|
60
61
|
end
|
|
61
62
|
true
|
|
62
63
|
end
|
|
@@ -29,12 +29,10 @@ module Contrast
|
|
|
29
29
|
@crc32 = 0
|
|
30
30
|
end
|
|
31
31
|
|
|
32
|
-
# Update to CRC checksum the finding route and verb if finding route
|
|
33
|
-
#
|
|
34
|
-
# request or Contrast::REQUEST_TRACKER.current.request uri and used request
|
|
35
|
-
# method.
|
|
32
|
+
# Update to CRC checksum the finding route and verb if finding route is available, else update the passed
|
|
33
|
+
# request or Contrast::REQUEST_TRACKER.current.request uri and used request method.
|
|
36
34
|
#
|
|
37
|
-
# @param finding [Contrast::
|
|
35
|
+
# @param finding [Contrast::Agent::Reporting::Finding] finding to be reported
|
|
38
36
|
# @param request [Contrast::Agent::Request] our wrapper around the Rack::Request.
|
|
39
37
|
# @return checksum [Integer, nil] returns nil if there is no request context or tracking
|
|
40
38
|
# is disabled.
|
|
@@ -43,12 +41,9 @@ module Contrast
|
|
|
43
41
|
return unless context || ::Contrast::ASSESS.non_request_tracking?
|
|
44
42
|
|
|
45
43
|
if (route = finding.routes[0])
|
|
46
|
-
|
|
47
|
-
|
|
44
|
+
update(route.signature)
|
|
45
|
+
if (observation = route.observations[0])
|
|
48
46
|
update(observation.verb)
|
|
49
|
-
else
|
|
50
|
-
update(route.route) # the normalized URL used to access the method in the route.
|
|
51
|
-
update(route.verb) # the HTTP Verb used to access the method in the route.
|
|
52
47
|
end
|
|
53
48
|
return
|
|
54
49
|
end
|
|
@@ -60,24 +55,14 @@ module Contrast
|
|
|
60
55
|
end
|
|
61
56
|
|
|
62
57
|
# Update to CRC checksum the event source name and source type.
|
|
63
|
-
# Expects Contrast::Api::Dtm::TraceEvent || Contrast::Agent::Assess::Events::SourceEvent
|
|
64
58
|
#
|
|
65
|
-
# @param events [
|
|
66
|
-
# <Contrast::Agent::Assess::Events::SourceEvent>]
|
|
67
|
-
# Array of TraceEvents
|
|
59
|
+
# @param events [Array<Contrast::Agent::Reporting::FindingEvent>]
|
|
68
60
|
# @return checksum [Integer, nil] returns nil if there is no events
|
|
69
61
|
def update_on_sources events
|
|
70
|
-
return unless events&.any?
|
|
71
|
-
|
|
72
62
|
events.each do |event|
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
update(source.name) # rubocop:disable Security/Module/Name
|
|
77
|
-
end
|
|
78
|
-
elsif event.cs__is_a?(Contrast::Agent::Assess::Events::SourceEvent)
|
|
79
|
-
update(event.source_type)
|
|
80
|
-
update(event.source_name)
|
|
63
|
+
event.event_sources.each do |source|
|
|
64
|
+
update(source.type)
|
|
65
|
+
update(source.name) # rubocop:disable Security/Module/Name
|
|
81
66
|
end
|
|
82
67
|
end
|
|
83
68
|
end
|
|
@@ -34,7 +34,7 @@ module Contrast
|
|
|
34
34
|
# crypto(crypto-bad-ciphers, crypto-bad-mac) rules or trigger event
|
|
35
35
|
# and returns string representation.
|
|
36
36
|
#
|
|
37
|
-
# @param finding [Contrast::
|
|
37
|
+
# @param finding [Contrast::Agent::Reporting::Finding] to be reported
|
|
38
38
|
# @param source [Object] the source of the Trigger Event
|
|
39
39
|
# @param request [Contrast::Agent::Request] our wrapper around the Rack::Request.
|
|
40
40
|
# @return checksum [String] String representation of CRC32 checksum
|
|
@@ -50,7 +50,7 @@ module Contrast
|
|
|
50
50
|
# Generates the hash checksum for configurations. Converts the finding rule_id, session_id and configPath and
|
|
51
51
|
# to CRC32 checksum and returns string representation to be appended to Contrast::Api::Dtm::Finding
|
|
52
52
|
#
|
|
53
|
-
# @param finding [Contrast::
|
|
53
|
+
# @param finding [Contrast::Agent::Reporting::Finding] to be reported
|
|
54
54
|
# @return checksum [String] String representation of CRC32 checksum.
|
|
55
55
|
def generate_config_hash finding
|
|
56
56
|
hash = new
|
|
@@ -65,7 +65,7 @@ module Contrast
|
|
|
65
65
|
# Generates the hash checksum for class scanning. Converts the rule_id, finding.properties(source, name)
|
|
66
66
|
# to CRC32 checksum and returns string representation.
|
|
67
67
|
#
|
|
68
|
-
# @param finding [Contrast::
|
|
68
|
+
# @param finding [Contrast::Agent::Reporting::Finding] to be reported
|
|
69
69
|
# @return checksum [String] String representation of CRC32 checksum.
|
|
70
70
|
def generate_class_scanning_hash finding
|
|
71
71
|
hash = new
|
|
@@ -86,7 +86,7 @@ module Contrast
|
|
|
86
86
|
# used in #generate_event_hash.
|
|
87
87
|
#
|
|
88
88
|
#
|
|
89
|
-
# @param finding [Contrast::
|
|
89
|
+
# @param finding [Contrast::Agent::Reporting::Finding] to be reported
|
|
90
90
|
# @param algorithm [Object] the source of the Trigger Event
|
|
91
91
|
# @param request [Contrast::Agent::Request] our wrapper around the Rack::Request.
|
|
92
92
|
# @return checksum [String] String representation of CRC32 checksum
|
|
@@ -101,7 +101,7 @@ module Contrast
|
|
|
101
101
|
# Generates the hash checksum for dataflow when the finding events are more than one
|
|
102
102
|
# used in #generate_event_hash.
|
|
103
103
|
#
|
|
104
|
-
# @param finding [Contrast::
|
|
104
|
+
# @param finding [Contrast::Agent::Reporting::Finding] to be reported
|
|
105
105
|
# @param request [Contrast::Agent::Request] our wrapper around the Rack::Request.
|
|
106
106
|
# @return checksum [String] String representation of CRC32 checksum
|
|
107
107
|
def generate_dataflow_hash finding, request
|
|
@@ -115,7 +115,7 @@ module Contrast
|
|
|
115
115
|
# Generates the hash checksum for trigger
|
|
116
116
|
# used in #generate_event_hash.
|
|
117
117
|
#
|
|
118
|
-
# @param finding [Contrast::
|
|
118
|
+
# @param finding [Contrast::Agent::Reporting::Finding] to be reported
|
|
119
119
|
# @param request [Contrast::Agent::Request] our wrapper around the Rack::Request.
|
|
120
120
|
# @return checksum [String] String representation of CRC32 checksum
|
|
121
121
|
def generate_trigger_hash finding, request
|
|
@@ -7,8 +7,8 @@ require 'contrast/components/scope'
|
|
|
7
7
|
|
|
8
8
|
module Contrast
|
|
9
9
|
module Utils
|
|
10
|
-
# This utility allows us to report invalid configurations detected in
|
|
11
|
-
#
|
|
10
|
+
# This utility allows us to report invalid configurations detected in customer applications, as determined by
|
|
11
|
+
# Configuration Rules at runtime.
|
|
12
12
|
module InvalidConfigurationUtil
|
|
13
13
|
include Contrast::Components::Logger::InstanceMethods
|
|
14
14
|
include Contrast::Components::Scope::InstanceMethods
|
|
@@ -20,64 +20,39 @@ module Contrast
|
|
|
20
20
|
# Build and report a finding for the given rule
|
|
21
21
|
#
|
|
22
22
|
# @param rule_id [String] the rule that was violated by the configuration
|
|
23
|
-
# @param user_provided_options [Hash] the configuration value(s) which
|
|
24
|
-
#
|
|
25
|
-
# @param call_location [Thread::Backtrace::Location] the location where
|
|
26
|
-
# the bad configuration was set
|
|
23
|
+
# @param user_provided_options [Hash] the configuration value(s) which violated the rule
|
|
24
|
+
# @param call_location [Thread::Backtrace::Location] the location where the bad configuration was set
|
|
27
25
|
def cs__report_finding rule_id, user_provided_options, call_location
|
|
28
26
|
with_contrast_scope do
|
|
29
|
-
finding =
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
hash = Contrast::Utils::HashDigest.generate_config_hash(finding)
|
|
34
|
-
finding.hash_code = Contrast::Utils::StringUtils.force_utf8(hash)
|
|
35
|
-
finding.preflight = Contrast::Utils::PreflightUtil.create_preflight(finding)
|
|
36
|
-
if Contrast::Agent::Reporter.enabled? # TODO: RUBY-1438 -- remove
|
|
37
|
-
cs__report_new_finding(hash, rule_id, user_provided_options, call_location)
|
|
38
|
-
else
|
|
39
|
-
Contrast::Agent::Assess::Policy::TriggerMethod.report_finding(finding)
|
|
40
|
-
end
|
|
27
|
+
finding = build_finding(rule_id, user_provided_options, call_location)
|
|
28
|
+
return unless finding
|
|
29
|
+
|
|
30
|
+
Contrast::Agent::Assess::Policy::TriggerMethod.report_finding(finding)
|
|
41
31
|
end
|
|
42
32
|
rescue StandardError => e
|
|
43
33
|
logger.error('Unable to build a finding', e, rule: rule_id)
|
|
44
34
|
end
|
|
45
35
|
|
|
46
|
-
def cs__report_new_finding hash_code, rule_id, user_provided_options, call_location
|
|
47
|
-
new_preflight = Contrast::Agent::Reporting::Preflight.new
|
|
48
|
-
new_preflight_message = Contrast::Agent::Reporting::PreflightMessage.new
|
|
49
|
-
new_preflight_message.hash_code = hash_code
|
|
50
|
-
new_preflight_message.data = "#{ rule_id },#{ hash_code }"
|
|
51
|
-
new_preflight.messages << new_preflight_message
|
|
52
|
-
|
|
53
|
-
ruby_finding = Contrast::Agent::Reporting::Finding.new(rule_id)
|
|
54
|
-
ruby_finding.hash_code = hash_code
|
|
55
|
-
set_new_finding_properties(ruby_finding, user_provided_options, call_location)
|
|
56
|
-
Contrast::Agent.reporter&.send_event(new_preflight)
|
|
57
|
-
Contrast::Agent::Reporting::ReportingStorage[hash_code] = ruby_finding
|
|
58
|
-
end
|
|
59
|
-
|
|
60
36
|
private
|
|
61
37
|
|
|
62
38
|
# Set the properties needed to report and subsequently render this finding on the finding given.
|
|
63
39
|
#
|
|
64
|
-
# @param
|
|
65
|
-
# @param user_provided_options [Hash] the configuration value(s) which
|
|
66
|
-
#
|
|
67
|
-
# @
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
40
|
+
# @param rule_id [String] the rule that was violated by the configuration
|
|
41
|
+
# @param user_provided_options [Hash] the configuration value(s) which violated the rule
|
|
42
|
+
# @param call_location [Thread::Backtrace::Location] the location where the bad configuration was set
|
|
43
|
+
# @return [Contrast::Agent::Reporting::Finding]
|
|
44
|
+
def build_finding rule_id, user_provided_options, call_location
|
|
45
|
+
finding = Contrast::Agent::Reporting::Finding.new(rule_id)
|
|
46
|
+
finding.properties[CS__SESSION_ID] = user_provided_options[:key].to_s if user_provided_options
|
|
71
47
|
# just get the file name, not the full path
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
finding.
|
|
75
|
-
finding
|
|
76
|
-
file_path = call_location.absolute_path
|
|
77
|
-
snippet = file_snippet(file_path, call_location)
|
|
78
|
-
finding.properties[CS__SNIPPET] = Contrast::Utils::StringUtils.force_utf8(snippet)
|
|
48
|
+
finding.properties[CS__PATH] = call_location.path.split(Contrast::Utils::ObjectShare::SLASH).last
|
|
49
|
+
finding.properties[CS__SNIPPET] = file_snippet(call_location.absolute_path, call_location)
|
|
50
|
+
finding.hash_code = Contrast::Utils::HashDigest.generate_config_hash(finding)
|
|
51
|
+
finding
|
|
79
52
|
end
|
|
80
53
|
|
|
54
|
+
# @param file_path [String] full path to the file with the property
|
|
55
|
+
# @param call_location [Thread::Backtrace::Location] the location where the bad configuration was set
|
|
81
56
|
def file_snippet file_path, call_location
|
|
82
57
|
idx = call_location&.lineno
|
|
83
58
|
if file_path && idx && File.exist?(file_path)
|
|
@@ -94,18 +69,6 @@ module Contrast
|
|
|
94
69
|
end
|
|
95
70
|
call_location&.label&.dup
|
|
96
71
|
end
|
|
97
|
-
|
|
98
|
-
def set_new_finding_properties finding, user_provided_options, call_location
|
|
99
|
-
path = call_location.path
|
|
100
|
-
# just get the file name, not the full path
|
|
101
|
-
path = path.split(Contrast::Utils::ObjectShare::SLASH).last
|
|
102
|
-
session_id = user_provided_options[:key].to_s if user_provided_options
|
|
103
|
-
finding.properties[CS__SESSION_ID] = session_id
|
|
104
|
-
finding.properties[CS__PATH] = path
|
|
105
|
-
file_path = call_location.absolute_path
|
|
106
|
-
snippet = file_snippet(file_path, call_location)
|
|
107
|
-
finding.properties[CS__SNIPPET] = snippet
|
|
108
|
-
end
|
|
109
72
|
end
|
|
110
73
|
end
|
|
111
74
|
end
|