contrast-agent 3.15.0 → 3.16.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/lib/contrast/agent.rb +2 -9
- data/lib/contrast/agent/assess/contrast_event.rb +142 -70
- data/lib/contrast/agent/assess/events/source_event.rb +1 -1
- data/lib/contrast/agent/assess/policy/dynamic_source_factory.rb +10 -3
- data/lib/contrast/agent/assess/policy/policy_node.rb +15 -10
- data/lib/contrast/agent/assess/policy/policy_scanner.rb +7 -1
- data/lib/contrast/agent/assess/policy/propagator/insert.rb +1 -1
- data/lib/contrast/agent/assess/policy/propagator/match_data.rb +0 -3
- data/lib/contrast/agent/assess/policy/propagator/select.rb +1 -3
- data/lib/contrast/agent/assess/policy/propagator/splat.rb +0 -5
- data/lib/contrast/agent/assess/policy/propagator/split.rb +12 -13
- data/lib/contrast/agent/assess/policy/propagator/substitution.rb +21 -14
- data/lib/contrast/agent/assess/policy/trigger/reflected_xss.rb +4 -5
- data/lib/contrast/agent/assess/policy/trigger_method.rb +39 -14
- data/lib/contrast/agent/assess/policy/trigger_node.rb +31 -37
- data/lib/contrast/agent/assess/property/evented.rb +5 -18
- data/lib/contrast/agent/assess/property/tagged.rb +9 -3
- data/lib/contrast/agent/assess/property/updated.rb +0 -5
- data/lib/contrast/agent/assess/rule/provider/hardcoded_key.rb +58 -5
- data/lib/contrast/agent/assess/rule/provider/hardcoded_password.rb +23 -8
- data/lib/contrast/agent/assess/rule/provider/hardcoded_value_rule.rb +82 -14
- data/lib/contrast/agent/assess/tag.rb +1 -1
- data/lib/contrast/agent/at_exit_hook.rb +5 -5
- data/lib/contrast/agent/patching/policy/after_load_patch.rb +5 -5
- data/lib/contrast/agent/patching/policy/after_load_patcher.rb +20 -20
- data/lib/contrast/agent/patching/policy/module_policy.rb +10 -10
- data/lib/contrast/agent/patching/policy/policy.rb +16 -2
- data/lib/contrast/agent/protect/policy/applies_command_injection_rule.rb +3 -5
- data/lib/contrast/agent/protect/policy/applies_xxe_rule.rb +1 -1
- data/lib/contrast/agent/protect/rule/no_sqli/mongo_no_sql_scanner.rb +1 -0
- data/lib/contrast/agent/request.rb +34 -34
- data/lib/contrast/agent/static_analysis.rb +6 -6
- data/lib/contrast/agent/version.rb +1 -1
- data/lib/contrast/api/communication/socket_client.rb +36 -1
- data/lib/contrast/api/decorators/address.rb +13 -13
- data/lib/contrast/api/decorators/message.rb +1 -0
- data/lib/contrast/api/decorators/trace_event.rb +20 -18
- data/lib/contrast/components/app_context.rb +39 -30
- data/lib/contrast/components/contrast_service.rb +9 -9
- data/lib/contrast/components/settings.rb +20 -23
- data/lib/contrast/config/service_configuration.rb +4 -2
- data/lib/contrast/configuration.rb +1 -1
- data/lib/contrast/extension/assess/array.rb +7 -3
- data/lib/contrast/extension/assess/erb.rb +5 -0
- data/lib/contrast/extension/assess/eval_trigger.rb +6 -6
- data/lib/contrast/extension/assess/exec_trigger.rb +1 -1
- data/lib/contrast/extension/assess/fiber.rb +3 -3
- data/lib/contrast/extension/assess/hash.rb +3 -3
- data/lib/contrast/extension/assess/kernel.rb +18 -20
- data/lib/contrast/extension/assess/marshal.rb +8 -4
- data/lib/contrast/extension/assess/regexp.rb +3 -3
- data/lib/contrast/extension/assess/string.rb +13 -11
- data/lib/contrast/extension/protect/kernel.rb +3 -3
- data/lib/contrast/framework/base_support.rb +1 -1
- data/lib/contrast/framework/manager.rb +3 -3
- data/lib/contrast/framework/rack/patch/session_cookie.rb +9 -9
- data/lib/contrast/framework/rails/patch/action_controller_live_buffer.rb +13 -13
- data/lib/contrast/framework/rails/patch/rails_application_configuration.rb +10 -10
- data/lib/contrast/framework/rails/patch/support.rb +1 -1
- data/lib/contrast/framework/rails/rewrite/action_controller_railties_helper_inherited.rb +11 -11
- data/lib/contrast/framework/rails/rewrite/active_record_attribute_methods_read.rb +12 -12
- data/lib/contrast/framework/rails/rewrite/active_record_named.rb +3 -3
- data/lib/contrast/framework/rails/rewrite/active_record_time_zone_inherited.rb +12 -12
- data/lib/contrast/framework/sinatra/patch/base.rb +11 -11
- data/lib/contrast/framework/sinatra/support.rb +4 -4
- data/lib/contrast/logger/log.rb +7 -2
- data/lib/contrast/utils/invalid_configuration_util.rb +2 -5
- data/resources/assess/policy.json +31 -12
- data/ruby-agent.gemspec +4 -3
- data/service_executables/VERSION +1 -1
- data/service_executables/linux/contrast-service +0 -0
- data/service_executables/mac/contrast-service +0 -0
- metadata +31 -17
@@ -17,12 +17,12 @@ module Contrast
|
|
17
17
|
# report the already-loaded gems.
|
18
18
|
def catchup
|
19
19
|
@_catchup ||= begin
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
20
|
+
with_contrast_scope do
|
21
|
+
Contrast::Utils::GemfileReader.instance.map_loaded_classes
|
22
|
+
send_inventory_message
|
23
|
+
true
|
24
|
+
end
|
25
|
+
end
|
26
26
|
rescue StandardError => e
|
27
27
|
logger.warn('Unable to run post-initialization static analysis', e)
|
28
28
|
end
|
@@ -15,7 +15,7 @@ module Contrast
|
|
15
15
|
# service proxy and tracks the state of that proxy.
|
16
16
|
class SocketClient
|
17
17
|
include Contrast::Components::Interface
|
18
|
-
access_component :contrast_service, :logging
|
18
|
+
access_component :config, :contrast_service, :logging
|
19
19
|
|
20
20
|
def initialize
|
21
21
|
@socket = init_connection
|
@@ -34,6 +34,7 @@ module Contrast
|
|
34
34
|
private
|
35
35
|
|
36
36
|
def init_connection
|
37
|
+
log_connection
|
37
38
|
if CONTRAST_SERVICE.use_tcp?
|
38
39
|
Contrast::Api::Communication::TcpSocket.new(CONTRAST_SERVICE.host, CONTRAST_SERVICE.port)
|
39
40
|
else
|
@@ -41,6 +42,40 @@ module Contrast
|
|
41
42
|
end
|
42
43
|
end
|
43
44
|
|
45
|
+
def log_connection
|
46
|
+
# The socket is set,
|
47
|
+
if CONFIG.root.agent.service.socket
|
48
|
+
logger.info('Connecting to the Contrast Service using a UnixSocket socket',
|
49
|
+
socket: CONTRAST_SERVICE.socket_path)
|
50
|
+
return
|
51
|
+
end
|
52
|
+
# The host & port are set,
|
53
|
+
if CONFIG.root.agent.service.host && CONFIG.root.agent.service.port
|
54
|
+
logger.info('Connecting to the Contrast Service using a TCP socket',
|
55
|
+
host: CONTRAST_SERVICE.host,
|
56
|
+
port: CONTRAST_SERVICE.port)
|
57
|
+
return
|
58
|
+
end
|
59
|
+
|
60
|
+
# Or something is not set.
|
61
|
+
msg = if CONFIG.root.agent.service.host
|
62
|
+
'Missing a required connection value to the Contrast Service. ' \
|
63
|
+
'`agent.service.port` is not set. ' \
|
64
|
+
'Falling back to default TCP socket port.'
|
65
|
+
elsif CONFIG.root.agent.service.port
|
66
|
+
'Missing a required connection value to the Contrast Service. ' \
|
67
|
+
'`agent.service.host` is not set. ' \
|
68
|
+
'Falling back to default TCP socket host.'
|
69
|
+
else
|
70
|
+
'Missing a required connection value to the Contrast Service. ' \
|
71
|
+
'Neither `agent.service.socket` nor the pair of `agent.service.host` and `agent.service.port` are set. '\
|
72
|
+
'Falling back to default TCP socket.'
|
73
|
+
end
|
74
|
+
logger.warn(msg,
|
75
|
+
host: CONTRAST_SERVICE.host,
|
76
|
+
port: CONTRAST_SERVICE.port)
|
77
|
+
end
|
78
|
+
|
44
79
|
def send_message msg
|
45
80
|
return unless msg
|
46
81
|
|
@@ -28,19 +28,19 @@ module Contrast
|
|
28
28
|
# @return [Contrast::Api::Dtm::Address] the address of this server
|
29
29
|
def build_receiver
|
30
30
|
@_build_receiver ||= begin
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
|
41
|
-
|
42
|
-
|
43
|
-
|
31
|
+
address = new
|
32
|
+
address.host = 'localhost'
|
33
|
+
address.ip = '127.0.0.1'
|
34
|
+
begin
|
35
|
+
Timeout.timeout(1) do
|
36
|
+
address.host = Contrast::Utils::StringUtils.force_utf8(Socket.gethostname)
|
37
|
+
address.ip = Contrast::Utils::StringUtils.force_utf8(Resolv.getaddress(address.host))
|
38
|
+
end
|
39
|
+
rescue StandardError => e
|
40
|
+
logger.warn('Unable to resolve host or ip', e, address: address)
|
41
|
+
end
|
42
|
+
address
|
43
|
+
end
|
44
44
|
end
|
45
45
|
|
46
46
|
# Parse out the sender of the Request and return it as an Address
|
@@ -47,10 +47,25 @@ module Contrast
|
|
47
47
|
Contrast::Agent::Assess::Tracker.properties(target)
|
48
48
|
end
|
49
49
|
end
|
50
|
+
return unless properties.tracked?
|
50
51
|
|
51
|
-
|
52
|
+
self.taint_ranges += properties.tags_to_dtm
|
53
|
+
end
|
54
|
+
|
55
|
+
def build_parent_ids contrast_event
|
56
|
+
contrast_event&.parent_events&.each do |event|
|
57
|
+
next unless event
|
58
|
+
|
59
|
+
parent = Contrast::Api::Dtm::ParentObjectId.new
|
60
|
+
parent.id = event.event_id.to_i
|
61
|
+
parent_object_ids << parent
|
62
|
+
end
|
63
|
+
end
|
52
64
|
|
53
|
-
|
65
|
+
def build_stack contrast_event
|
66
|
+
# We delayed doing this as long as possible b/c it's expensive
|
67
|
+
stack_dtms = Contrast::Utils::StackTraceUtils.build_assess_stack_array(contrast_event.stack_trace)
|
68
|
+
self.stack += stack_dtms
|
54
69
|
end
|
55
70
|
|
56
71
|
# Class methods for TraceEvent
|
@@ -71,23 +86,10 @@ module Contrast
|
|
71
86
|
truncate_ret = Contrast::Utils::ObjectShare::RETURN_KEY != taint_target
|
72
87
|
event_dtm.ret = Contrast::Api::Dtm::TraceEventObject.build(contrast_event.ret, truncate_ret)
|
73
88
|
event_dtm.build_event_args(contrast_event, taint_target)
|
74
|
-
|
75
|
-
|
76
|
-
|
77
|
-
event_dtm.taint_ranges << range
|
78
|
-
end
|
79
|
-
|
80
|
-
# We delayed doing this as long as possible b/c it's expensive
|
81
|
-
stack = Contrast::Utils::StackTraceUtils.build_assess_stack_array(contrast_event.stack_trace)
|
82
|
-
event_dtm.stack += stack
|
83
|
-
|
89
|
+
event_dtm.build_parent_ids(contrast_event)
|
90
|
+
event_dtm.build_taint_ranges(contrast_event, taint_target)
|
91
|
+
event_dtm.build_stack(contrast_event)
|
84
92
|
event_dtm.object_id = contrast_event.event_id.to_i
|
85
|
-
contrast_event.parent_ids&.each do |id|
|
86
|
-
parent = Contrast::Api::Dtm::ParentObjectId.new
|
87
|
-
parent.id = id.to_i
|
88
|
-
event_dtm.parent_object_ids << parent
|
89
|
-
end
|
90
|
-
|
91
93
|
event_dtm.signature = Contrast::Api::Dtm::TraceEventSignature.build(contrast_event.ret, contrast_event.policy_node, contrast_event.args)
|
92
94
|
event_dtm
|
93
95
|
end
|
@@ -2,6 +2,7 @@
|
|
2
2
|
# frozen_string_literal: true
|
3
3
|
|
4
4
|
require 'rubygems/version'
|
5
|
+
require 'contrast/utils/object_share'
|
5
6
|
|
6
7
|
module Contrast
|
7
8
|
module Components
|
@@ -15,7 +16,7 @@ module Contrast
|
|
15
16
|
include Contrast::Components::ComponentBase
|
16
17
|
include Contrast::Components::Interface
|
17
18
|
|
18
|
-
access_component :agent, :analysis, :config
|
19
|
+
access_component :agent, :analysis, :config, :logging
|
19
20
|
|
20
21
|
DEFAULT_APP_NAME = 'rails'
|
21
22
|
DEFAULT_APP_PATH = '/'
|
@@ -28,51 +29,51 @@ module Contrast
|
|
28
29
|
|
29
30
|
def server_type
|
30
31
|
@_server_type ||= begin
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
32
|
+
tmp = CONFIG.root.server.type
|
33
|
+
tmp = Contrast::Agent.framework_manager.server_type unless Contrast::Utils::StringUtils.present?(tmp)
|
34
|
+
tmp
|
35
|
+
end
|
35
36
|
end
|
36
37
|
|
37
38
|
def name
|
38
39
|
@_name ||= begin
|
39
|
-
|
40
|
-
|
41
|
-
|
42
|
-
|
43
|
-
|
44
|
-
|
45
|
-
|
40
|
+
tmp = CONFIG.root.application.name
|
41
|
+
tmp = Contrast::Agent.framework_manager.app_name unless Contrast::Utils::StringUtils.present?(tmp)
|
42
|
+
tmp = File.basename(Dir.pwd) unless Contrast::Utils::StringUtils.present?(tmp)
|
43
|
+
Contrast::Utils::StringUtils.truncate(tmp, DEFAULT_APP_NAME)
|
44
|
+
rescue StandardError
|
45
|
+
DEFAULT_APP_NAME
|
46
|
+
end
|
46
47
|
end
|
47
48
|
|
48
49
|
def path
|
49
50
|
@_path ||= begin
|
50
|
-
|
51
|
-
|
52
|
-
|
53
|
-
|
54
|
-
|
51
|
+
tmp = CONFIG.root.application.path
|
52
|
+
Contrast::Utils::StringUtils.truncate(tmp, DEFAULT_APP_PATH)
|
53
|
+
rescue StandardError
|
54
|
+
DEFAULT_APP_PATH
|
55
|
+
end
|
55
56
|
end
|
56
57
|
|
57
58
|
def server_name
|
58
59
|
@_server_name ||= begin
|
59
|
-
|
60
|
-
|
61
|
-
|
62
|
-
|
63
|
-
|
64
|
-
|
65
|
-
|
60
|
+
tmp = CONFIG.root.server.name
|
61
|
+
tmp = Socket.gethostname unless Contrast::Utils::StringUtils.present?(tmp)
|
62
|
+
tmp = Contrast::Utils::StringUtils.force_utf8(tmp)
|
63
|
+
Contrast::Utils::StringUtils.truncate(tmp, DEFAULT_SERVER_NAME)
|
64
|
+
rescue StandardError
|
65
|
+
DEFAULT_SERVER_NAME
|
66
|
+
end
|
66
67
|
end
|
67
68
|
|
68
69
|
def server_path
|
69
70
|
@_server_path ||= begin
|
70
|
-
|
71
|
-
|
72
|
-
|
73
|
-
|
74
|
-
|
75
|
-
|
71
|
+
tmp = CONFIG.root.server.path
|
72
|
+
tmp = Dir.pwd unless Contrast::Utils::StringUtils.present?(tmp)
|
73
|
+
Contrast::Utils::StringUtils.truncate(tmp, DEFAULT_SERVER_PATH)
|
74
|
+
rescue StandardError
|
75
|
+
DEFAULT_SERVER_PATH
|
76
|
+
end
|
76
77
|
end
|
77
78
|
|
78
79
|
def build_app_startup_message
|
@@ -101,6 +102,14 @@ module Contrast
|
|
101
102
|
msg.application_tags = Contrast::Utils::StringUtils.protobuf_format CONFIG.root.application.tags
|
102
103
|
msg.library_tags = Contrast::Utils::StringUtils.protobuf_format CONFIG.root.inventory.tags
|
103
104
|
msg.finding_tags = Contrast::Utils::StringUtils.protobuf_format ASSESS.tags
|
105
|
+
logger.info('Application context',
|
106
|
+
server_name: msg.server_name,
|
107
|
+
server_path: msg.server_path,
|
108
|
+
server_type: msg.server_type,
|
109
|
+
application_name: name,
|
110
|
+
application_path: path,
|
111
|
+
application_language: Contrast::Utils::ObjectShare::RUBY)
|
112
|
+
|
104
113
|
msg
|
105
114
|
end
|
106
115
|
|
@@ -26,21 +26,21 @@ module Contrast
|
|
26
26
|
# Validates the config to decide if it's suitable for starting
|
27
27
|
# the bundled service
|
28
28
|
@_use_bundled_service ||= begin
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
|
29
|
+
# Requirement says "must be true" but that
|
30
|
+
# should be "must not be false" -- oops.
|
31
|
+
!false?(CONFIG.root.agent.start_bundled_service) &&
|
32
|
+
# Either a valid host or a valid socket
|
33
|
+
# Path validity is the service's problem
|
34
|
+
(LOCALHOST.match?(host) || !!socket_path)
|
35
|
+
end
|
36
36
|
end
|
37
37
|
|
38
38
|
def host
|
39
|
-
@_host ||= CONFIG.root.agent.service.host.to_s
|
39
|
+
@_host ||= (CONFIG.root.agent.service.host || Contrast::Config::ServiceConfiguration::DEFAULT_HOST).to_s
|
40
40
|
end
|
41
41
|
|
42
42
|
def port
|
43
|
-
@_port ||= CONFIG.root.agent.service.port.to_i
|
43
|
+
@_port ||= (CONFIG.root.agent.service.port || Contrast::Config::ServiceConfiguration::DEFAULT_PORT).to_i
|
44
44
|
end
|
45
45
|
|
46
46
|
def socket_path
|
@@ -55,23 +55,22 @@ module Contrast
|
|
55
55
|
APPLICATION_STATE_ATTRS = %i[modes_by_id exclusion_matchers disabled_assess_rules].cs__freeze
|
56
56
|
|
57
57
|
# Meta-define an accessor for each state attribute.
|
58
|
-
|
59
|
-
|
60
|
-
|
61
|
-
|
62
|
-
end
|
58
|
+
|
59
|
+
PROTECT_STATE_ATTRS.each do |attr|
|
60
|
+
define_method(attr) do
|
61
|
+
protect_state[attr]
|
63
62
|
end
|
63
|
+
end
|
64
64
|
|
65
|
-
|
66
|
-
|
67
|
-
|
68
|
-
end
|
65
|
+
ASSESS_STATE_ATTRS.each do |attr|
|
66
|
+
define_method(attr) do
|
67
|
+
assess_state[attr]
|
69
68
|
end
|
69
|
+
end
|
70
70
|
|
71
|
-
|
72
|
-
|
73
|
-
|
74
|
-
end
|
71
|
+
APPLICATION_STATE_ATTRS.each do |attr|
|
72
|
+
define_method(attr) do
|
73
|
+
application_state[attr]
|
75
74
|
end
|
76
75
|
end
|
77
76
|
|
@@ -95,18 +94,16 @@ module Contrast
|
|
95
94
|
|
96
95
|
def update_from_server_features server_features
|
97
96
|
# protect
|
98
|
-
|
99
|
-
|
100
|
-
|
101
|
-
end
|
97
|
+
|
98
|
+
@_protect_enabled = nil
|
99
|
+
protect_state[:enabled] = server_features.protect_enabled?
|
102
100
|
|
103
101
|
# assess
|
104
|
-
|
105
|
-
|
106
|
-
|
107
|
-
|
108
|
-
|
109
|
-
end
|
102
|
+
|
103
|
+
@_assess_enabled = nil
|
104
|
+
assess_state[:enabled] = server_features.assess_enabled?
|
105
|
+
assess_state[:sampling_settings] = server_features.assess.sampling
|
106
|
+
Contrast::Utils::Assess::SamplingUtil.instance.update
|
110
107
|
end
|
111
108
|
|
112
109
|
def update_from_application_settings application_settings
|
@@ -9,13 +9,15 @@ module Contrast
|
|
9
9
|
# Common Configuration settings. Those in this section pertain to the
|
10
10
|
# communication between the Agent & the Service.
|
11
11
|
class ServiceConfiguration < BaseConfiguration
|
12
|
+
# We don't set these b/c we've been asked to handle the default values of
|
13
|
+
# these settings differently, logging when we have to use them.
|
12
14
|
DEFAULT_HOST = '127.0.0.1'
|
13
15
|
DEFAULT_PORT = '30555'
|
14
16
|
|
15
17
|
KEYS = {
|
16
18
|
enable: EMPTY_VALUE,
|
17
|
-
host:
|
18
|
-
port:
|
19
|
+
host: EMPTY_VALUE,
|
20
|
+
port: EMPTY_VALUE,
|
19
21
|
socket: EMPTY_VALUE,
|
20
22
|
logger: Contrast::Config::LoggerConfiguration
|
21
23
|
}.cs__freeze
|
@@ -238,7 +238,7 @@ module Contrast
|
|
238
238
|
# @return [Hash, Object] the leaf of each
|
239
239
|
# Contrast::Config::BaseConfiguration will be returned in the N > 0 steps
|
240
240
|
# the Hash will be returned at the end of the 0 level
|
241
|
-
def convert_to_hash convert=root, hash={}
|
241
|
+
def convert_to_hash convert = root, hash = {}
|
242
242
|
case convert
|
243
243
|
when Contrast::Config::BaseConfiguration
|
244
244
|
convert.cs__class::KEYS.each_key do |key|
|
@@ -44,10 +44,13 @@ module Contrast
|
|
44
44
|
|
45
45
|
shift = 0
|
46
46
|
separator_length = separator.nil? ? 0 : separator.to_s.length
|
47
|
+
parent_events = []
|
47
48
|
ary.each do |obj|
|
48
49
|
if obj # skip nil here
|
49
50
|
properties.copy_from(obj, ret, shift)
|
50
51
|
shift += obj.to_s.length
|
52
|
+
parent_event = Contrast::Agent::Assess::Tracker.properties(obj)&.event
|
53
|
+
parent_events << parent_event if parent_event
|
51
54
|
end
|
52
55
|
shift += separator_length
|
53
56
|
end
|
@@ -60,15 +63,16 @@ module Contrast
|
|
60
63
|
ary,
|
61
64
|
ret,
|
62
65
|
[separator])
|
66
|
+
properties.event.instance_variable_set(:@_parent_events, parent_events)
|
63
67
|
ret
|
64
68
|
end
|
65
69
|
end
|
66
70
|
|
67
71
|
def instrument_array_track
|
68
72
|
@_instrument_array_track ||= begin
|
69
|
-
|
70
|
-
|
71
|
-
|
73
|
+
require 'cs__assess_array/cs__assess_array'
|
74
|
+
true
|
75
|
+
end
|
72
76
|
rescue StandardError, LoadError => e
|
73
77
|
logger.error('Error loading assess track patch', e)
|
74
78
|
false
|