contrast-agent 6.2.0 → 6.5.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/.gitignore +0 -3
- data/.simplecov +1 -0
- data/Rakefile +0 -27
- data/ext/cs__assess_basic_object/cs__assess_basic_object.c +7 -5
- data/ext/cs__assess_kernel/cs__assess_kernel.c +14 -3
- data/ext/cs__assess_kernel/cs__assess_kernel.h +2 -0
- data/ext/cs__assess_marshal_module/cs__assess_marshal_module.c +10 -3
- data/ext/cs__assess_marshal_module/cs__assess_marshal_module.h +2 -1
- data/ext/cs__assess_regexp/cs__assess_regexp.c +9 -7
- data/ext/{cs__assess_string_interpolation26/cs__assess_string_interpolation26.c → cs__assess_string_interpolation/cs__assess_string_interpolation.c} +14 -3
- data/ext/{cs__assess_string_interpolation26/cs__assess_string_interpolation26.h → cs__assess_string_interpolation/cs__assess_string_interpolation.h} +1 -1
- data/ext/{cs__assess_string_interpolation26 → cs__assess_string_interpolation}/extconf.rb +0 -0
- data/ext/cs__common/cs__common.c +5 -4
- data/ext/cs__contrast_patch/cs__contrast_patch.c +17 -11
- data/lib/contrast/agent/assess/events/source_event.rb +16 -12
- data/lib/contrast/agent/assess/finalizers/hash.rb +1 -0
- data/lib/contrast/agent/assess/policy/policy_node.rb +6 -0
- data/lib/contrast/agent/assess/policy/propagation_method.rb +8 -42
- data/lib/contrast/agent/assess/policy/propagation_node.rb +8 -0
- data/lib/contrast/agent/assess/policy/propagator/base.rb +2 -0
- data/lib/contrast/agent/assess/policy/propagator/custom.rb +4 -0
- data/lib/contrast/agent/assess/policy/propagator/database_write.rb +5 -0
- data/lib/contrast/agent/assess/policy/propagator/split.rb +3 -0
- data/lib/contrast/agent/assess/policy/source_method.rb +7 -47
- data/lib/contrast/agent/assess/policy/source_node.rb +1 -0
- data/lib/contrast/agent/assess/policy/trigger_method.rb +9 -3
- data/lib/contrast/agent/assess/policy/trigger_node.rb +8 -0
- data/lib/contrast/agent/assess/property/evented.rb +4 -18
- data/lib/contrast/agent/assess/tag.rb +19 -0
- data/lib/contrast/agent/assess/tracker.rb +12 -0
- data/lib/contrast/agent/at_exit_hook.rb +8 -8
- data/lib/contrast/agent/inventory/database_config.rb +6 -3
- data/lib/contrast/agent/inventory/dependency_analysis.rb +5 -4
- data/lib/contrast/agent/inventory/dependency_usage_analysis.rb +11 -11
- data/lib/contrast/agent/inventory/policy/datastores.rb +1 -1
- data/lib/contrast/agent/inventory/policy/policy.rb +1 -1
- data/lib/contrast/agent/middleware.rb +4 -0
- data/lib/contrast/agent/patching/policy/after_load_patcher.rb +27 -2
- data/lib/contrast/agent/patching/policy/method_policy.rb +3 -3
- data/lib/contrast/agent/patching/policy/policy.rb +5 -0
- data/lib/contrast/agent/patching/policy/policy_node.rb +6 -0
- data/lib/contrast/agent/patching/policy/trigger_node.rb +3 -0
- data/lib/contrast/agent/protect/policy/applies_deserialization_rule.rb +3 -4
- data/lib/contrast/agent/protect/policy/applies_path_traversal_rule.rb +1 -0
- data/lib/contrast/agent/protect/policy/rule_applicator.rb +2 -2
- data/lib/contrast/agent/protect/rule/base.rb +1 -0
- data/lib/contrast/agent/protect/rule/no_sqli.rb +2 -0
- data/lib/contrast/agent/reporting/reporter.rb +32 -7
- data/lib/contrast/agent/reporting/reporter_heartbeat.rb +22 -18
- data/lib/contrast/agent/reporting/reporting_events/application_defend_activity.rb +17 -21
- data/lib/contrast/agent/reporting/reporting_events/application_defend_attack_sample.rb +1 -1
- data/lib/contrast/agent/reporting/reporting_events/application_defend_attack_sample_activity.rb +26 -3
- data/lib/contrast/agent/reporting/reporting_events/application_update.rb +5 -24
- data/lib/contrast/agent/reporting/reporting_events/architecture_component.rb +8 -1
- data/lib/contrast/agent/reporting/reporting_events/discovered_route.rb +8 -1
- data/lib/contrast/agent/reporting/reporting_events/finding.rb +7 -1
- data/lib/contrast/agent/reporting/reporting_events/finding_event.rb +10 -1
- data/lib/contrast/agent/reporting/reporting_events/finding_event_object.rb +11 -1
- data/lib/contrast/agent/reporting/reporting_events/finding_event_parent_object.rb +11 -1
- data/lib/contrast/agent/reporting/reporting_events/finding_event_property.rb +12 -1
- data/lib/contrast/agent/reporting/reporting_events/finding_event_signature.rb +10 -1
- data/lib/contrast/agent/reporting/reporting_events/finding_event_source.rb +11 -1
- data/lib/contrast/agent/reporting/reporting_events/finding_event_stack.rb +11 -1
- data/lib/contrast/agent/reporting/reporting_events/finding_event_taint_range.rb +11 -1
- data/lib/contrast/agent/reporting/reporting_events/finding_request.rb +11 -1
- data/lib/contrast/agent/reporting/reporting_events/library_discovery.rb +29 -32
- data/lib/contrast/agent/reporting/reporting_events/library_usage_observation.rb +13 -1
- data/lib/contrast/agent/reporting/reporting_events/observed_library_usage.rb +11 -8
- data/lib/contrast/agent/reporting/reporting_events/observed_route.rb +12 -5
- data/lib/contrast/agent/reporting/reporting_events/preflight_message.rb +8 -1
- data/lib/contrast/agent/reporting/reporting_events/reporting_event.rb +9 -1
- data/lib/contrast/agent/reporting/reporting_events/route_discovery.rb +10 -1
- data/lib/contrast/agent/reporting/reporting_events/route_discovery_observation.rb +11 -4
- data/lib/contrast/agent/reporting/reporting_events/server_activity.rb +0 -8
- data/lib/contrast/agent/reporting/reporting_utilities/audit.rb +1 -4
- data/lib/contrast/agent/reporting/reporting_utilities/dtm_message.rb +0 -22
- data/lib/contrast/agent/reporting/reporting_utilities/reporter_client.rb +1 -3
- data/lib/contrast/agent/reporting/reporting_utilities/reporter_client_utils.rb +1 -11
- data/lib/contrast/agent/request.rb +5 -7
- data/lib/contrast/agent/request_context.rb +16 -17
- data/lib/contrast/agent/request_context_extend.rb +8 -9
- data/lib/contrast/agent/request_handler.rb +9 -38
- data/lib/contrast/agent/rule_set.rb +4 -0
- data/lib/contrast/agent/service_heartbeat.rb +3 -4
- data/lib/contrast/agent/static_analysis.rb +7 -12
- data/lib/contrast/agent/telemetry/base.rb +35 -35
- data/lib/contrast/agent/telemetry/events/exceptions/telemetry_exception_base.rb +2 -0
- data/lib/contrast/agent/telemetry/events/exceptions/telemetry_exception_event.rb +2 -0
- data/lib/contrast/agent/telemetry/events/exceptions/telemetry_exception_message.rb +5 -2
- data/lib/contrast/agent/telemetry/events/exceptions/telemetry_exception_message_exception.rb +3 -0
- data/lib/contrast/agent/telemetry/events/exceptions/telemetry_exception_stack_frame.rb +3 -0
- data/lib/contrast/agent/telemetry/events/exceptions/telemetry_exceptions.rb +0 -1
- data/lib/contrast/agent/thread_watcher.rb +1 -4
- data/lib/contrast/agent/version.rb +1 -1
- data/lib/contrast/agent/worker_thread.rb +10 -0
- data/lib/contrast/api/communication/socket.rb +1 -0
- data/lib/contrast/api/decorators/message.rb +0 -6
- data/lib/contrast/api/decorators.rb +0 -2
- data/lib/contrast/api/dtm.pb.rb +1 -1
- data/lib/contrast/api/settings.pb.rb +1 -1
- data/lib/contrast/components/agent.rb +51 -13
- data/lib/contrast/components/assess.rb +16 -6
- data/lib/contrast/components/config.rb +18 -2
- data/lib/contrast/components/contrast_service.rb +1 -1
- data/lib/contrast/components/heap_dump.rb +51 -1
- data/lib/contrast/components/inventory.rb +19 -13
- data/lib/contrast/components/logger.rb +18 -0
- data/lib/contrast/config/assess_configuration.rb +28 -0
- data/lib/contrast/config/base_configuration.rb +8 -15
- data/lib/contrast/config/root_configuration.rb +12 -8
- data/lib/contrast/config/ruby_configuration.rb +2 -9
- data/lib/contrast/config/service_configuration.rb +4 -4
- data/lib/contrast/config.rb +0 -6
- data/lib/contrast/configuration.rb +0 -2
- data/lib/contrast/extension/assess/eval_trigger.rb +0 -4
- data/lib/contrast/extension/assess/hash.rb +3 -2
- data/lib/contrast/extension/assess/kernel.rb +22 -0
- data/lib/contrast/extension/assess/marshal.rb +16 -0
- data/lib/contrast/extension/assess/string.rb +21 -20
- data/lib/contrast/extension/object.rb +19 -0
- data/lib/contrast/framework/base_support.rb +8 -0
- data/lib/contrast/framework/manager.rb +6 -20
- data/lib/contrast/framework/manager_extend.rb +0 -1
- data/lib/contrast/framework/rails/patch/action_controller_live_buffer.rb +11 -16
- data/lib/contrast/framework/rails/support.rb +4 -1
- data/lib/contrast/logger/aliased_logging.rb +2 -0
- data/lib/contrast/logger/log.rb +2 -1
- data/lib/contrast/utils/assess/event_limit_utils.rb +96 -0
- data/lib/contrast/utils/assess/propagation_method_utils.rb +27 -7
- data/lib/contrast/utils/assess/source_method_utils.rb +0 -9
- data/lib/contrast/utils/log_utils.rb +2 -2
- data/lib/contrast/utils/lru_cache.rb +3 -0
- data/lib/contrast/utils/middleware_utils.rb +2 -0
- data/lib/contrast/utils/patching/policy/patch_utils.rb +6 -23
- data/lib/contrast/utils/telemetry_client.rb +7 -7
- data/lib/contrast.rb +37 -18
- data/lib/protobuf/code_generator.rb +129 -0
- data/lib/protobuf/decoder.rb +28 -0
- data/lib/protobuf/deprecation.rb +117 -0
- data/lib/protobuf/descriptors/google/protobuf/compiler/plugin.pb.rb +79 -0
- data/lib/protobuf/descriptors/google/protobuf/descriptor.pb.rb +360 -0
- data/lib/protobuf/descriptors.rb +3 -0
- data/lib/protobuf/encoder.rb +11 -0
- data/lib/protobuf/enum.rb +365 -0
- data/lib/protobuf/exceptions.rb +9 -0
- data/lib/protobuf/field/base_field.rb +380 -0
- data/lib/protobuf/field/base_field_object_definitions.rb +504 -0
- data/lib/protobuf/field/bool_field.rb +64 -0
- data/lib/protobuf/field/bytes_field.rb +67 -0
- data/lib/protobuf/field/double_field.rb +25 -0
- data/lib/protobuf/field/enum_field.rb +56 -0
- data/lib/protobuf/field/field_array.rb +102 -0
- data/lib/protobuf/field/field_hash.rb +122 -0
- data/lib/protobuf/field/fixed32_field.rb +25 -0
- data/lib/protobuf/field/fixed64_field.rb +28 -0
- data/lib/protobuf/field/float_field.rb +43 -0
- data/lib/protobuf/field/int32_field.rb +21 -0
- data/lib/protobuf/field/int64_field.rb +34 -0
- data/lib/protobuf/field/integer_field.rb +23 -0
- data/lib/protobuf/field/message_field.rb +51 -0
- data/lib/protobuf/field/sfixed32_field.rb +27 -0
- data/lib/protobuf/field/sfixed64_field.rb +28 -0
- data/lib/protobuf/field/signed_integer_field.rb +29 -0
- data/lib/protobuf/field/sint32_field.rb +21 -0
- data/lib/protobuf/field/sint64_field.rb +21 -0
- data/lib/protobuf/field/string_field.rb +51 -0
- data/lib/protobuf/field/uint32_field.rb +21 -0
- data/lib/protobuf/field/uint64_field.rb +21 -0
- data/lib/protobuf/field/varint_field.rb +77 -0
- data/lib/protobuf/field.rb +74 -0
- data/lib/protobuf/generators/base.rb +85 -0
- data/lib/protobuf/generators/enum_generator.rb +39 -0
- data/lib/protobuf/generators/extension_generator.rb +27 -0
- data/lib/protobuf/generators/field_generator.rb +193 -0
- data/lib/protobuf/generators/file_generator.rb +262 -0
- data/lib/protobuf/generators/group_generator.rb +122 -0
- data/lib/protobuf/generators/message_generator.rb +104 -0
- data/lib/protobuf/generators/option_generator.rb +17 -0
- data/lib/protobuf/generators/printable.rb +160 -0
- data/lib/protobuf/generators/service_generator.rb +50 -0
- data/lib/protobuf/lifecycle.rb +33 -0
- data/lib/protobuf/logging.rb +39 -0
- data/lib/protobuf/message/fields.rb +233 -0
- data/lib/protobuf/message/serialization.rb +85 -0
- data/lib/protobuf/message.rb +241 -0
- data/lib/protobuf/optionable.rb +72 -0
- data/lib/protobuf/tasks/compile.rake +80 -0
- data/lib/protobuf/tasks.rb +1 -0
- data/lib/protobuf/varint.rb +20 -0
- data/lib/protobuf/varint_pure.rb +31 -0
- data/lib/protobuf/version.rb +3 -0
- data/lib/protobuf/wire_type.rb +10 -0
- data/lib/protobuf.rb +91 -0
- data/proto/dynamic_discovery.proto +46 -0
- data/proto/google/protobuf/compiler/plugin.proto +183 -0
- data/proto/google/protobuf/descriptor.proto +911 -0
- data/proto/rpc.proto +71 -0
- data/resources/assess/policy.json +6 -23
- data/ruby-agent.gemspec +4 -2
- metadata +122 -33
- data/lib/contrast/agent/telemetry/events/exceptions/telemetry_exceptions_report.rb +0 -30
- data/lib/contrast/api/decorators/application_update.rb +0 -44
- data/lib/contrast/api/decorators/library.rb +0 -56
- data/lib/contrast/config/agent_configuration.rb +0 -63
- data/lib/contrast/config/heap_dump_configuration.rb +0 -59
- data/lib/contrast/config/inventory_configuration.rb +0 -33
- data/lib/contrast/config/logger_configuration.rb +0 -26
- data/lib/contrast/framework/platform_version.rb +0 -22
@@ -47,10 +47,14 @@ module Contrast
|
|
47
47
|
# Scope of the method parsed from our JSON policy.
|
48
48
|
attr_reader :method_scope
|
49
49
|
|
50
|
+
# @raise [NoMethodError] This is being raised if any of the implementing subclasses does not have
|
51
|
+
# that method implemented, but is being called on.
|
50
52
|
def node_class
|
51
53
|
raise(NoMethodError, 'specify the type of the feature for which this node patches')
|
52
54
|
end
|
53
55
|
|
56
|
+
# @raise [NoMethodError] This is being raised if any of the implementing subclasses does not have
|
57
|
+
# that method implemented, but is being called on.
|
54
58
|
def feature
|
55
59
|
raise(NoMethodError, 'specify the name of the feature for which this node patches')
|
56
60
|
end
|
@@ -62,6 +66,8 @@ module Contrast
|
|
62
66
|
# Don't let nodes be created that will be missing things we need
|
63
67
|
# later on. Really, if they don't have these things, they couldn't have
|
64
68
|
# done their jobs anyway.
|
69
|
+
#
|
70
|
+
# @raise [ArgumentError] Validates if the created nodes have everything that we'll need now or later on.
|
65
71
|
def validate
|
66
72
|
unless class_name
|
67
73
|
raise(ArgumentError, "#{ node_class } #{ id } did not have a proper class name. Unable to create.")
|
@@ -41,6 +41,7 @@ module Contrast
|
|
41
41
|
NODE
|
42
42
|
end
|
43
43
|
|
44
|
+
# @raise [ArgumentError] Validates if the created nodes have everything that we'll need now or later on.
|
44
45
|
def validate
|
45
46
|
super
|
46
47
|
unless applicator.public_methods(false).any?(applicator_method)
|
@@ -52,6 +53,7 @@ module Contrast
|
|
52
53
|
validate_rule
|
53
54
|
end
|
54
55
|
|
56
|
+
# @raise [ArgumentError] Validates if the created nodes have everything that we'll need now or later on.
|
55
57
|
def validate_properties
|
56
58
|
if (required_properties & optional_properties).any?
|
57
59
|
raise(ArgumentError,
|
@@ -66,6 +68,7 @@ module Contrast
|
|
66
68
|
raise(ArgumentError, "#{ id } did not have a required property. Unable to create.")
|
67
69
|
end
|
68
70
|
|
71
|
+
# @raise [ArgumentError] Validates if the created nodes have everything that we'll need now or later on.
|
69
72
|
def validate_rule
|
70
73
|
raise(ArgumentError, 'Unknown rule did not have a proper name. Unable to create.') unless rule_id
|
71
74
|
raise(ArgumentError, "#{ id } did not have a proper applicator. Unable to create.") unless applicator
|
@@ -31,13 +31,12 @@ module Contrast
|
|
31
31
|
# was invoked
|
32
32
|
# @param args [Array<Object>] the arguments passed to the triggering
|
33
33
|
# method at invocation
|
34
|
-
# @raise [Contrast::SecurityException] on block, will pass the
|
35
|
-
# exception from the rule
|
36
34
|
def invoke _method, _exception, _properties, _object, args
|
37
35
|
return unless valid_input?(args)
|
38
36
|
return if skip_analysis?
|
39
37
|
|
40
38
|
rule.infilter(Contrast::Agent::REQUEST_TRACKER.current, args[0])
|
39
|
+
# add rescue here
|
41
40
|
end
|
42
41
|
|
43
42
|
# Calls the actual rule for this applicator, if required, when the
|
@@ -46,13 +45,12 @@ module Contrast
|
|
46
45
|
#
|
47
46
|
# @param arg [Object] the argument passed to the triggering method
|
48
47
|
# at invocation
|
49
|
-
# @raise [Contrast::SecurityException] on block, will pass the
|
50
|
-
# exception from the rule
|
51
48
|
def prepended_invoke arg
|
52
49
|
return unless arg&.cs__is_a?(String)
|
53
50
|
return if skip_analysis?
|
54
51
|
|
55
52
|
rule.infilter(Contrast::Agent::REQUEST_TRACKER.current, arg)
|
53
|
+
# add rescue here
|
56
54
|
end
|
57
55
|
|
58
56
|
# Allow the rule to check if the given input is an attempt to
|
@@ -67,6 +65,7 @@ module Contrast
|
|
67
65
|
return if skip_analysis?
|
68
66
|
|
69
67
|
rule.check_command_scope(command)
|
68
|
+
# add rescue here
|
70
69
|
end
|
71
70
|
|
72
71
|
protected
|
@@ -64,6 +64,7 @@ module Contrast
|
|
64
64
|
write_marker && possible_write?(write_marker)
|
65
65
|
end
|
66
66
|
|
67
|
+
# @raise [Contrast::SecurityException] re-raises if some attack is blocked and raised from the infilter
|
67
68
|
def path_traversal_rule path, possible_write, object, method
|
68
69
|
return unless applies_to?(path, possible_write: possible_write)
|
69
70
|
|
@@ -61,8 +61,7 @@ module Contrast
|
|
61
61
|
# was invoked
|
62
62
|
# @param _args [Array<Object>] the arguments passed to the triggering
|
63
63
|
# method at invocation
|
64
|
-
# @raise [
|
65
|
-
# exception from the rule
|
64
|
+
# @raise [NoMethodError] This is abstract method
|
66
65
|
def invoke _method, _exception, _properties, _object, _args
|
67
66
|
raise(NoMethodError, 'This is abstract, override it.')
|
68
67
|
end
|
@@ -70,6 +69,7 @@ module Contrast
|
|
70
69
|
# The name of the rule, as expected by the Contrast Service and Contrast UI.
|
71
70
|
#
|
72
71
|
# @return [String]
|
72
|
+
# @raise [NoMethodError] This is abstract method
|
73
73
|
def rule_name
|
74
74
|
raise(NoMethodError, 'This is abstract, override it.')
|
75
75
|
end
|
@@ -230,6 +230,7 @@ module Contrast
|
|
230
230
|
# the rule and matched the attack detection logic
|
231
231
|
# @param _kwargs [Hash] key-value pairs used by the rule to build a
|
232
232
|
# report.
|
233
|
+
# @raise[NoMethodError] raises if subclass did not implement this method on extend
|
233
234
|
def find_attacker _context, _potential_attack_string, **_kwargs
|
234
235
|
raise(NoMethodError, "Rule #{ rule_name } did not implement find_attack")
|
235
236
|
end
|
@@ -38,14 +38,12 @@ module Contrast
|
|
38
38
|
@_thread = Contrast::Agent::Thread.new do
|
39
39
|
logger.debug('Starting background Reporter thread.')
|
40
40
|
loop do
|
41
|
-
|
42
|
-
|
43
|
-
# to figure out why that is and lock it so that it isn't.
|
44
|
-
next unless client && connection
|
41
|
+
next unless connected?
|
42
|
+
next unless app_create_complete?
|
45
43
|
|
46
44
|
process_event(queue.pop)
|
47
45
|
rescue StandardError => e
|
48
|
-
logger.debug('Reporter thread could not process because of:',
|
46
|
+
logger.debug('Reporter thread could not process because of:', e)
|
49
47
|
end
|
50
48
|
end
|
51
49
|
end
|
@@ -71,7 +69,6 @@ module Contrast
|
|
71
69
|
end
|
72
70
|
return unless event
|
73
71
|
|
74
|
-
logger.debug('Enqueued event for sending', event_type: event.cs__class)
|
75
72
|
queue << event
|
76
73
|
end
|
77
74
|
|
@@ -84,7 +81,9 @@ module Contrast
|
|
84
81
|
logger.warn('Reporter attempted to send event immediately with Agent disabled', caller: caller, event: event)
|
85
82
|
return
|
86
83
|
end
|
87
|
-
|
84
|
+
return unless event
|
85
|
+
|
86
|
+
client.send_event(event, connection)
|
88
87
|
rescue StandardError => e
|
89
88
|
logger.error('Could not send message to TeamServer from Reporter queue.', e)
|
90
89
|
end
|
@@ -108,6 +107,32 @@ module Contrast
|
|
108
107
|
@_queue ||= Queue.new
|
109
108
|
end
|
110
109
|
|
110
|
+
# TODO: RUBY-99999
|
111
|
+
# The client and connection are being used in multiple threads/ concurrently, and that's not okay. We need
|
112
|
+
# to figure out why that is and lock it so that it isn't.
|
113
|
+
#
|
114
|
+
# @return [Boolean]
|
115
|
+
def connected?
|
116
|
+
return true if client && connection
|
117
|
+
|
118
|
+
logger.debug('No client/connection; sleeping', client: client, connection: connection)
|
119
|
+
sleep(5)
|
120
|
+
false
|
121
|
+
end
|
122
|
+
|
123
|
+
# Unless we're in bypass mode, we need to make sure the service has started and built the application on
|
124
|
+
# TeamServer since we're doing a split style here.
|
125
|
+
#
|
126
|
+
# @return [Boolean]
|
127
|
+
def app_create_complete?
|
128
|
+
return true if Contrast::CONTRAST_SERVICE.use_agent_communication?
|
129
|
+
return true if Contrast::Agent.messaging_queue&.speedracer&.status&.startup_messages_sent?
|
130
|
+
|
131
|
+
logger.debug('Service startup incomplete; Application may not be created; sleeping')
|
132
|
+
sleep(5)
|
133
|
+
false
|
134
|
+
end
|
135
|
+
|
111
136
|
# @param event [Contrast::Agent::Reporting::ReportingEvent]
|
112
137
|
def process_event event
|
113
138
|
client.send_event(event, connection)
|
@@ -3,47 +3,51 @@
|
|
3
3
|
|
4
4
|
require 'contrast/agent/worker_thread'
|
5
5
|
require 'contrast/agent/reporting/report'
|
6
|
-
require 'contrast/
|
7
|
-
require 'contrast/agent/reporting/reporting_events/
|
6
|
+
require 'contrast/agent/inventory/dependency_usage_analysis'
|
7
|
+
require 'contrast/agent/reporting/reporting_events/poll'
|
8
|
+
require 'contrast/agent/reporting/reporting_events/server_activity'
|
8
9
|
|
9
10
|
module Contrast
|
10
11
|
module Agent
|
11
12
|
# The ReporterHeartbeat will make sure that the process remains marked alive by TeamServer and that we periodically
|
12
|
-
# reach out to get the latest settings for this application.
|
13
|
+
# reach out to get the latest settings for this application. It also sends out those messages which do not need to
|
14
|
+
# be associated directly with a request, such as Server Activity and Library Observation.
|
13
15
|
class ReporterHeartbeat < WorkerThread
|
14
|
-
include Contrast::Components::Logger::InstanceMethods
|
15
|
-
|
16
16
|
# TeamServer will mark an application offline after 5 minutes. Sending this every one should be more than enough
|
17
17
|
# to satisfy our goals.
|
18
18
|
REFRESH_INTERVAL_SEC = 60
|
19
19
|
|
20
|
-
# check if we can report to TS
|
21
|
-
#
|
22
|
-
# @return[Boolean] true if bypass is enabled, or false if bypass disabled
|
23
|
-
def enabled?
|
24
|
-
@_enabled = Contrast::CONTRAST_SERVICE.use_agent_communication? if @_enabled.nil?
|
25
|
-
@_enabled
|
26
|
-
end
|
27
|
-
|
28
|
-
def connection
|
29
|
-
@_connection ||= client.initialize_connection
|
30
|
-
end
|
31
|
-
|
32
20
|
def start_thread!
|
33
21
|
return if running?
|
34
22
|
|
35
23
|
@_thread = Contrast::Agent::Thread.new do
|
36
24
|
logger.info('Starting heartbeat thread.')
|
37
25
|
loop do
|
38
|
-
|
26
|
+
polling_events.each do |event|
|
27
|
+
Contrast::Agent.reporter&.send_event(event)
|
28
|
+
end
|
29
|
+
clean_properties
|
39
30
|
sleep(REFRESH_INTERVAL_SEC)
|
40
31
|
end
|
41
32
|
end
|
42
33
|
end
|
43
34
|
|
35
|
+
private
|
36
|
+
|
44
37
|
def poll_message
|
45
38
|
@_poll_message ||= Contrast::Agent::Reporting::Poll.new
|
46
39
|
end
|
40
|
+
|
41
|
+
# Those events which should be sent periodically, rather than on event or request.
|
42
|
+
#
|
43
|
+
# @return [Array<Contrast::Agent::Reporting::ReportingEvent>]
|
44
|
+
def polling_events
|
45
|
+
[
|
46
|
+
Contrast::Agent::Inventory::DependencyUsageAnalysis.instance.generate_library_usage,
|
47
|
+
Contrast::Agent::Reporting::ServerActivity.new,
|
48
|
+
poll_message
|
49
|
+
].compact
|
50
|
+
end
|
47
51
|
end
|
48
52
|
end
|
49
53
|
end
|
@@ -56,35 +56,31 @@ module Contrast
|
|
56
56
|
# @param attacker_activity [Contrast::Agent::Reporting::ApplicationDefendAttackerActivity]
|
57
57
|
# @param rule [String]
|
58
58
|
def attach_existing existing_attacker_activity, attacker_activity, rule
|
59
|
-
# TODO: RUBY-1663 figure out attack time mapping
|
60
59
|
new_violation = attacker_activity.protection_rules[rule]
|
60
|
+
sample_activity = Contrast::Agent::Reporting::ApplicationDefendAttackSampleActivity
|
61
61
|
if (previously_violated = existing_attacker_activity.protection_rules[rule])
|
62
|
-
if (new_blocked = new_violation.blocked
|
63
|
-
|
64
|
-
|
65
|
-
|
66
|
-
previously_violated.blocked.samples.concat(new_blocked)
|
62
|
+
if (new_blocked = new_violation.blocked)
|
63
|
+
previously_violated.blocked ||= sample_activity.new
|
64
|
+
previously_violated.blocked.samples.concat(new_blocked.samples) if new_blocked.samples
|
65
|
+
previously_violated.blocked.merge_time_maps(new_blocked.time_map)
|
67
66
|
end
|
68
67
|
|
69
|
-
if (new_exploited = new_violation.exploited
|
70
|
-
|
71
|
-
|
72
|
-
|
73
|
-
previously_violated.exploited.samples.concat(new_exploited)
|
68
|
+
if (new_exploited = new_violation.exploited)
|
69
|
+
previously_violated.exploited ||= sample_activity.new
|
70
|
+
previously_violated.exploited.samples.concat(new_exploited.samples) if new_exploited.samples
|
71
|
+
previously_violated.exploited.merge_time_maps(new_exploited.time_map)
|
74
72
|
end
|
75
73
|
|
76
|
-
if (new_ineffective = new_violation.ineffective
|
77
|
-
|
78
|
-
|
79
|
-
|
80
|
-
previously_violated.ineffective.samples.concat(new_ineffective)
|
74
|
+
if (new_ineffective = new_violation.ineffective)
|
75
|
+
previously_violated.ineffective ||= sample_activity.new
|
76
|
+
previously_violated.ineffective.samples.concat(new_ineffective.samples) if new_ineffective.samples
|
77
|
+
previously_violated.ineffective.merge_time_maps(new_ineffective.time_map)
|
81
78
|
end
|
82
79
|
|
83
|
-
if (new_suspicious = new_violation.suspicious
|
84
|
-
|
85
|
-
|
86
|
-
|
87
|
-
previously_violated.suspicious.samples.concat(new_suspicious)
|
80
|
+
if (new_suspicious = new_violation.suspicious)
|
81
|
+
previously_violated.suspicious ||= sample_activity.new
|
82
|
+
previously_violated.suspicious.samples.concat(new_suspicious.samples) if new_suspicious.samples
|
83
|
+
previously_violated.suspicious.merge_time_maps(new_suspicious.time_map)
|
88
84
|
end
|
89
85
|
else
|
90
86
|
existing_attacker_activity.protection_rules[rule] = new_violation
|
data/lib/contrast/agent/reporting/reporting_events/application_defend_attack_sample_activity.rb
CHANGED
@@ -2,6 +2,7 @@
|
|
2
2
|
# frozen_string_literal: true
|
3
3
|
|
4
4
|
require 'contrast/components/logger'
|
5
|
+
require 'contrast/utils/timer'
|
5
6
|
require 'contrast/agent/reporting/reporting_events/application_defend_attack_sample'
|
6
7
|
|
7
8
|
module Contrast
|
@@ -26,7 +27,7 @@ module Contrast
|
|
26
27
|
|
27
28
|
def initialize
|
28
29
|
@samples = []
|
29
|
-
@start_time =
|
30
|
+
@start_time = Contrast::Agent::REQUEST_TRACKER.current&.timer&.start_ms || 0 # in ms
|
30
31
|
@event_type = :application_defend_attack_sample_activity
|
31
32
|
@time_map = Hash.new { |h, k| h[k] = 0 }
|
32
33
|
super
|
@@ -36,7 +37,7 @@ module Contrast
|
|
36
37
|
{
|
37
38
|
attackTimeMap: time_map,
|
38
39
|
samples: samples.map(&:to_controlled_hash),
|
39
|
-
startTime: @start_time,
|
40
|
+
startTime: @start_time, # Start time in ms.
|
40
41
|
total: 1 # there will only ever be 1 attack sample, until batching is done
|
41
42
|
}
|
42
43
|
end
|
@@ -44,12 +45,34 @@ module Contrast
|
|
44
45
|
# @param attack_result [Contrast::Api::Dtm::AttackResult]
|
45
46
|
def attach_data attack_result
|
46
47
|
attack_result.samples.each do |attack_sample|
|
48
|
+
base_time = Contrast::Agent::REQUEST_TRACKER.current&.timer&.start_ms || 0
|
49
|
+
dmt_time = attack_sample.timestamp_ms.to_i
|
47
50
|
converted = Contrast::Agent::Reporting::ApplicationDefendAttackSample.convert(attack_result, attack_sample)
|
48
51
|
samples << converted
|
49
|
-
|
52
|
+
@start_time = if dmt_time.zero?
|
53
|
+
@start_time
|
54
|
+
else
|
55
|
+
dmt_time
|
56
|
+
end
|
57
|
+
attack_second = (@start_time - base_time) / 1000 # in seconds
|
50
58
|
time_map[attack_second] += 1
|
51
59
|
end
|
52
60
|
end
|
61
|
+
|
62
|
+
# This method will merge time_maps of attack samples with same
|
63
|
+
# type.
|
64
|
+
#
|
65
|
+
# @param map [Hash<Integer,Integer>] TimeMap to append to previously_violated rule
|
66
|
+
# samples.
|
67
|
+
# @return time_map [Hash<Integer,Integer>] merged time map with updated occurrences.
|
68
|
+
def merge_time_maps map
|
69
|
+
# If the second is the same (key) if we just merge there won't be a new entry,
|
70
|
+
# so just increase the attack count.
|
71
|
+
map.each_key do |key|
|
72
|
+
@time_map[key] = @time_map.fetch(key, 0) + map[key]
|
73
|
+
end
|
74
|
+
@time_map
|
75
|
+
end
|
53
76
|
end
|
54
77
|
end
|
55
78
|
end
|
@@ -17,23 +17,16 @@ module Contrast
|
|
17
17
|
# system. Contains data used by TeamServer to render the Flow Map and SCA features.
|
18
18
|
#
|
19
19
|
# @attr_reader components [Array<Contrast::Agent::Reporting::ArchitectureComponent>]
|
20
|
-
# @
|
20
|
+
# @attr_accessor libraries [Array<Contrast::Agent::Reporting::LibraryDiscovery>]
|
21
21
|
class ApplicationUpdate < Contrast::Agent::Reporting::ApplicationReportingEvent
|
22
|
-
attr_reader :components
|
23
|
-
|
24
|
-
class << self
|
25
|
-
# @param app_update_dtm [Contrast::Api::Dtm::ApplicationUpdate]
|
26
|
-
# @return [Contrast::Agent::Reporting::ApplicationUpdate]
|
27
|
-
def convert app_update_dtm
|
28
|
-
report = new
|
29
|
-
report.attach_data(app_update_dtm)
|
30
|
-
report
|
31
|
-
end
|
32
|
-
end
|
22
|
+
attr_reader :components
|
23
|
+
attr_accessor :libraries
|
33
24
|
|
34
25
|
def initialize
|
35
26
|
@event_method = :PUT
|
36
27
|
@event_endpoint = "#{ Contrast::API.api_url }/api/ng/update/application"
|
28
|
+
@components = []
|
29
|
+
@libraries = []
|
37
30
|
super
|
38
31
|
end
|
39
32
|
|
@@ -41,18 +34,6 @@ module Contrast
|
|
41
34
|
'update-application'
|
42
35
|
end
|
43
36
|
|
44
|
-
# Attach the data from the protobuf models to this reporter so that it can be sent to TeamServer directly.
|
45
|
-
#
|
46
|
-
# @param app_update_dtm [Contrast::Api::Dtm::ApplicationUpdate]
|
47
|
-
def attach_data app_update_dtm
|
48
|
-
@components = app_update_dtm.components.map do |component|
|
49
|
-
Contrast::Agent::Reporting::ArchitectureComponent.convert(component)
|
50
|
-
end
|
51
|
-
@libraries = app_update_dtm.libraries.values.map do |library|
|
52
|
-
Contrast::Agent::Reporting::LibraryDiscovery.convert(library)
|
53
|
-
end
|
54
|
-
end
|
55
|
-
|
56
37
|
# Convert the instance variables on the class, and other information, into the identifiers required for
|
57
38
|
# TeamServer to process the JSON form of this message.
|
58
39
|
#
|
@@ -2,6 +2,7 @@
|
|
2
2
|
# frozen_string_literal: true
|
3
3
|
|
4
4
|
require 'contrast/api/dtm.pb'
|
5
|
+
require 'contrast/components/logger'
|
5
6
|
|
6
7
|
module Contrast
|
7
8
|
module Agent
|
@@ -17,6 +18,7 @@ module Contrast
|
|
17
18
|
# @attr_reader url [String] the url used to connect to the component. Required for reporting.
|
18
19
|
# @attr_reader vendor [String] the publisher of the component, like MySQL.
|
19
20
|
class ArchitectureComponent
|
21
|
+
include Contrast::Components::Logger::InstanceMethods
|
20
22
|
# required attributes
|
21
23
|
attr_reader :type, :url
|
22
24
|
# optional attributes
|
@@ -55,7 +57,12 @@ module Contrast
|
|
55
57
|
# @return [Hash]
|
56
58
|
# @raise [ArgumentError]
|
57
59
|
def to_controlled_hash
|
58
|
-
|
60
|
+
begin
|
61
|
+
validate
|
62
|
+
rescue ArgumentError => e
|
63
|
+
logger.error('ArchitectureComponent validation failed with: ', e)
|
64
|
+
return
|
65
|
+
end
|
59
66
|
{
|
60
67
|
remoteHost: remote_host,
|
61
68
|
remotePort: remote_port,
|
@@ -16,6 +16,8 @@ module Contrast
|
|
16
16
|
# includes the literal URL and HTTP Verb used to invoke them, as they must have been called at this point to be
|
17
17
|
# recorded.
|
18
18
|
class DiscoveredRoute < Contrast::Agent::Reporting::ObservedRoute
|
19
|
+
include Contrast::Components::Logger::InstanceMethods
|
20
|
+
|
19
21
|
class << self
|
20
22
|
# @param obj [Regexp, Object]
|
21
23
|
# @return [String]
|
@@ -104,7 +106,12 @@ module Contrast
|
|
104
106
|
end
|
105
107
|
|
106
108
|
def to_controlled_hash
|
107
|
-
|
109
|
+
begin
|
110
|
+
validate
|
111
|
+
rescue ArgumentError => e
|
112
|
+
logger.error('DiscoveredRoute validation failed with: ', e)
|
113
|
+
return
|
114
|
+
end
|
108
115
|
{ session_id: ::Contrast::ASSESS.session_id, signature: @signature, verb: @verb, url: @url }.compact
|
109
116
|
end
|
110
117
|
|
@@ -130,7 +130,13 @@ module Contrast
|
|
130
130
|
# @return [Hash]
|
131
131
|
# @raise [ArgumentError]
|
132
132
|
def to_controlled_hash
|
133
|
-
|
133
|
+
begin
|
134
|
+
validate
|
135
|
+
rescue ArgumentError => e
|
136
|
+
logger.error('Finding event validation failed with: ', e)
|
137
|
+
return
|
138
|
+
end
|
139
|
+
|
134
140
|
hsh = {
|
135
141
|
created: created,
|
136
142
|
hash: hash_code.to_s,
|
@@ -8,6 +8,7 @@ require 'contrast/agent/reporting/reporting_events/finding_event_signature'
|
|
8
8
|
require 'contrast/agent/reporting/reporting_events/finding_event_source'
|
9
9
|
require 'contrast/agent/reporting/reporting_events/finding_event_stack'
|
10
10
|
require 'contrast/agent/reporting/reporting_events/finding_event_taint_range'
|
11
|
+
require 'contrast/components/logger'
|
11
12
|
|
12
13
|
module Contrast
|
13
14
|
module Agent
|
@@ -17,6 +18,8 @@ module Contrast
|
|
17
18
|
# construct the vulnerability information for the assess feature. They represent the operation the application
|
18
19
|
# underwent that transformed data during the dataflow.
|
19
20
|
class FindingEvent
|
21
|
+
include Contrast::Components::Logger::InstanceMethods
|
22
|
+
|
20
23
|
# @return [Symbol] what the event did; CREATION, A2O, A2P, A2A, A2R, O2A, O2O, O2P, O2R, P2A, P2O, P2P, P2R,
|
21
24
|
# TAG, TRIGGER.
|
22
25
|
attr_reader :action
|
@@ -120,7 +123,13 @@ module Contrast
|
|
120
123
|
# @return [Hash]
|
121
124
|
# @raise [ArgumentError]
|
122
125
|
def to_controlled_hash # rubocop:disable Metrics/AbcSize
|
123
|
-
|
126
|
+
begin
|
127
|
+
validate
|
128
|
+
rescue ArgumentError => e
|
129
|
+
logger.error('FindingEvent validation failed with: ', e)
|
130
|
+
return
|
131
|
+
end
|
132
|
+
|
124
133
|
{
|
125
134
|
action: action,
|
126
135
|
args: args.map(&:to_controlled_hash),
|
@@ -3,6 +3,7 @@
|
|
3
3
|
|
4
4
|
require 'base64'
|
5
5
|
require 'contrast/agent/assess/contrast_object'
|
6
|
+
require 'contrast/components/logger'
|
6
7
|
|
7
8
|
module Contrast
|
8
9
|
module Agent
|
@@ -12,6 +13,8 @@ module Contrast
|
|
12
13
|
# TeamServer to construct the vulnerability information for the assess feature. They represent those parts of the
|
13
14
|
# objects that were acted on in a Dataflow Finding.
|
14
15
|
class FindingEventObject
|
16
|
+
include Contrast::Components::Logger::InstanceMethods
|
17
|
+
|
15
18
|
# @return [Integer] the id of the Object this represents.
|
16
19
|
attr_reader :hash
|
17
20
|
# @return [Boolean] if the Object is tracked or not
|
@@ -52,7 +55,13 @@ module Contrast
|
|
52
55
|
# @return [Hash]
|
53
56
|
# @raise [ArgumentError]
|
54
57
|
def to_controlled_hash
|
55
|
-
|
58
|
+
begin
|
59
|
+
validate
|
60
|
+
rescue ArgumentError => e
|
61
|
+
logger.error('FindingEventObject validation failed with: ', e)
|
62
|
+
return
|
63
|
+
end
|
64
|
+
|
56
65
|
{
|
57
66
|
hash: hash,
|
58
67
|
tracked: tracked,
|
@@ -60,6 +69,7 @@ module Contrast
|
|
60
69
|
}
|
61
70
|
end
|
62
71
|
|
72
|
+
# @raise [ArgumentError]
|
63
73
|
def validate
|
64
74
|
raise(ArgumentError, "#{ self } did not have a proper hash. Unable to continue.") unless hash
|
65
75
|
raise(ArgumentError, "#{ self } did not have a proper tracked. Unable to continue.") if tracked.nil?
|
@@ -2,6 +2,7 @@
|
|
2
2
|
# frozen_string_literal: true
|
3
3
|
|
4
4
|
require 'base64'
|
5
|
+
require 'contrast/components/logger'
|
5
6
|
|
6
7
|
module Contrast
|
7
8
|
module Agent
|
@@ -11,6 +12,8 @@ module Contrast
|
|
11
12
|
# used by TeamServer to relate this event to those that came previously. They represent the events that directly
|
12
13
|
# preceding the FindingEvent generated.
|
13
14
|
class FindingEventParentObject
|
15
|
+
include Contrast::Components::Logger::InstanceMethods
|
16
|
+
|
14
17
|
# @return [Integer] the Id of the parent event
|
15
18
|
attr_reader :id
|
16
19
|
|
@@ -24,12 +27,19 @@ module Contrast
|
|
24
27
|
# @return [Hash]
|
25
28
|
# @raise [ArgumentError]
|
26
29
|
def to_controlled_hash
|
27
|
-
|
30
|
+
begin
|
31
|
+
validate
|
32
|
+
rescue ArgumentError => e
|
33
|
+
logger.error('FindingEventParentObject validation failed with: ', e)
|
34
|
+
return
|
35
|
+
end
|
36
|
+
|
28
37
|
{
|
29
38
|
id: id
|
30
39
|
}
|
31
40
|
end
|
32
41
|
|
42
|
+
# @raise [ArgumentError]
|
33
43
|
def validate
|
34
44
|
raise(ArgumentError, "#{ self } did not have a proper id. Unable to continue.") unless id
|
35
45
|
end
|
@@ -1,6 +1,8 @@
|
|
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
|
+
|
4
6
|
module Contrast
|
5
7
|
module Agent
|
6
8
|
module Reporting
|
@@ -8,6 +10,8 @@ module Contrast
|
|
8
10
|
# system to relay this information in the Finding/Trace messages. Events have properties on them which are held
|
9
11
|
# as an array of key-value pairs.
|
10
12
|
class FindingEventProperty
|
13
|
+
include Contrast::Components::Logger::InstanceMethods
|
14
|
+
|
11
15
|
# @return [String] the key of the property
|
12
16
|
attr_reader :key
|
13
17
|
# @return [String] the value of the source
|
@@ -24,13 +28,20 @@ module Contrast
|
|
24
28
|
# @return [Hash]
|
25
29
|
# @raise [ArgumentError]
|
26
30
|
def to_controlled_hash
|
27
|
-
|
31
|
+
begin
|
32
|
+
validate
|
33
|
+
rescue ArgumentError => e
|
34
|
+
logger.error('FindingEventProperty validation failed with: ', e)
|
35
|
+
return
|
36
|
+
end
|
37
|
+
|
28
38
|
{
|
29
39
|
key: key,
|
30
40
|
value: value
|
31
41
|
}
|
32
42
|
end
|
33
43
|
|
44
|
+
# @raise [ArgumentError]
|
34
45
|
def validate
|
35
46
|
raise(ArgumentError, "#{ self } did not have a proper key. Unable to continue.") unless key && !key.empty?
|
36
47
|
end
|