contrast-agent 3.15.0 → 4.3.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/Rakefile +1 -0
- data/ext/cs__assess_marshal_module/cs__assess_marshal_module.c +22 -10
- data/ext/cs__assess_marshal_module/cs__assess_marshal_module.h +4 -3
- data/lib/contrast/agent.rb +4 -12
- data/lib/contrast/agent/assess/contrast_event.rb +121 -130
- data/lib/contrast/agent/assess/contrast_object.rb +51 -0
- data/lib/contrast/agent/assess/events/source_event.rb +5 -10
- data/lib/contrast/agent/assess/policy/dynamic_source_factory.rb +10 -3
- data/lib/contrast/agent/assess/policy/patcher.rb +4 -3
- data/lib/contrast/agent/assess/policy/policy_node.rb +46 -69
- data/lib/contrast/agent/assess/policy/policy_scanner.rb +19 -2
- data/lib/contrast/agent/assess/policy/preshift.rb +3 -3
- data/lib/contrast/agent/assess/policy/propagation_method.rb +13 -19
- data/lib/contrast/agent/assess/policy/propagation_node.rb +12 -24
- data/lib/contrast/agent/assess/policy/propagator/append.rb +1 -2
- data/lib/contrast/agent/assess/policy/propagator/center.rb +1 -2
- data/lib/contrast/agent/assess/policy/propagator/custom.rb +1 -1
- data/lib/contrast/agent/assess/policy/propagator/database_write.rb +1 -3
- data/lib/contrast/agent/assess/policy/propagator/insert.rb +2 -3
- data/lib/contrast/agent/assess/policy/propagator/keep.rb +1 -2
- data/lib/contrast/agent/assess/policy/propagator/match_data.rb +3 -5
- data/lib/contrast/agent/assess/policy/propagator/next.rb +1 -2
- data/lib/contrast/agent/assess/policy/propagator/prepend.rb +1 -2
- data/lib/contrast/agent/assess/policy/propagator/remove.rb +2 -4
- data/lib/contrast/agent/assess/policy/propagator/replace.rb +1 -2
- data/lib/contrast/agent/assess/policy/propagator/reverse.rb +1 -2
- data/lib/contrast/agent/assess/policy/propagator/select.rb +4 -7
- data/lib/contrast/agent/assess/policy/propagator/splat.rb +2 -9
- data/lib/contrast/agent/assess/policy/propagator/split.rb +77 -122
- data/lib/contrast/agent/assess/policy/propagator/substitution.rb +32 -25
- data/lib/contrast/agent/assess/policy/propagator/trim.rb +3 -7
- data/lib/contrast/agent/assess/policy/source_method.rb +2 -14
- data/lib/contrast/agent/assess/policy/trigger/reflected_xss.rb +9 -13
- data/lib/contrast/agent/assess/policy/trigger/xpath.rb +1 -1
- 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/policy/trigger_validation/ssrf_validator.rb +1 -1
- data/lib/contrast/agent/assess/property/evented.rb +5 -18
- data/lib/contrast/agent/assess/property/tagged.rb +28 -16
- 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 +83 -14
- data/lib/contrast/agent/assess/rule/redos.rb +1 -1
- data/lib/contrast/agent/assess/tag.rb +1 -1
- data/lib/contrast/agent/assess/tracker.rb +16 -18
- data/lib/contrast/agent/at_exit_hook.rb +5 -5
- data/lib/contrast/agent/deadzone/policy/deadzone_node.rb +7 -0
- data/lib/contrast/agent/inventory.rb +15 -0
- data/lib/contrast/agent/inventory/dependencies.rb +50 -0
- data/lib/contrast/agent/inventory/dependency_analysis.rb +37 -0
- data/lib/contrast/agent/inventory/dependency_usage_analysis.rb +104 -0
- data/lib/contrast/agent/inventory/gemfile_digest_cache.rb +38 -0
- data/lib/contrast/agent/middleware.rb +51 -3
- 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/method_policy.rb +1 -1
- data/lib/contrast/agent/patching/policy/module_policy.rb +10 -10
- data/lib/contrast/agent/patching/policy/patch.rb +6 -0
- 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_deserialization_rule.rb +47 -1
- data/lib/contrast/agent/protect/policy/applies_path_traversal_rule.rb +4 -3
- data/lib/contrast/agent/protect/policy/applies_xxe_rule.rb +1 -1
- data/lib/contrast/agent/protect/policy/rule_applicator.rb +53 -0
- data/lib/contrast/agent/protect/rule/base.rb +63 -14
- data/lib/contrast/agent/protect/rule/cmd_injection.rb +12 -28
- data/lib/contrast/agent/protect/rule/default_scanner.rb +1 -4
- data/lib/contrast/agent/protect/rule/deserialization.rb +4 -1
- data/lib/contrast/agent/protect/rule/no_sqli.rb +3 -3
- data/lib/contrast/agent/protect/rule/no_sqli/mongo_no_sql_scanner.rb +1 -0
- data/lib/contrast/agent/protect/rule/sqli.rb +3 -3
- data/lib/contrast/agent/protect/rule/xxe.rb +32 -11
- data/lib/contrast/agent/protect/rule/xxe/entity_wrapper.rb +10 -6
- data/lib/contrast/agent/reaction_processor.rb +1 -1
- data/lib/contrast/agent/request.rb +34 -34
- data/lib/contrast/agent/request_handler.rb +1 -1
- data/lib/contrast/agent/response.rb +5 -5
- data/lib/contrast/agent/rewriter.rb +3 -3
- data/lib/contrast/agent/scope.rb +81 -55
- data/lib/contrast/agent/static_analysis.rb +15 -9
- data/lib/contrast/agent/tracepoint_hook.rb +1 -1
- data/lib/contrast/agent/version.rb +1 -1
- data/lib/contrast/api/communication/socket_client.rb +36 -1
- data/lib/contrast/api/decorators.rb +3 -0
- data/lib/contrast/api/decorators/address.rb +13 -14
- data/lib/contrast/api/decorators/application_update.rb +1 -1
- data/lib/contrast/api/decorators/library.rb +54 -0
- data/lib/contrast/api/decorators/library_usage_update.rb +31 -0
- data/lib/contrast/api/decorators/message.rb +1 -0
- data/lib/contrast/api/decorators/trace_event.rb +31 -41
- data/lib/contrast/api/decorators/trace_event_object.rb +11 -3
- data/lib/contrast/api/decorators/trace_event_signature.rb +27 -5
- data/lib/contrast/api/decorators/user_input.rb +2 -1
- data/lib/contrast/common_agent_configuration.rb +2 -1
- data/lib/contrast/components/agent.rb +6 -5
- data/lib/contrast/components/app_context.rb +39 -30
- data/lib/contrast/components/assess.rb +36 -0
- data/lib/contrast/components/config.rb +29 -37
- data/lib/contrast/components/contrast_service.rb +9 -9
- data/lib/contrast/components/interface.rb +30 -6
- data/lib/contrast/components/inventory.rb +6 -1
- data/lib/contrast/components/scope.rb +72 -6
- data/lib/contrast/components/settings.rb +23 -23
- data/lib/contrast/config/assess_configuration.rb +2 -1
- data/lib/contrast/config/inventory_configuration.rb +2 -2
- data/lib/contrast/config/service_configuration.rb +4 -2
- data/lib/contrast/configuration.rb +1 -1
- data/lib/contrast/extension/assess/array.rb +9 -6
- data/lib/contrast/extension/assess/erb.rb +6 -3
- data/lib/contrast/extension/assess/eval_trigger.rb +6 -6
- data/lib/contrast/extension/assess/exec_trigger.rb +0 -3
- data/lib/contrast/extension/assess/fiber.rb +5 -6
- data/lib/contrast/extension/assess/hash.rb +7 -5
- data/lib/contrast/extension/assess/kernel.rb +19 -22
- data/lib/contrast/extension/assess/marshal.rb +40 -28
- data/lib/contrast/extension/assess/regexp.rb +6 -11
- data/lib/contrast/extension/assess/string.rb +14 -13
- data/lib/contrast/extension/protect/kernel.rb +3 -3
- data/lib/contrast/framework/base_support.rb +51 -53
- data/lib/contrast/framework/manager.rb +6 -5
- data/lib/contrast/framework/rack/patch/session_cookie.rb +10 -10
- data/lib/contrast/framework/rack/support.rb +2 -1
- data/lib/contrast/framework/rails/patch/action_controller_live_buffer.rb +14 -14
- data/lib/contrast/framework/rails/patch/assess_configuration.rb +1 -1
- data/lib/contrast/framework/rails/patch/rails_application_configuration.rb +11 -11
- data/lib/contrast/framework/rails/patch/support.rb +1 -1
- data/lib/contrast/framework/rails/rewrite/action_controller_railties_helper_inherited.rb +12 -12
- data/lib/contrast/framework/rails/rewrite/active_record_attribute_methods_read.rb +13 -13
- data/lib/contrast/framework/rails/rewrite/active_record_named.rb +3 -3
- data/lib/contrast/framework/rails/rewrite/active_record_time_zone_inherited.rb +13 -13
- data/lib/contrast/framework/rails/support.rb +5 -1
- data/lib/contrast/framework/sinatra/patch/base.rb +11 -11
- data/lib/contrast/framework/sinatra/support.rb +7 -6
- data/lib/contrast/logger/application.rb +1 -4
- data/lib/contrast/logger/log.rb +7 -2
- data/lib/contrast/utils/duck_utils.rb +1 -1
- data/lib/contrast/utils/heap_dump_util.rb +1 -1
- data/lib/contrast/utils/invalid_configuration_util.rb +2 -5
- data/lib/contrast/utils/inventory_util.rb +0 -7
- data/lib/contrast/utils/object_share.rb +3 -3
- data/lib/contrast/utils/preflight_util.rb +1 -1
- data/lib/contrast/utils/prevent_serialization.rb +1 -1
- data/lib/contrast/utils/resource_loader.rb +1 -1
- data/lib/contrast/utils/sha256_builder.rb +2 -14
- data/lib/contrast/utils/string_utils.rb +1 -1
- data/lib/contrast/utils/tag_util.rb +9 -13
- data/resources/assess/policy.json +31 -12
- data/resources/deadzone/policy.json +156 -0
- data/resources/protect/policy.json +12 -0
- data/ruby-agent.gemspec +11 -6
- data/service_executables/VERSION +1 -1
- data/service_executables/linux/contrast-service +0 -0
- data/service_executables/mac/contrast-service +0 -0
- metadata +91 -28
- data/lib/contrast/utils/boolean_util.rb +0 -30
- data/lib/contrast/utils/gemfile_reader.rb +0 -193
@@ -10,23 +10,11 @@ module Contrast
|
|
10
10
|
module Property
|
11
11
|
# This module serves to hold the functionality required for the
|
12
12
|
# management of our dataflow events.
|
13
|
+
#
|
14
|
+
# @attr_reader event [Contrast::Agent::Assess::ContrastEvent] the
|
15
|
+
# latest event to track
|
13
16
|
module Evented
|
14
|
-
|
15
|
-
#
|
16
|
-
# @return [Array<Contrast::Agent::Assess::ContrastEvent>]
|
17
|
-
def events
|
18
|
-
@_events ||= []
|
19
|
-
end
|
20
|
-
|
21
|
-
# Add an event to these properties. It will be used to build
|
22
|
-
# a trace if this object ends up in a trigger.
|
23
|
-
#
|
24
|
-
# @param event [Contrast::Agent::Assess::ContrastEvent] the latest
|
25
|
-
# event to track
|
26
|
-
def add_event event
|
27
|
-
events << event
|
28
|
-
self
|
29
|
-
end
|
17
|
+
attr_accessor :event
|
30
18
|
|
31
19
|
# Create a new event and add it to the event set.
|
32
20
|
#
|
@@ -43,8 +31,7 @@ module Contrast
|
|
43
31
|
# the key used to accessed if from a map or nil if a type like
|
44
32
|
# BODY
|
45
33
|
def build_event policy_node, tagged, object, ret, args, source_type = nil, source_name = nil
|
46
|
-
event = Contrast::Agent::Assess::Events::EventFactory.build(policy_node, tagged, object, ret, args, source_type, source_name)
|
47
|
-
add_event(event)
|
34
|
+
@event = Contrast::Agent::Assess::Events::EventFactory.build(policy_node, tagged, object, ret, args, source_type, source_name)
|
48
35
|
report_sources(tagged, event)
|
49
36
|
end
|
50
37
|
|
@@ -79,6 +79,10 @@ module Contrast
|
|
79
79
|
# range : 5-10
|
80
80
|
# result : 0-05
|
81
81
|
#
|
82
|
+
# Note that we disable Lint/DuplicateBranch in this branch in order
|
83
|
+
# list out all tag range cases in the proper order to make this
|
84
|
+
# easier to understand
|
85
|
+
#
|
82
86
|
# @param range [Range] the span to check, inclusive to exclusive
|
83
87
|
# @return [Hash{String => Contrast::Agent::Assess::Tag}] the hash of
|
84
88
|
# key to tags
|
@@ -101,10 +105,10 @@ module Contrast
|
|
101
105
|
finish = range.size - start
|
102
106
|
add << Contrast::Agent::Assess::Tag.new(tag.label, finish, start)
|
103
107
|
# the tag spans the requested range.
|
104
|
-
when Contrast::Agent::Assess::Tag::WITHOUT
|
108
|
+
when Contrast::Agent::Assess::Tag::WITHOUT # rubocop:disable Lint/DuplicateBranch
|
105
109
|
add << Contrast::Agent::Assess::Tag.new(tag.label, range.size)
|
106
110
|
# part of the tag is being selected
|
107
|
-
when Contrast::Agent::Assess::Tag::HIGH_SPAN
|
111
|
+
when Contrast::Agent::Assess::Tag::HIGH_SPAN # rubocop:disable Lint/DuplicateBranch
|
108
112
|
add << Contrast::Agent::Assess::Tag.new(tag.label, range.size)
|
109
113
|
end
|
110
114
|
end
|
@@ -162,19 +166,17 @@ module Contrast
|
|
162
166
|
end
|
163
167
|
|
164
168
|
# We'll use this as a helper method to retrieve tags from the hash.
|
165
|
-
# Because the hash auto-populates an empty array when we try to
|
166
|
-
# a tag in it, we cannot use the [] method without side
|
167
|
-
# around this, we'll use a fetch work around.
|
169
|
+
# Because the hash auto-populates an empty array when we try to
|
170
|
+
# access a tag in it, we cannot use the [] method without side
|
171
|
+
# effect. To get around this, we'll use a fetch work around.
|
172
|
+
#
|
173
|
+
# @param label [Symbol] the label to look up
|
174
|
+
# @return [Array<Contrast::Agent::Assess::Tag>] all the tags with
|
175
|
+
# that label
|
168
176
|
def fetch_tag label
|
169
177
|
tags.fetch(label, nil) if tracked?
|
170
178
|
end
|
171
179
|
|
172
|
-
# Convert the tags of this object into the TraceTaintRange required
|
173
|
-
# to be sent to the service
|
174
|
-
def tags_to_dtm
|
175
|
-
Contrast::Api::Dtm::TraceTaintRange.build_for_event(tags)
|
176
|
-
end
|
177
|
-
|
178
180
|
# Remove all tags within the given ranges.
|
179
181
|
# This does not delete an entire tag if part of that tag is
|
180
182
|
# outside this range, meaning we may reduce sizes of tags
|
@@ -295,9 +297,19 @@ module Contrast
|
|
295
297
|
end
|
296
298
|
end
|
297
299
|
|
298
|
-
# Shift the tag ranges covering the given range
|
299
|
-
# We assume this is for a insertion, meaning we
|
300
|
-
# have to move tags to the right
|
300
|
+
# Shift the tag ranges covering the given range to account for new
|
301
|
+
# data being added. We assume this is for a insertion, meaning we
|
302
|
+
# have to move tags out of the range and to the right. For example,
|
303
|
+
# given current tags: 0-15
|
304
|
+
# when we insert a range: 5-10
|
305
|
+
# then the result is: 0-5, 10-20
|
306
|
+
#
|
307
|
+
# Note that we disable Lint/DuplicateBranch in this branch in order
|
308
|
+
# list out all tag range cases in the proper order to make this
|
309
|
+
# easier to understand
|
310
|
+
#
|
311
|
+
# @param range [Range] the range of new information that's been
|
312
|
+
# inserted
|
301
313
|
def shift_tags_for_insertion range
|
302
314
|
return unless tracked?
|
303
315
|
|
@@ -319,13 +331,13 @@ module Contrast
|
|
319
331
|
when Contrast::Agent::Assess::Tag::WITHIN
|
320
332
|
tag.shift(length)
|
321
333
|
# the tag spans the range. leave the part below alone
|
322
|
-
when Contrast::Agent::Assess::Tag::WITHOUT
|
334
|
+
when Contrast::Agent::Assess::Tag::WITHOUT # rubocop:disable Lint/DuplicateBranch
|
323
335
|
new_tag = tag.clone
|
324
336
|
new_tag.update_start(range.begin)
|
325
337
|
new_tag.shift(length)
|
326
338
|
add << new_tag
|
327
339
|
tag.update_end(range.begin)
|
328
|
-
when Contrast::Agent::Assess::Tag::HIGH_SPAN, Contrast::Agent::Assess::Tag::ABOVE
|
340
|
+
when Contrast::Agent::Assess::Tag::HIGH_SPAN, Contrast::Agent::Assess::Tag::ABOVE # rubocop:disable Lint/DuplicateBranch
|
329
341
|
tag.shift(length)
|
330
342
|
end
|
331
343
|
end
|
@@ -33,6 +33,64 @@ module Contrast
|
|
33
33
|
NON_KEY_PARTIAL_NAMES.none? { |name| constant_string.index(name) }
|
34
34
|
end
|
35
35
|
|
36
|
+
BYTE_HOLDERS = %i[ARRAY LIST].cs__freeze
|
37
|
+
# Determine if the given value node violates the hardcode key rule
|
38
|
+
# @param value_node [RubyVM::AbstractSyntaxTree::Node] the node to
|
39
|
+
# evaluate
|
40
|
+
# @return [Boolean]
|
41
|
+
def value_node_passes? value_node
|
42
|
+
# If it's a freeze call, then evaluate the entity being frozen
|
43
|
+
value_node = value_node.children[0] if freeze_call?(value_node)
|
44
|
+
# If it's a String being turned into bytes, then it matches key
|
45
|
+
# expectations
|
46
|
+
return true if bytes_call?(value_node)
|
47
|
+
|
48
|
+
type = value_node.type
|
49
|
+
return false unless BYTE_HOLDERS.include?(type)
|
50
|
+
return false unless value_node.children.any?
|
51
|
+
|
52
|
+
# Unless this is an array of literal numerics, we don't match.
|
53
|
+
# That array seems to always end in a nil value, so we allow
|
54
|
+
# those as well.
|
55
|
+
value_node.children.each do |child|
|
56
|
+
next unless child
|
57
|
+
|
58
|
+
return false unless child.cs__is_a?(RubyVM::AbstractSyntaxTree::Node) &&
|
59
|
+
child.type == :LIT &&
|
60
|
+
child.children[0]&.cs__is_a?(Integer)
|
61
|
+
end
|
62
|
+
|
63
|
+
true
|
64
|
+
end
|
65
|
+
|
66
|
+
REDACTED_MARKER = ' = [**REDACTED**]'
|
67
|
+
def redacted_marker
|
68
|
+
REDACTED_MARKER
|
69
|
+
end
|
70
|
+
|
71
|
+
# A node is a bytes_call if it's the Node for String#bytes. We care
|
72
|
+
# about this specifically as it's likely to be a common way to
|
73
|
+
# generate a key constant, rather than directly declaring an
|
74
|
+
# integer array.
|
75
|
+
#
|
76
|
+
# @param value_node [RubyVM::AbstractSyntaxTree::Node] the node to
|
77
|
+
# evaluate
|
78
|
+
# @return [Boolean] is this a node for String#bytes or not
|
79
|
+
def bytes_call? value_node
|
80
|
+
return false unless value_node.type == :CALL
|
81
|
+
|
82
|
+
children = value_node.children
|
83
|
+
return false unless children
|
84
|
+
return false unless children.length >= 2
|
85
|
+
|
86
|
+
potential_string_node = children[0]
|
87
|
+
return false unless potential_string_node.cs__is_a?(RubyVM::AbstractSyntaxTree::Node) &&
|
88
|
+
potential_string_node.type == :STR
|
89
|
+
|
90
|
+
children[1] == :bytes
|
91
|
+
end
|
92
|
+
|
93
|
+
# TODO: RUBY-1014 remove `#value_type_passes?` and `#value_passes?`
|
36
94
|
# If the value is a byte array, or at least an array of numbers, it
|
37
95
|
# passes for this rule
|
38
96
|
def value_type_passes? value
|
@@ -49,11 +107,6 @@ module Contrast
|
|
49
107
|
def value_passes? _value
|
50
108
|
true
|
51
109
|
end
|
52
|
-
|
53
|
-
REDACTED_MARKER = ' = [**REDACTED**]'
|
54
|
-
def redacted_marker
|
55
|
-
REDACTED_MARKER
|
56
|
-
end
|
57
110
|
end
|
58
111
|
end
|
59
112
|
end
|
@@ -38,15 +38,18 @@ module Contrast
|
|
38
38
|
NON_PASSWORD_PARTIAL_NAMES.none? { |name| constant_string.index(name) }
|
39
39
|
end
|
40
40
|
|
41
|
-
#
|
42
|
-
|
43
|
-
|
44
|
-
|
41
|
+
# Determine if the given value node violates the hardcode key rule
|
42
|
+
# @param value_node [RubyVM::AbstractSyntaxTree::Node] the node to
|
43
|
+
# evaluate
|
44
|
+
# @return [Boolean]
|
45
|
+
def value_node_passes? value_node
|
46
|
+
# If it's a freeze call, then evaluate the entity being frozen
|
47
|
+
value_node = value_node.children[0] if freeze_call?(value_node)
|
48
|
+
return false unless value_node.type == :STR
|
45
49
|
|
46
|
-
|
47
|
-
|
48
|
-
|
49
|
-
!probably_property_name?(value)
|
50
|
+
# https://www.rubydoc.info/gems/ruby-internal/Node/STR
|
51
|
+
string = value_node.children[0]
|
52
|
+
!probably_property_name?(string)
|
50
53
|
end
|
51
54
|
|
52
55
|
# If a field name matches an expected password field, we'll check it's
|
@@ -65,6 +68,18 @@ module Contrast
|
|
65
68
|
def redacted_marker
|
66
69
|
REDACTED_MARKER
|
67
70
|
end
|
71
|
+
|
72
|
+
# TODO: RUBY-1014 remove `#value_type_passes?` and `#value_passes?`
|
73
|
+
# If the value is a string, it passes for this rule
|
74
|
+
def value_type_passes? value
|
75
|
+
value.is_a?(String)
|
76
|
+
end
|
77
|
+
|
78
|
+
# If the value probably isn't a property name, it passes for this
|
79
|
+
# rule
|
80
|
+
def value_passes? value
|
81
|
+
!probably_property_name?(value)
|
82
|
+
end
|
68
83
|
end
|
69
84
|
end
|
70
85
|
end
|
@@ -1,6 +1,7 @@
|
|
1
1
|
# Copyright (c) 2020 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/agent/assess/policy/trigger_method'
|
4
5
|
require 'contrast/components/interface'
|
5
6
|
require 'contrast/extension/module'
|
6
7
|
|
@@ -9,14 +10,13 @@ module Contrast
|
|
9
10
|
module Assess
|
10
11
|
module Rule
|
11
12
|
module Provider
|
12
|
-
# Hardcoded rules detect if any secret value has been written
|
13
|
-
# sourcecode of the application. To use this base
|
14
|
-
# implement
|
13
|
+
# Hardcoded rules detect if any secret value has been written
|
14
|
+
# directly into the sourcecode of the application. To use this base
|
15
|
+
# class, a provider must implement three methods:
|
15
16
|
# 1) name_passes? : does the constant name match a given value set
|
16
|
-
# 2)
|
17
|
-
#
|
18
|
-
# 3)
|
19
|
-
# 4) redacted_marker : the value to plug in for the obfuscated value
|
17
|
+
# 2) value_node_passes? : does the value of the constant match a
|
18
|
+
# given value set
|
19
|
+
# 3) redacted_marker : the value to plug in for the obfuscated value
|
20
20
|
module HardcodedValueRule
|
21
21
|
include Contrast::Components::Interface
|
22
22
|
access_component :analysis, :app_context, :logging
|
@@ -25,6 +25,7 @@ module Contrast
|
|
25
25
|
!ASSESS.enabled? || ASSESS.rule_disabled?(rule_id)
|
26
26
|
end
|
27
27
|
|
28
|
+
# TODO: RUBY-1014 - remove `#analyze`
|
28
29
|
COMMON_CONSTANTS = %i[
|
29
30
|
CONTRAST_ASSESS_POLICY_STATUS
|
30
31
|
VERSION
|
@@ -69,10 +70,31 @@ module Contrast
|
|
69
70
|
# if it looks like a placeholder / pointer to a config, skip it
|
70
71
|
next unless value_passes?(value)
|
71
72
|
|
72
|
-
|
73
|
+
build_finding(clazz, constant_string)
|
73
74
|
end
|
74
75
|
end
|
75
76
|
|
77
|
+
# Parse the file pertaining to the given TracePoint to walk its AST
|
78
|
+
# to determine if a Constant is hardcoded. For our purposes, this
|
79
|
+
# hard coding means directly set rather than as an interpolated
|
80
|
+
# String or through a method call.
|
81
|
+
#
|
82
|
+
# Note: This is a top layer check, we make no assertions about what
|
83
|
+
# the methods or interpolations do. Their presence, even if only
|
84
|
+
# calling a hardcoded thing, causes this check to not report.
|
85
|
+
#
|
86
|
+
# @param trace_point [TracePoint] the TracePoint event created on
|
87
|
+
# the :end of a Module being loaded
|
88
|
+
# @param ast [RubyVM::AbstractSyntaxTree::Node] the abstract syntax
|
89
|
+
# tree of the Module defined in the TracePoint end event
|
90
|
+
def parse trace_point, ast
|
91
|
+
return if disabled?
|
92
|
+
|
93
|
+
parse_ast(trace_point.self, ast)
|
94
|
+
rescue StandardError => e
|
95
|
+
logger.error('Unable to parse AST for hardcoded keys', e, module: trace_point.self)
|
96
|
+
end
|
97
|
+
|
76
98
|
# Constants can be variable or classes defined in the given
|
77
99
|
# class. We ONLY want the variables, which should be defined in
|
78
100
|
# the MACRO_CASE (upper case & underscore format)
|
@@ -90,7 +112,57 @@ module Contrast
|
|
90
112
|
|
91
113
|
private
|
92
114
|
|
93
|
-
|
115
|
+
# @param mod [Module] the module to which this AST pertains
|
116
|
+
# @param ast [RubyVM::AbstractSyntaxTree::Node, Object] a node
|
117
|
+
# within the AST, which may be a leaf, so any Object
|
118
|
+
def parse_ast mod, ast
|
119
|
+
return unless ast.cs__is_a?(RubyVM::AbstractSyntaxTree::Node)
|
120
|
+
return unless ast.cs__respond_to?(:children)
|
121
|
+
|
122
|
+
children = ast.children
|
123
|
+
return unless children.any?
|
124
|
+
|
125
|
+
ast.children.each do |child|
|
126
|
+
parse_ast(mod, child)
|
127
|
+
end
|
128
|
+
|
129
|
+
# https://www.rubydoc.info/gems/ruby-internal/Node/CDECL
|
130
|
+
return unless ast.type == :CDECL
|
131
|
+
|
132
|
+
# The CDECL Node has two children, the first being the Constant
|
133
|
+
# name as a symbol, the second as the value to assign to that
|
134
|
+
# constant
|
135
|
+
children = ast.children
|
136
|
+
name = children[0].to_s
|
137
|
+
# If that constant name doesn't pass our checks, move on.
|
138
|
+
return unless name_passes?(name)
|
139
|
+
|
140
|
+
value = children[1]
|
141
|
+
# The assignment node could be a direct value or a call of some
|
142
|
+
# sort. We leave it to each rule to properly handle these nodes.
|
143
|
+
return unless value_node_passes?(value)
|
144
|
+
|
145
|
+
build_finding(mod, name)
|
146
|
+
end
|
147
|
+
|
148
|
+
# Constants can be set as frozen directly. We need to account for
|
149
|
+
# this change as it means the Node given to the :CDECL call will be
|
150
|
+
# a :CALL, not a constant.
|
151
|
+
#
|
152
|
+
# @param value_node [RubyVM::AbstractSyntaxTree::Node] the node to
|
153
|
+
# evaluate
|
154
|
+
# @return [Boolean] is this a freeze call or not
|
155
|
+
def freeze_call? value_node
|
156
|
+
return false unless value_node.type == :CALL
|
157
|
+
|
158
|
+
children = value_node.children
|
159
|
+
return false unless children
|
160
|
+
return false unless children.length >= 2
|
161
|
+
|
162
|
+
children[1] == :freeze
|
163
|
+
end
|
164
|
+
|
165
|
+
def build_finding clazz, constant_string
|
94
166
|
class_name = clazz.cs__name
|
95
167
|
|
96
168
|
finding = Contrast::Api::Dtm::Finding.new
|
@@ -104,13 +176,10 @@ module Contrast
|
|
104
176
|
hash = Contrast::Utils::HashDigest.generate_class_scanning_hash(finding)
|
105
177
|
finding.hash_code = Contrast::Utils::StringUtils.protobuf_safe_string(hash)
|
106
178
|
finding.preflight = Contrast::Utils::PreflightUtil.create_preflight(finding)
|
107
|
-
|
108
|
-
activity = Contrast::Api::Dtm::Activity.new
|
109
|
-
activity.findings << finding
|
110
|
-
|
111
|
-
Contrast::Agent.messaging_queue.send_event_eventually(activity)
|
179
|
+
Contrast::Agent::Assess::Policy::TriggerMethod.report_finding(finding)
|
112
180
|
rescue StandardError => e
|
113
181
|
logger.error('Unable to build a finding for Hardcoded Rule', e)
|
182
|
+
nil
|
114
183
|
end
|
115
184
|
end
|
116
185
|
end
|
@@ -33,7 +33,7 @@ module Contrast
|
|
33
33
|
# (2) regexp must evaluate against user input
|
34
34
|
return unless trigger_node.violated?(string)
|
35
35
|
|
36
|
-
Contrast::Agent::Assess::Policy::TriggerMethod.build_finding(context, trigger_node, source, object, ret, args)
|
36
|
+
Contrast::Agent::Assess::Policy::TriggerMethod.build_finding(context, trigger_node, source, object, ret, *args)
|
37
37
|
end
|
38
38
|
|
39
39
|
protected
|
@@ -14,7 +14,7 @@ module Contrast
|
|
14
14
|
|
15
15
|
# Initialize a new tag
|
16
16
|
#
|
17
|
-
# @param label [String] the
|
17
|
+
# @param label [String] the label of the tag
|
18
18
|
# @param length [Integer] the length of the string described with this
|
19
19
|
# tag
|
20
20
|
# @param start_idx [Integer] (0) the starting position in the string for
|
@@ -14,7 +14,20 @@ module Contrast
|
|
14
14
|
PROPERTIES_HASH = Contrast::Agent::Assess::Finalizers::Hash.new
|
15
15
|
|
16
16
|
class << self
|
17
|
+
# Retrieve the properties of the given Object, iff they exist.
|
18
|
+
#
|
19
|
+
# @param source [Object] the thing for which to look up properties.
|
20
|
+
# @return [Contrast::Agent::Assess::Properties, nil]
|
17
21
|
def properties source
|
22
|
+
PROPERTIES_HASH[source]
|
23
|
+
end
|
24
|
+
|
25
|
+
# Retrieve the properties of the given Object, returning a new
|
26
|
+
# instance if one does not already exist and the source is trackable.
|
27
|
+
#
|
28
|
+
# @param source [Object] the thing for which to look up properties.
|
29
|
+
# @return [Contrast::Agent::Assess::Properties, nil]
|
30
|
+
def properties! source
|
18
31
|
return unless trackable?(source)
|
19
32
|
|
20
33
|
PROPERTIES_HASH[source] ||= Contrast::Agent::Assess::Properties.new
|
@@ -33,30 +46,15 @@ module Contrast
|
|
33
46
|
end
|
34
47
|
|
35
48
|
# Copy the properties from one object to the next, assuming the
|
36
|
-
# target does not already have its own properties.
|
49
|
+
# target does not already have its own properties. This should only
|
50
|
+
# ever be called when building PreShift for those objects sources
|
51
|
+
# which are already tracked.
|
37
52
|
#
|
38
53
|
# @param source [Object] the instance from which to copy properties
|
39
54
|
# @param target [Object] the instance to which to copy properties
|
40
55
|
def copy source, target
|
41
56
|
PROPERTIES_HASH[target] ||= properties(source).dup
|
42
57
|
end
|
43
|
-
|
44
|
-
# Duplicate the given object, returning the duplicate after copying
|
45
|
-
# the properties of the original and storing them as the properties
|
46
|
-
# of the duplicate.
|
47
|
-
#
|
48
|
-
# @param source [Object] the thing to duplicate
|
49
|
-
# @return [Object] the duplicate of the original, or the original if
|
50
|
-
# it does not respond to duplication
|
51
|
-
def duplicate source
|
52
|
-
return source unless source
|
53
|
-
|
54
|
-
duplicate = source.dup
|
55
|
-
PROPERTIES_HASH[duplicate] ||= PROPERTIES_HASH[source].dup
|
56
|
-
duplicate
|
57
|
-
rescue StandardError
|
58
|
-
source
|
59
|
-
end
|
60
58
|
end
|
61
59
|
end
|
62
60
|
end
|